mirror of
https://github.com/CappielloAntonio/tempo.git
synced 2026-01-30 14:22:05 +00:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3102bff729 | ||
|
|
992b16cc1d | ||
|
|
2046872d79 | ||
|
|
7312ae20d1 | ||
|
|
e8f2385928 | ||
|
|
204f9ff4c7 | ||
|
|
17e64f3dd0 | ||
|
|
d09e7e7981 | ||
|
|
95e9d06e47 | ||
|
|
f147c762d5 | ||
|
|
200ce6555b | ||
|
|
b5e5537691 | ||
|
|
3b80725673 | ||
|
|
d7c5be834e | ||
|
|
4a5672fc77 | ||
|
|
34fa2f456e | ||
|
|
663285611b | ||
|
|
a62cc78e18 |
@@ -1,5 +1,3 @@
|
||||
<h1 align="center"> Tempo </h1>
|
||||
<br>
|
||||
<p align="center">
|
||||
<img alt="Tempo" title="Tempo" src="mockup/svg/horizontal_logo.svg" width="250">
|
||||
</p>
|
||||
|
||||
@@ -10,8 +10,8 @@ android {
|
||||
applicationId 'com.cappielloantonio.tempo'
|
||||
minSdkVersion 24
|
||||
targetSdkVersion 34
|
||||
versionCode 5
|
||||
versionName '3.3.0'
|
||||
versionCode 11
|
||||
versionName '3.4.4'
|
||||
|
||||
testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
|
||||
|
||||
@@ -62,7 +62,7 @@ dependencies {
|
||||
implementation 'androidx.navigation:navigation-fragment-ktx:2.6.0'
|
||||
implementation 'androidx.navigation:navigation-ui-ktx:2.6.0'
|
||||
implementation 'androidx.recyclerview:recyclerview:1.3.0'
|
||||
implementation 'androidx.room:room-runtime:2.5.1'
|
||||
implementation 'androidx.room:room-runtime:2.5.2'
|
||||
implementation 'androidx.cardview:cardview:1.0.0'
|
||||
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
|
||||
implementation 'androidx.core:core-splashscreen:1.0.1'
|
||||
@@ -73,10 +73,6 @@ dependencies {
|
||||
// Android Material
|
||||
implementation 'com.google.android.material:material:1.9.0'
|
||||
|
||||
// SearchBar
|
||||
implementation 'com.paulrybitskyi.persistentsearchview:persistentsearchview:1.1.4'
|
||||
implementation 'com.arthurivanets.adapster:adapster:1.0.13'
|
||||
|
||||
// Glide
|
||||
implementation 'com.github.bumptech.glide:glide:4.15.1'
|
||||
implementation 'com.github.bumptech.glide:annotations:4.15.1'
|
||||
@@ -89,7 +85,7 @@ dependencies {
|
||||
implementation 'androidx.media3:media3-cast:1.0.2'
|
||||
|
||||
annotationProcessor 'com.github.bumptech.glide:compiler:4.15.1'
|
||||
annotationProcessor 'androidx.room:room-compiler:2.5.1'
|
||||
annotationProcessor 'androidx.room:room-compiler:2.5.2'
|
||||
|
||||
// Retrofit
|
||||
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
|
||||
|
||||
@@ -12,8 +12,8 @@ import java.util.List;
|
||||
|
||||
@Dao
|
||||
public interface RecentSearchDao {
|
||||
@Query("SELECT * FROM recent_search GROUP BY search ORDER BY search DESC LIMIT :limit")
|
||||
List<String> getRecent(int limit);
|
||||
@Query("SELECT * FROM recent_search ORDER BY search ASC")
|
||||
List<String> getRecent();
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
void insert(RecentSearch search);
|
||||
|
||||
@@ -17,8 +17,12 @@ public class ScanRepository {
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull retrofit2.Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getScanStatus() != null) {
|
||||
callback.onSuccess(response.body().getSubsonicResponse().getScanStatus().isScanning(), response.body().getSubsonicResponse().getScanStatus().getCount());
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse() != null) {
|
||||
if (response.body().getSubsonicResponse().getError() != null) {
|
||||
callback.onError(new Exception(response.body().getSubsonicResponse().getError().getMessage()));
|
||||
} else if (response.body().getSubsonicResponse().getScanStatus() != null) {
|
||||
callback.onSuccess(response.body().getSubsonicResponse().getScanStatus().isScanning(), response.body().getSubsonicResponse().getScanStatus().getCount());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,8 +40,12 @@ public class ScanRepository {
|
||||
.enqueue(new Callback<ApiResponse>() {
|
||||
@Override
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull retrofit2.Response<ApiResponse> response) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getScanStatus() != null) {
|
||||
callback.onSuccess(response.body().getSubsonicResponse().getScanStatus().isScanning(), response.body().getSubsonicResponse().getScanStatus().getCount());
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse() != null) {
|
||||
if (response.body().getSubsonicResponse().getError() != null) {
|
||||
callback.onError(new Exception(response.body().getSubsonicResponse().getError().getMessage()));
|
||||
} else if (response.body().getSubsonicResponse().getScanStatus() != null) {
|
||||
callback.onSuccess(response.body().getSubsonicResponse().getScanStatus().isScanning(), response.body().getSubsonicResponse().getScanStatus().getCount());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -103,10 +103,10 @@ public class SearchingRepository {
|
||||
thread.start();
|
||||
}
|
||||
|
||||
public List<String> getRecentSearchSuggestion(int limit) {
|
||||
public List<String> getRecentSearchSuggestion() {
|
||||
List<String> recent = new ArrayList<>();
|
||||
|
||||
RecentThreadSafe suggestionsThread = new RecentThreadSafe(recentSearchDao, limit);
|
||||
RecentThreadSafe suggestionsThread = new RecentThreadSafe(recentSearchDao);
|
||||
Thread thread = new Thread(suggestionsThread);
|
||||
thread.start();
|
||||
|
||||
@@ -152,17 +152,15 @@ public class SearchingRepository {
|
||||
|
||||
private static class RecentThreadSafe implements Runnable {
|
||||
private final RecentSearchDao recentSearchDao;
|
||||
private final int limit;
|
||||
private List<String> recent = new ArrayList<>();
|
||||
|
||||
public RecentThreadSafe(RecentSearchDao recentSearchDao, int limit) {
|
||||
public RecentThreadSafe(RecentSearchDao recentSearchDao) {
|
||||
this.recentSearchDao = recentSearchDao;
|
||||
this.limit = limit;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
recent = recentSearchDao.getRecent(limit);
|
||||
recent = recentSearchDao.getRecent();
|
||||
}
|
||||
|
||||
public List<String> getRecent() {
|
||||
|
||||
@@ -86,7 +86,7 @@ public class SongRepository {
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull Response<ApiResponse> response) {
|
||||
List<Child> songs = new ArrayList<>();
|
||||
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getRandomSongs() != null) {
|
||||
if (response.isSuccessful() && response.body() != null && response.body().getSubsonicResponse().getRandomSongs() != null && response.body().getSubsonicResponse().getRandomSongs().getSongs() != null) {
|
||||
songs.addAll(response.body().getSubsonicResponse().getRandomSongs().getSongs());
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ public class SystemRepository {
|
||||
public void onResponse(@NonNull Call<ApiResponse> call, @NonNull retrofit2.Response<ApiResponse> response) {
|
||||
if (response.body() != null) {
|
||||
if (response.body().getSubsonicResponse().getStatus().equals(ResponseStatus.FAILED)) {
|
||||
callback.onError(new Exception(response.body().getSubsonicResponse().getError().getCode().getValue() + " - " + response.body().getSubsonicResponse().getError().getMessage()));
|
||||
callback.onError(new Exception(response.body().getSubsonicResponse().getError().getCode() + " - " + response.body().getSubsonicResponse().getError().getMessage()));
|
||||
} else if (response.body().getSubsonicResponse().getStatus().equals(ResponseStatus.OK)) {
|
||||
String password = response.raw().request().url().queryParameter("p");
|
||||
String token = response.raw().request().url().queryParameter("t");
|
||||
|
||||
@@ -16,6 +16,7 @@ class RetrofitClient(subsonic: Subsonic) {
|
||||
init {
|
||||
retrofit = Retrofit.Builder()
|
||||
.baseUrl(subsonic.url)
|
||||
.addConverterFactory(GsonConverterFactory.create(GsonBuilder().setDateFormat("yyyy-MM-dd'T'HH:mm:ss").create()))
|
||||
.addConverterFactory(GsonConverterFactory.create(GsonBuilder().setLenient().create()))
|
||||
.client(getOkHttpClient())
|
||||
.build()
|
||||
|
||||
@@ -4,6 +4,6 @@ import androidx.annotation.Keep
|
||||
|
||||
@Keep
|
||||
class Error {
|
||||
var code: ErrorCode? = null
|
||||
var code: Int? = null
|
||||
var message: String? = null
|
||||
}
|
||||
@@ -247,6 +247,7 @@ public class MainActivity extends BaseActivity {
|
||||
}
|
||||
|
||||
private void goToLogin() {
|
||||
bottomSheetBehavior.setState(BottomSheetBehavior.STATE_HIDDEN);
|
||||
setBottomNavigationBarVisibility(false);
|
||||
setBottomSheetVisibility(false);
|
||||
|
||||
|
||||
@@ -2,15 +2,12 @@ package com.cappielloantonio.tempo.ui.activity.base;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.ComponentName;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.PowerManager;
|
||||
import android.provider.Settings;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
@@ -19,9 +16,10 @@ import androidx.media3.exoplayer.offline.DownloadService;
|
||||
import androidx.media3.session.MediaBrowser;
|
||||
import androidx.media3.session.SessionToken;
|
||||
|
||||
import com.cappielloantonio.tempo.R;
|
||||
import com.cappielloantonio.tempo.service.DownloaderService;
|
||||
import com.cappielloantonio.tempo.service.MediaService;
|
||||
import com.cappielloantonio.tempo.ui.dialog.BatteryOptimizationDialog;
|
||||
import com.cappielloantonio.tempo.util.Preferences;
|
||||
import com.cappielloantonio.tempo.util.UIUtil;
|
||||
import com.google.android.gms.cast.framework.CastContext;
|
||||
import com.google.android.material.elevation.SurfaceColors;
|
||||
@@ -38,6 +36,8 @@ public class BaseActivity extends AppCompatActivity {
|
||||
super.onCreate(savedInstanceState);
|
||||
initializeCastContext();
|
||||
initializeDownloader();
|
||||
checkBatteryOptimization();
|
||||
checkPermission();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -47,13 +47,6 @@ public class BaseActivity extends AppCompatActivity {
|
||||
initializeBrowser();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
checkBatteryOptimization();
|
||||
checkPermission();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStop() {
|
||||
releaseBrowser();
|
||||
@@ -61,7 +54,7 @@ public class BaseActivity extends AppCompatActivity {
|
||||
}
|
||||
|
||||
private void checkBatteryOptimization() {
|
||||
if (detectBatteryOptimization()) {
|
||||
if (detectBatteryOptimization() && Preferences.askForOptimization()) {
|
||||
showBatteryOptimizationDialog();
|
||||
}
|
||||
}
|
||||
@@ -81,19 +74,8 @@ public class BaseActivity extends AppCompatActivity {
|
||||
}
|
||||
|
||||
private void showBatteryOptimizationDialog() {
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(this);
|
||||
builder.setMessage(R.string.activity_battery_optimizations_summary)
|
||||
.setTitle(R.string.activity_battery_optimizations_title)
|
||||
.setNegativeButton(R.string.activity_negative_button, null)
|
||||
.setPositiveButton(R.string.activity_neutral_button, (dialog, id) -> openPowerSettings())
|
||||
.show();
|
||||
}
|
||||
|
||||
private void openPowerSettings() {
|
||||
Intent intent = new Intent();
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
intent.setAction(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS);
|
||||
startActivity(intent);
|
||||
BatteryOptimizationDialog dialog = new BatteryOptimizationDialog();
|
||||
dialog.show(getSupportFragmentManager(), null);
|
||||
}
|
||||
|
||||
private void initializeBrowser() {
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
package com.cappielloantonio.tempo.ui.dialog;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.Intent;
|
||||
import android.os.Bundle;
|
||||
import android.provider.Settings;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.OptIn;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
|
||||
import com.cappielloantonio.tempo.R;
|
||||
import com.cappielloantonio.tempo.databinding.DialogBatteryOptimizationBinding;
|
||||
import com.cappielloantonio.tempo.util.Preferences;
|
||||
|
||||
@OptIn(markerClass = UnstableApi.class)
|
||||
public class BatteryOptimizationDialog extends DialogFragment {
|
||||
private static final String TAG = "BatteryOptimizationDialog";
|
||||
|
||||
private DialogBatteryOptimizationBinding bind;
|
||||
|
||||
@NonNull
|
||||
@Override
|
||||
public Dialog onCreateDialog(Bundle savedInstanceState) {
|
||||
bind = DialogBatteryOptimizationBinding.inflate(getLayoutInflater());
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||
|
||||
builder.setView(bind.getRoot())
|
||||
.setTitle(R.string.activity_battery_optimizations_title)
|
||||
.setNeutralButton(R.string.battery_optimization_neutral_button, (dialog, id) -> Preferences.dontAskForOptimization())
|
||||
.setNegativeButton(R.string.battery_optimization_negative_button, null)
|
||||
.setPositiveButton(R.string.battery_optimization_positive_button, (dialog, id) -> openPowerSettings());
|
||||
|
||||
AlertDialog popup = builder.create();
|
||||
|
||||
popup.setCancelable(false);
|
||||
popup.setCanceledOnTouchOutside(false);
|
||||
|
||||
return popup;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDestroyView() {
|
||||
super.onDestroyView();
|
||||
bind = null;
|
||||
}
|
||||
|
||||
private void openPowerSettings() {
|
||||
Intent intent = new Intent();
|
||||
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
||||
intent.setAction(Settings.ACTION_IGNORE_BATTERY_OPTIMIZATION_SETTINGS);
|
||||
startActivity(intent);
|
||||
}
|
||||
}
|
||||
@@ -116,6 +116,11 @@ public class ServerSignupDialog extends DialogFragment {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!server.matches("^https?://(.*)")) {
|
||||
bind.serverTextView.setError(getString(R.string.error_server_prefix));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -2,9 +2,14 @@ package com.cappielloantonio.tempo.ui.fragment;
|
||||
|
||||
import android.content.ComponentName;
|
||||
import android.os.Bundle;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
@@ -28,17 +33,15 @@ import com.cappielloantonio.tempo.ui.adapter.AlbumAdapter;
|
||||
import com.cappielloantonio.tempo.ui.adapter.ArtistAdapter;
|
||||
import com.cappielloantonio.tempo.ui.adapter.SongHorizontalAdapter;
|
||||
import com.cappielloantonio.tempo.util.Constants;
|
||||
import com.cappielloantonio.tempo.util.MusicUtil;
|
||||
import com.cappielloantonio.tempo.viewmodel.SearchViewModel;
|
||||
import com.google.common.util.concurrent.ListenableFuture;
|
||||
import com.paulrybitskyi.persistentsearchview.adapters.model.SuggestionItem;
|
||||
import com.paulrybitskyi.persistentsearchview.listeners.OnSuggestionChangeListener;
|
||||
import com.paulrybitskyi.persistentsearchview.utils.SuggestionCreationUtil;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
@UnstableApi
|
||||
public class SearchFragment extends Fragment implements ClickCallback {
|
||||
private static final String TAG = "SearchFragment";
|
||||
|
||||
private FragmentSearchBinding bind;
|
||||
private MainActivity activity;
|
||||
private SearchViewModel searchViewModel;
|
||||
@@ -60,6 +63,7 @@ public class SearchFragment extends Fragment implements ClickCallback {
|
||||
|
||||
initSearchResultView();
|
||||
initSearchView();
|
||||
inputFocus();
|
||||
|
||||
return view;
|
||||
}
|
||||
@@ -70,12 +74,6 @@ public class SearchFragment extends Fragment implements ClickCallback {
|
||||
initializeMediaBrowser();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
inputFocus();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStop() {
|
||||
releaseMediaBrowser();
|
||||
@@ -118,64 +116,101 @@ public class SearchFragment extends Fragment implements ClickCallback {
|
||||
}
|
||||
|
||||
private void initSearchView() {
|
||||
if (isQueryValid(searchViewModel.getQuery())) {
|
||||
search(searchViewModel.getQuery());
|
||||
}
|
||||
setRecentSuggestions();
|
||||
|
||||
bind.persistentSearchView.setInputQuery(searchViewModel.getQuery());
|
||||
setSuggestions();
|
||||
bind.searchView
|
||||
.getEditText()
|
||||
.setOnEditorActionListener((textView, actionId, keyEvent) -> {
|
||||
|
||||
bind.persistentSearchView.setOnSearchQueryChangeListener((searchView, oldQuery, newQuery) -> {
|
||||
if (!newQuery.trim().equals("") && newQuery.trim().length() > 1) {
|
||||
searchViewModel.getSearchSuggestion(newQuery).observe(getViewLifecycleOwner(), suggestions -> searchView.setSuggestions(SuggestionCreationUtil.asRegularSearchSuggestions(MusicUtil.getReadableStrings(suggestions)), false));
|
||||
} else {
|
||||
setSuggestions();
|
||||
}
|
||||
});
|
||||
String query = bind.searchView.getText().toString();
|
||||
|
||||
bind.persistentSearchView.setOnSuggestionChangeListener(new OnSuggestionChangeListener() {
|
||||
@Override
|
||||
public void onSuggestionPicked(SuggestionItem suggestion) {
|
||||
search(suggestion.getItemModel().getText());
|
||||
}
|
||||
if (actionId == EditorInfo.IME_ACTION_DONE) {
|
||||
if (isQueryValid(query)) {
|
||||
search(bind.searchView.getText().toString());
|
||||
return true;
|
||||
} else {
|
||||
Toast.makeText(requireContext(), getString(R.string.search_info_minimum_characters), Toast.LENGTH_SHORT).show();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuggestionRemoved(SuggestionItem suggestion) {
|
||||
}
|
||||
});
|
||||
return false;
|
||||
});
|
||||
|
||||
bind.persistentSearchView.setOnSearchConfirmedListener((searchView, query) -> {
|
||||
if (isQueryValid(query)) {
|
||||
searchView.collapse();
|
||||
search(query);
|
||||
} else {
|
||||
Toast.makeText(requireContext(), getString(R.string.search_info_minimum_characters), Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
});
|
||||
bind.searchView
|
||||
.getEditText()
|
||||
.addTextChangedListener(new TextWatcher() {
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence charSequence, int start, int count, int after) {
|
||||
|
||||
bind.persistentSearchView.setOnSuggestionChangeListener(new OnSuggestionChangeListener() {
|
||||
@Override
|
||||
public void onSuggestionPicked(SuggestionItem suggestion) {
|
||||
search(suggestion.getItemModel().getText());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSuggestionRemoved(SuggestionItem suggestion) {
|
||||
searchViewModel.deleteRecentSearch(suggestion.getItemModel().getText());
|
||||
}
|
||||
});
|
||||
@Override
|
||||
public void onTextChanged(CharSequence charSequence, int start, int before, int count) {
|
||||
if (count > 1) {
|
||||
setSearchSuggestions(charSequence.toString());
|
||||
} else {
|
||||
setRecentSuggestions();
|
||||
}
|
||||
}
|
||||
|
||||
bind.persistentSearchView.setOnClearInputBtnClickListener(v -> searchViewModel.setQuery(""));
|
||||
@Override
|
||||
public void afterTextChanged(Editable editable) {
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setSuggestions() {
|
||||
bind.persistentSearchView.setSuggestions(SuggestionCreationUtil.asRecentSearchSuggestions(searchViewModel.getRecentSearchSuggestion()), false);
|
||||
public void setRecentSuggestions() {
|
||||
bind.searchViewSuggestionContainer.removeAllViews();
|
||||
|
||||
for (String suggestion : searchViewModel.getRecentSearchSuggestion()) {
|
||||
View view = LayoutInflater.from(bind.searchViewSuggestionContainer.getContext()).inflate(R.layout.item_search_suggestion, bind.searchViewSuggestionContainer, false);
|
||||
|
||||
ImageView leadingImageView = view.findViewById(R.id.search_suggestion_icon);
|
||||
TextView titleView = view.findViewById(R.id.search_suggestion_title);
|
||||
ImageView tailingImageView = view.findViewById(R.id.search_suggestion_delete_icon);
|
||||
|
||||
leadingImageView.setImageDrawable(getResources().getDrawable(R.drawable.ic_history, null));
|
||||
titleView.setText(suggestion);
|
||||
|
||||
view.setOnClickListener(v -> search(suggestion));
|
||||
|
||||
tailingImageView.setOnClickListener(v -> {
|
||||
searchViewModel.deleteRecentSearch(suggestion);
|
||||
setRecentSuggestions();
|
||||
});
|
||||
|
||||
bind.searchViewSuggestionContainer.addView(view);
|
||||
}
|
||||
}
|
||||
|
||||
public void setSearchSuggestions(String query) {
|
||||
searchViewModel.getSearchSuggestion(query).observe(getViewLifecycleOwner(), suggestions -> {
|
||||
bind.searchViewSuggestionContainer.removeAllViews();
|
||||
|
||||
for (String suggestion : suggestions) {
|
||||
View view = LayoutInflater.from(bind.searchViewSuggestionContainer.getContext()).inflate(R.layout.item_search_suggestion, bind.searchViewSuggestionContainer, false);
|
||||
|
||||
ImageView leadingImageView = view.findViewById(R.id.search_suggestion_icon);
|
||||
TextView titleView = view.findViewById(R.id.search_suggestion_title);
|
||||
ImageView tailingImageView = view.findViewById(R.id.search_suggestion_delete_icon);
|
||||
|
||||
leadingImageView.setImageDrawable(getResources().getDrawable(R.drawable.ic_search, null));
|
||||
titleView.setText(suggestion);
|
||||
tailingImageView.setVisibility(View.GONE);
|
||||
|
||||
view.setOnClickListener(v -> search(suggestion));
|
||||
|
||||
bind.searchViewSuggestionContainer.addView(view);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void search(String query) {
|
||||
searchViewModel.setQuery(query);
|
||||
|
||||
bind.persistentSearchView.setInputQuery(query);
|
||||
bind.searchBar.setText(query);
|
||||
bind.searchView.hide();
|
||||
performSearch(query);
|
||||
}
|
||||
|
||||
@@ -216,7 +251,7 @@ public class SearchFragment extends Fragment implements ClickCallback {
|
||||
}
|
||||
|
||||
private void inputFocus() {
|
||||
bind.persistentSearchView.expand();
|
||||
bind.searchView.show();
|
||||
}
|
||||
|
||||
private void initializeMediaBrowser() {
|
||||
|
||||
@@ -10,6 +10,7 @@ object Preferences {
|
||||
private const val TOKEN = "token"
|
||||
private const val SALT = "salt"
|
||||
private const val LOW_SECURITY = "low_security"
|
||||
private const val BATTERY_OPTIMIZATION = "battery_optimization"
|
||||
private const val SERVER_ID = "server_id"
|
||||
private const val PLAYBACK_SPEED = "playback_speed"
|
||||
private const val SKIP_SILENCE = "skip_silence"
|
||||
@@ -100,6 +101,16 @@ object Preferences {
|
||||
App.getInstance().preferences.edit().putString(SERVER_ID, serverId).apply()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun askForOptimization(): Boolean? {
|
||||
return App.getInstance().preferences.getBoolean(BATTERY_OPTIMIZATION, true)
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun dontAskForOptimization() {
|
||||
App.getInstance().preferences.edit().putBoolean(BATTERY_OPTIMIZATION, false).apply()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun getPlaybackSpeed(): Float {
|
||||
return App.getInstance().preferences.getFloat(PLAYBACK_SPEED, 1f)
|
||||
|
||||
@@ -56,7 +56,7 @@ public class SearchViewModel extends AndroidViewModel {
|
||||
|
||||
public List<String> getRecentSearchSuggestion() {
|
||||
ArrayList<String> suggestions = new ArrayList<>();
|
||||
suggestions.addAll(searchingRepository.getRecentSearchSuggestion(5));
|
||||
suggestions.addAll(searchingRepository.getRecentSearchSuggestion());
|
||||
|
||||
return suggestions;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z" />
|
||||
</vector>
|
||||
android:fillColor="@color/titleTextColor"
|
||||
android:pathData="M256,760L200,704L424,480L200,256L256,200L480,424L704,200L760,256L536,480L760,704L704,760L480,536L256,760Z"/>
|
||||
</vector>
|
||||
9
app/src/main/res/drawable/ic_history.xml
Normal file
9
app/src/main/res/drawable/ic_history.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960">
|
||||
<path
|
||||
android:fillColor="@color/titleTextColor"
|
||||
android:pathData="M480,840Q342,840 239.5,748.5Q137,657 122,520L204,520Q218,624 296.5,692Q375,760 480,760Q597,760 678.5,678.5Q760,597 760,480Q760,363 678.5,281.5Q597,200 480,200Q411,200 351,232Q291,264 250,320L360,320L360,400L120,400L120,160L200,160L200,254Q251,190 324.5,155Q398,120 480,120Q555,120 620.5,148.5Q686,177 734.5,225.5Q783,274 811.5,339.5Q840,405 840,480Q840,555 811.5,620.5Q783,686 734.5,734.5Q686,783 620.5,811.5Q555,840 480,840ZM592,648L440,496L440,280L520,280L520,464L648,592L592,648Z"/>
|
||||
</vector>
|
||||
25
app/src/main/res/layout/dialog_battery_optimization.xml
Normal file
25
app/src/main/res/layout/dialog_battery_optimization.xml
Normal file
@@ -0,0 +1,25 @@
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/battery_optimization_primary"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="24dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:text="@string/activity_battery_optimizations_summary" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/battery_optimization_secondary"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="24dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="24dp"
|
||||
android:autoLink="web"
|
||||
android:text="@string/activity_battery_optimizations_conclusion" />
|
||||
</LinearLayout>
|
||||
@@ -1,54 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<com.paulrybitskyi.persistentsearchview.PersistentSearchView
|
||||
android:id="@+id/persistentSearchView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:paddingStart="12dp"
|
||||
android:paddingEnd="12dp"
|
||||
app:areSuggestionsDisabled="false"
|
||||
app:cardBackgroundColor="?attr/colorSurface"
|
||||
app:cardCornerRadius="0dp"
|
||||
app:cardElevation="0dp"
|
||||
app:clearInputButtonDrawable="@drawable/ic_close"
|
||||
app:dividerColor="@color/dividerColor"
|
||||
app:isClearInputButtonEnabled="true"
|
||||
app:isDismissableOnTouchOutside="true"
|
||||
app:isProgressBarEnabled="true"
|
||||
app:isVoiceInputButtonEnabled="false"
|
||||
app:leftButtonDrawable="@drawable/ic_search"
|
||||
app:progressBarColor="?attr/colorOnSurface"
|
||||
app:queryInputBarIconColor="@color/searchPlaceholderColor"
|
||||
app:queryInputCursorColor="?attr/colorOnSurface"
|
||||
app:queryInputHint="@string/search_hint"
|
||||
app:queryInputHintColor="@color/searchPlaceholderColor"
|
||||
app:queryInputTextColor="@color/searchPlaceholderColor"
|
||||
app:shouldDimBehind="false"
|
||||
app:suggestionRecentSearchIconColor="@color/searchColor"
|
||||
app:suggestionSearchSuggestionIconColor="@color/searchColor"
|
||||
app:suggestionSelectedTextColor="?attr/colorPrimary"
|
||||
app:suggestionTextColor="@color/searchColor"
|
||||
app:suggestionIconColor="@color/searchColor" />
|
||||
|
||||
<View
|
||||
android:id="@+id/search_bar_divider"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0.75dp"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginEnd="12dp"
|
||||
android:layout_below="@+id/persistentSearchView"
|
||||
android:background="?attr/colorSurfaceVariant" />
|
||||
|
||||
<androidx.core.widget.NestedScrollView
|
||||
android:id="@+id/search_result_nested_scroll_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/search_bar_divider">
|
||||
android:layout_height="match_parent"
|
||||
app:layout_behavior="@string/searchbar_scrolling_view_behavior">
|
||||
|
||||
<!-- Search result -->
|
||||
<LinearLayout
|
||||
@@ -56,6 +16,7 @@
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingTop="64dp"
|
||||
android:paddingBottom="@dimen/global_padding_bottom">
|
||||
|
||||
<!-- Artist -->
|
||||
@@ -150,4 +111,39 @@
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
</androidx.core.widget.NestedScrollView>
|
||||
</RelativeLayout>
|
||||
|
||||
<com.google.android.material.appbar.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<com.google.android.material.search.SearchBar
|
||||
android:id="@+id/search_bar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="@string/search_hint">
|
||||
|
||||
</com.google.android.material.search.SearchBar>
|
||||
</com.google.android.material.appbar.AppBarLayout>
|
||||
|
||||
<com.google.android.material.search.SearchView
|
||||
android:id="@+id/search_view"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:hint="@string/search_hint"
|
||||
app:layout_anchor="@id/search_bar">
|
||||
|
||||
<!-- Content goes here (ScrollView, RecyclerView, etc.). -->
|
||||
<ScrollView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:scrollbars="none"
|
||||
android:paddingBottom="@dimen/global_padding_bottom">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/search_view_suggestion_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"/>
|
||||
</ScrollView>
|
||||
</com.google.android.material.search.SearchView>
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
48
app/src/main/res/layout/item_search_suggestion.xml
Normal file
48
app/src/main/res/layout/item_search_suggestion.xml
Normal file
@@ -0,0 +1,48 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:paddingBottom="16dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/search_suggestion_icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:src="@drawable/ic_history"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:ignore="ContentDescription" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/search_suggestion_title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginHorizontal="12dp"
|
||||
android:text="@string/label_placeholder"
|
||||
android:textAppearance="?attr/textAppearanceBody2"
|
||||
app:layout_constraintBottom_toBottomOf="@id/search_suggestion_icon"
|
||||
app:layout_constraintEnd_toStartOf="@id/search_suggestion_delete_icon"
|
||||
app:layout_constraintStart_toEndOf="@id/search_suggestion_icon"
|
||||
app:layout_constraintTop_toTopOf="@id/search_suggestion_icon" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/search_suggestion_delete_icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?selectableItemBackgroundBorderless"
|
||||
android:src="@drawable/ic_close"
|
||||
app:layout_constraintBottom_toBottomOf="@id/search_suggestion_icon"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@id/search_suggestion_title"
|
||||
app:layout_constraintTop_toTopOf="@id/search_suggestion_icon"
|
||||
tools:ignore="ContentDescription" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@@ -1,9 +1,11 @@
|
||||
<resources>
|
||||
<string name="activity_battery_optimizations_summary">Please disable battery optimizations for media playback while the screen is off.</string>
|
||||
<string name="activity_battery_optimizations_conclusion">If in trouble visit https://dontkillmyapp.com. It provides detailed instructions on how to disable any power-saving features that may affect app\'s performance.</string>
|
||||
<string name="activity_battery_optimizations_title">Battery Optimizations</string>
|
||||
<string name="activity_info_offline_mode">Offline mode</string>
|
||||
<string name="activity_negative_button">Ignore</string>
|
||||
<string name="activity_neutral_button">Disable</string>
|
||||
<string name="battery_optimization_negative_button">Ignore</string>
|
||||
<string name="battery_optimization_positive_button">Disable</string>
|
||||
<string name="battery_optimization_neutral_button">Don\'t ask again</string>
|
||||
<string name="album_bottom_sheet_add_to_queue">Add to queue</string>
|
||||
<string name="album_bottom_sheet_download_all">Download all</string>
|
||||
<string name="album_bottom_sheet_go_to_artist">Go to artist</string>
|
||||
@@ -52,6 +54,7 @@
|
||||
<string name="download_title_section">Downloads</string>
|
||||
<string name="empty_string" />
|
||||
<string name="error_required">Required</string>
|
||||
<string name="error_server_prefix">http or https prefix required</string>
|
||||
<string name="exo_download_notification_channel_name">Downloads</string>
|
||||
<string name="filter_info_selection">Select two or more filters</string>
|
||||
<string name="filter_title">Filter</string>
|
||||
|
||||
Reference in New Issue
Block a user