Merge branch 'develop' into refactor-events
This commit is contained in:
commit
fec2d78d30
|
@ -1,504 +0,0 @@
|
|||
package org.moire.ultrasonic.fragment;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.SharedPreferences;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.provider.SearchRecentSuggestions;
|
||||
import android.view.View;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.StringRes;
|
||||
import androidx.fragment.app.DialogFragment;
|
||||
import androidx.navigation.Navigation;
|
||||
import androidx.preference.CheckBoxPreference;
|
||||
import androidx.preference.EditTextPreference;
|
||||
import androidx.preference.ListPreference;
|
||||
import androidx.preference.Preference;
|
||||
import androidx.preference.PreferenceCategory;
|
||||
import androidx.preference.PreferenceFragmentCompat;
|
||||
import androidx.preference.PreferenceManager;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.koin.java.KoinJavaComponent;
|
||||
import org.moire.ultrasonic.R;
|
||||
import org.moire.ultrasonic.featureflags.Feature;
|
||||
import org.moire.ultrasonic.featureflags.FeatureStorage;
|
||||
import org.moire.ultrasonic.filepicker.FilePickerDialog;
|
||||
import org.moire.ultrasonic.filepicker.OnFileSelectedListener;
|
||||
import org.moire.ultrasonic.log.FileLoggerTree;
|
||||
import org.moire.ultrasonic.provider.SearchSuggestionProvider;
|
||||
import org.moire.ultrasonic.service.Consumer;
|
||||
import org.moire.ultrasonic.service.MediaPlayerController;
|
||||
import org.moire.ultrasonic.service.RxBus;
|
||||
import org.moire.ultrasonic.util.Constants;
|
||||
import org.moire.ultrasonic.util.FileUtil;
|
||||
import org.moire.ultrasonic.util.MediaSessionHandler;
|
||||
import org.moire.ultrasonic.util.PermissionUtil;
|
||||
import org.moire.ultrasonic.util.Settings;
|
||||
import org.moire.ultrasonic.util.TimeSpanPreference;
|
||||
import org.moire.ultrasonic.util.TimeSpanPreferenceDialogFragmentCompat;
|
||||
import org.moire.ultrasonic.util.Util;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import kotlin.Lazy;
|
||||
import kotlin.Unit;
|
||||
import timber.log.Timber;
|
||||
|
||||
import static org.koin.java.KoinJavaComponent.inject;
|
||||
import static org.moire.ultrasonic.fragment.ServerSelectorFragment.SERVER_SELECTOR_MANAGE_MODE;
|
||||
|
||||
/**
|
||||
* Shows main app settings.
|
||||
*/
|
||||
public class SettingsFragment extends PreferenceFragmentCompat
|
||||
implements SharedPreferences.OnSharedPreferenceChangeListener {
|
||||
|
||||
private ListPreference theme;
|
||||
private ListPreference maxBitrateWifi;
|
||||
private ListPreference maxBitrateMobile;
|
||||
private ListPreference cacheSize;
|
||||
private Preference cacheLocation;
|
||||
private ListPreference preloadCount;
|
||||
private ListPreference bufferLength;
|
||||
private ListPreference incrementTime;
|
||||
private ListPreference networkTimeout;
|
||||
private ListPreference maxAlbums;
|
||||
private ListPreference maxSongs;
|
||||
private ListPreference maxArtists;
|
||||
private ListPreference defaultAlbums;
|
||||
private ListPreference defaultSongs;
|
||||
private ListPreference defaultArtists;
|
||||
private ListPreference chatRefreshInterval;
|
||||
private ListPreference directoryCacheTime;
|
||||
private CheckBoxPreference mediaButtonsEnabled;
|
||||
private CheckBoxPreference lockScreenEnabled;
|
||||
private CheckBoxPreference sendBluetoothNotifications;
|
||||
private CheckBoxPreference sendBluetoothAlbumArt;
|
||||
private CheckBoxPreference showArtistPicture;
|
||||
private ListPreference viewRefresh;
|
||||
private EditTextPreference sharingDefaultDescription;
|
||||
private EditTextPreference sharingDefaultGreeting;
|
||||
private TimeSpanPreference sharingDefaultExpiration;
|
||||
private Preference resumeOnBluetoothDevice;
|
||||
private Preference pauseOnBluetoothDevice;
|
||||
private CheckBoxPreference debugLogToFile;
|
||||
|
||||
private SharedPreferences settings;
|
||||
|
||||
private final Lazy<MediaPlayerController> mediaPlayerControllerLazy = inject(MediaPlayerController.class);
|
||||
private final Lazy<PermissionUtil> permissionUtil = inject(PermissionUtil.class);
|
||||
private final Lazy<MediaSessionHandler> mediaSessionHandler = inject(MediaSessionHandler.class);
|
||||
|
||||
@Override
|
||||
public void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
|
||||
settings = PreferenceManager.getDefaultSharedPreferences(getActivity());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
|
||||
setPreferencesFromResource(R.xml.settings, rootKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewCreated(@NotNull View view, @Nullable Bundle savedInstanceState) {
|
||||
super.onViewCreated(view, savedInstanceState);
|
||||
FragmentTitle.Companion.setTitle(this, R.string.menu_settings);
|
||||
|
||||
theme = findPreference(Constants.PREFERENCES_KEY_THEME);
|
||||
maxBitrateWifi = findPreference(Constants.PREFERENCES_KEY_MAX_BITRATE_WIFI);
|
||||
maxBitrateMobile = findPreference(Constants.PREFERENCES_KEY_MAX_BITRATE_MOBILE);
|
||||
cacheSize = findPreference(Constants.PREFERENCES_KEY_CACHE_SIZE);
|
||||
cacheLocation = findPreference(Constants.PREFERENCES_KEY_CACHE_LOCATION);
|
||||
preloadCount = findPreference(Constants.PREFERENCES_KEY_PRELOAD_COUNT);
|
||||
bufferLength = findPreference(Constants.PREFERENCES_KEY_BUFFER_LENGTH);
|
||||
incrementTime = findPreference(Constants.PREFERENCES_KEY_INCREMENT_TIME);
|
||||
networkTimeout = findPreference(Constants.PREFERENCES_KEY_NETWORK_TIMEOUT);
|
||||
maxAlbums = findPreference(Constants.PREFERENCES_KEY_MAX_ALBUMS);
|
||||
maxSongs = findPreference(Constants.PREFERENCES_KEY_MAX_SONGS);
|
||||
maxArtists = findPreference(Constants.PREFERENCES_KEY_MAX_ARTISTS);
|
||||
defaultArtists = findPreference(Constants.PREFERENCES_KEY_DEFAULT_ARTISTS);
|
||||
defaultSongs = findPreference(Constants.PREFERENCES_KEY_DEFAULT_SONGS);
|
||||
defaultAlbums = findPreference(Constants.PREFERENCES_KEY_DEFAULT_ALBUMS);
|
||||
chatRefreshInterval = findPreference(Constants.PREFERENCES_KEY_CHAT_REFRESH_INTERVAL);
|
||||
directoryCacheTime = findPreference(Constants.PREFERENCES_KEY_DIRECTORY_CACHE_TIME);
|
||||
mediaButtonsEnabled = findPreference(Constants.PREFERENCES_KEY_MEDIA_BUTTONS);
|
||||
lockScreenEnabled = findPreference(Constants.PREFERENCES_KEY_SHOW_LOCK_SCREEN_CONTROLS);
|
||||
sendBluetoothAlbumArt = findPreference(Constants.PREFERENCES_KEY_SEND_BLUETOOTH_ALBUM_ART);
|
||||
sendBluetoothNotifications = findPreference(Constants.PREFERENCES_KEY_SEND_BLUETOOTH_NOTIFICATIONS);
|
||||
viewRefresh = findPreference(Constants.PREFERENCES_KEY_VIEW_REFRESH);
|
||||
sharingDefaultDescription = findPreference(Constants.PREFERENCES_KEY_DEFAULT_SHARE_DESCRIPTION);
|
||||
sharingDefaultGreeting = findPreference(Constants.PREFERENCES_KEY_DEFAULT_SHARE_GREETING);
|
||||
sharingDefaultExpiration = findPreference(Constants.PREFERENCES_KEY_DEFAULT_SHARE_EXPIRATION);
|
||||
resumeOnBluetoothDevice = findPreference(Constants.PREFERENCES_KEY_RESUME_ON_BLUETOOTH_DEVICE);
|
||||
pauseOnBluetoothDevice = findPreference(Constants.PREFERENCES_KEY_PAUSE_ON_BLUETOOTH_DEVICE);
|
||||
debugLogToFile = findPreference(Constants.PREFERENCES_KEY_DEBUG_LOG_TO_FILE);
|
||||
showArtistPicture = findPreference(Constants.PREFERENCES_KEY_SHOW_ARTIST_PICTURE);
|
||||
|
||||
sharingDefaultGreeting.setText(Settings.getShareGreeting());
|
||||
setupClearSearchPreference();
|
||||
setupFeatureFlagsPreferences();
|
||||
setupCacheLocationPreference();
|
||||
setupBluetoothDevicePreferences();
|
||||
|
||||
// After API26 foreground services must be used for music playback, and they must have a notification
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
PreferenceCategory notificationsCategory = findPreference(Constants.PREFERENCES_KEY_CATEGORY_NOTIFICATIONS);
|
||||
Preference preferenceToRemove = findPreference(Constants.PREFERENCES_KEY_SHOW_NOTIFICATION);
|
||||
if (preferenceToRemove != null) notificationsCategory.removePreference(preferenceToRemove);
|
||||
preferenceToRemove = findPreference(Constants.PREFERENCES_KEY_ALWAYS_SHOW_NOTIFICATION);
|
||||
if (preferenceToRemove != null) notificationsCategory.removePreference(preferenceToRemove);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
|
||||
super.onActivityCreated(savedInstanceState);
|
||||
|
||||
update();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onResume() {
|
||||
super.onResume();
|
||||
SharedPreferences preferences = Settings.getPreferences();
|
||||
preferences.registerOnSharedPreferenceChangeListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPause() {
|
||||
super.onPause();
|
||||
SharedPreferences prefs = Settings.getPreferences();
|
||||
prefs.unregisterOnSharedPreferenceChangeListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
||||
Timber.d("Preference changed: %s", key);
|
||||
update();
|
||||
|
||||
if (Constants.PREFERENCES_KEY_HIDE_MEDIA.equals(key)) {
|
||||
setHideMedia(sharedPreferences.getBoolean(key, false));
|
||||
} else if (Constants.PREFERENCES_KEY_MEDIA_BUTTONS.equals(key)) {
|
||||
setMediaButtonsEnabled(sharedPreferences.getBoolean(key, true));
|
||||
} else if (Constants.PREFERENCES_KEY_SEND_BLUETOOTH_NOTIFICATIONS.equals(key)) {
|
||||
setBluetoothPreferences(sharedPreferences.getBoolean(key, true));
|
||||
} else if (Constants.PREFERENCES_KEY_DEBUG_LOG_TO_FILE.equals(key)) {
|
||||
setDebugLogToFile(sharedPreferences.getBoolean(key, false));
|
||||
} else if (Constants.PREFERENCES_KEY_ID3_TAGS.equals(key)) {
|
||||
showArtistPicture.setEnabled(sharedPreferences.getBoolean(key, false));
|
||||
} else if (Constants.PREFERENCES_KEY_THEME.equals(key)) {
|
||||
RxBus.INSTANCE.getThemeChangedEventPublisher().onNext(Unit.INSTANCE);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisplayPreferenceDialog(Preference preference)
|
||||
{
|
||||
DialogFragment dialogFragment = null;
|
||||
if (preference instanceof TimeSpanPreference)
|
||||
{
|
||||
dialogFragment = new TimeSpanPreferenceDialogFragmentCompat();
|
||||
Bundle bundle = new Bundle(1);
|
||||
bundle.putString("key", preference.getKey());
|
||||
dialogFragment.setArguments(bundle);
|
||||
}
|
||||
|
||||
if (dialogFragment != null)
|
||||
{
|
||||
dialogFragment.setTargetFragment(this, 0);
|
||||
dialogFragment.show(this.getParentFragmentManager(), "android.support.v7.preference.PreferenceFragment.DIALOG");
|
||||
}
|
||||
else
|
||||
{
|
||||
super.onDisplayPreferenceDialog(preference);
|
||||
}
|
||||
}
|
||||
|
||||
private void setupCacheLocationPreference() {
|
||||
cacheLocation.setSummary(Settings.getCacheLocation());
|
||||
|
||||
cacheLocation.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
// If the user tries to change the cache location, we must first check to see if we have write access.
|
||||
PermissionUtil.requestInitialPermission(getActivity(), new PermissionUtil.PermissionRequestFinishedCallback() {
|
||||
@Override
|
||||
public void onPermissionRequestFinished(boolean hasPermission) {
|
||||
if (hasPermission) {
|
||||
FilePickerDialog filePickerDialog = FilePickerDialog.Companion.createFilePickerDialog(getContext());
|
||||
filePickerDialog.setDefaultDirectory(FileUtil.getDefaultMusicDirectory().getPath());
|
||||
filePickerDialog.setInitialDirectory(cacheLocation.getSummary().toString());
|
||||
filePickerDialog.setOnFileSelectedListener(new OnFileSelectedListener() {
|
||||
@Override
|
||||
public void onFileSelected(File file, String path) {
|
||||
SharedPreferences.Editor editor = cacheLocation.getSharedPreferences().edit();
|
||||
editor.putString(Constants.PREFERENCES_KEY_CACHE_LOCATION, path);
|
||||
editor.apply();
|
||||
|
||||
setCacheLocation(path);
|
||||
}
|
||||
});
|
||||
filePickerDialog.show();
|
||||
}
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void setupBluetoothDevicePreferences() {
|
||||
final int resumeSetting = Settings.getResumeOnBluetoothDevice();
|
||||
final int pauseSetting = Settings.getPauseOnBluetoothDevice();
|
||||
|
||||
resumeOnBluetoothDevice.setSummary(bluetoothDevicePreferenceToString(resumeSetting));
|
||||
pauseOnBluetoothDevice.setSummary(bluetoothDevicePreferenceToString(pauseSetting));
|
||||
|
||||
resumeOnBluetoothDevice.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
showBluetoothDevicePreferenceDialog(
|
||||
R.string.settings_playback_resume_on_bluetooth_device,
|
||||
Settings.getResumeOnBluetoothDevice(),
|
||||
new Consumer<Integer>() {
|
||||
@Override
|
||||
public void accept(Integer choice) {
|
||||
SharedPreferences.Editor editor = resumeOnBluetoothDevice.getSharedPreferences().edit();
|
||||
editor.putInt(Constants.PREFERENCES_KEY_RESUME_ON_BLUETOOTH_DEVICE, choice);
|
||||
editor.apply();
|
||||
resumeOnBluetoothDevice.setSummary(bluetoothDevicePreferenceToString(choice));
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
|
||||
pauseOnBluetoothDevice.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
showBluetoothDevicePreferenceDialog(
|
||||
R.string.settings_playback_pause_on_bluetooth_device,
|
||||
Settings.getPauseOnBluetoothDevice(),
|
||||
new Consumer<Integer>() {
|
||||
@Override
|
||||
public void accept(Integer choice) {
|
||||
SharedPreferences.Editor editor = pauseOnBluetoothDevice.getSharedPreferences().edit();
|
||||
editor.putInt(Constants.PREFERENCES_KEY_PAUSE_ON_BLUETOOTH_DEVICE, choice);
|
||||
editor.apply();
|
||||
pauseOnBluetoothDevice.setSummary(bluetoothDevicePreferenceToString(choice));
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void showBluetoothDevicePreferenceDialog(@StringRes int title, int defaultChoice, final Consumer<Integer> onChosen) {
|
||||
final int[] choice = {defaultChoice};
|
||||
new AlertDialog.Builder(getActivity()).setTitle(title)
|
||||
.setSingleChoiceItems(R.array.bluetoothDeviceSettingNames, defaultChoice,
|
||||
new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
choice[0] = i;
|
||||
}
|
||||
})
|
||||
.setNegativeButton(R.string.common_cancel, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
dialogInterface.cancel();
|
||||
}
|
||||
})
|
||||
.setPositiveButton(R.string.common_ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
onChosen.accept(choice[0]);
|
||||
dialogInterface.dismiss();
|
||||
}
|
||||
})
|
||||
.create().show();
|
||||
}
|
||||
|
||||
private String bluetoothDevicePreferenceToString(int preferenceValue) {
|
||||
switch (preferenceValue) {
|
||||
case Constants.PREFERENCE_VALUE_ALL: return getString(R.string.settings_playback_bluetooth_all);
|
||||
case Constants.PREFERENCE_VALUE_A2DP: return getString(R.string.settings_playback_bluetooth_a2dp);
|
||||
case Constants.PREFERENCE_VALUE_DISABLED: return getString(R.string.settings_playback_bluetooth_disabled);
|
||||
default: return "";
|
||||
}
|
||||
}
|
||||
|
||||
private void setupClearSearchPreference() {
|
||||
Preference clearSearchPreference = findPreference(Constants.PREFERENCES_KEY_CLEAR_SEARCH_HISTORY);
|
||||
|
||||
if (clearSearchPreference != null) {
|
||||
clearSearchPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() {
|
||||
@Override
|
||||
public boolean onPreferenceClick(Preference preference) {
|
||||
SearchRecentSuggestions suggestions =
|
||||
new SearchRecentSuggestions(getActivity(),
|
||||
SearchSuggestionProvider.AUTHORITY,
|
||||
SearchSuggestionProvider.MODE);
|
||||
suggestions.clearHistory();
|
||||
Util.toast(getActivity(), R.string.settings_search_history_cleared);
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void setupFeatureFlagsPreferences() {
|
||||
final FeatureStorage featureStorage = KoinJavaComponent.get(FeatureStorage.class);
|
||||
|
||||
CheckBoxPreference useFiveStarRating = (CheckBoxPreference) findPreference(
|
||||
Constants.PREFERENCES_KEY_USE_FIVE_STAR_RATING);
|
||||
|
||||
if (useFiveStarRating != null) {
|
||||
useFiveStarRating.setChecked(featureStorage.isFeatureEnabled(Feature.FIVE_STAR_RATING));
|
||||
useFiveStarRating.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
|
||||
@Override
|
||||
public boolean onPreferenceChange(Preference preference, Object o) {
|
||||
featureStorage.changeFeatureFlag(Feature.FIVE_STAR_RATING, (Boolean) o);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void update() {
|
||||
theme.setSummary(theme.getEntry());
|
||||
maxBitrateWifi.setSummary(maxBitrateWifi.getEntry());
|
||||
maxBitrateMobile.setSummary(maxBitrateMobile.getEntry());
|
||||
cacheSize.setSummary(cacheSize.getEntry());
|
||||
preloadCount.setSummary(preloadCount.getEntry());
|
||||
bufferLength.setSummary(bufferLength.getEntry());
|
||||
incrementTime.setSummary(incrementTime.getEntry());
|
||||
networkTimeout.setSummary(networkTimeout.getEntry());
|
||||
maxAlbums.setSummary(maxAlbums.getEntry());
|
||||
maxArtists.setSummary(maxArtists.getEntry());
|
||||
maxSongs.setSummary(maxSongs.getEntry());
|
||||
defaultAlbums.setSummary(defaultAlbums.getEntry());
|
||||
defaultArtists.setSummary(defaultArtists.getEntry());
|
||||
defaultSongs.setSummary(defaultSongs.getEntry());
|
||||
chatRefreshInterval.setSummary(chatRefreshInterval.getEntry());
|
||||
directoryCacheTime.setSummary(directoryCacheTime.getEntry());
|
||||
viewRefresh.setSummary(viewRefresh.getEntry());
|
||||
sharingDefaultExpiration.setSummary(sharingDefaultExpiration.getText());
|
||||
sharingDefaultDescription.setSummary(sharingDefaultDescription.getText());
|
||||
sharingDefaultGreeting.setSummary(sharingDefaultGreeting.getText());
|
||||
cacheLocation.setSummary(Settings.getCacheLocation());
|
||||
|
||||
if (!mediaButtonsEnabled.isChecked()) {
|
||||
lockScreenEnabled.setChecked(false);
|
||||
lockScreenEnabled.setEnabled(false);
|
||||
}
|
||||
|
||||
if (!sendBluetoothNotifications.isChecked()) {
|
||||
sendBluetoothAlbumArt.setChecked(false);
|
||||
sendBluetoothAlbumArt.setEnabled(false);
|
||||
}
|
||||
|
||||
if (debugLogToFile.isChecked()) {
|
||||
debugLogToFile.setSummary(getString(R.string.settings_debug_log_path,
|
||||
FileUtil.getUltrasonicDirectory(), FileLoggerTree.FILENAME));
|
||||
} else {
|
||||
debugLogToFile.setSummary("");
|
||||
}
|
||||
|
||||
showArtistPicture.setEnabled(Settings.getShouldUseId3Tags());
|
||||
}
|
||||
|
||||
|
||||
private void setHideMedia(boolean hide) {
|
||||
File nomediaDir = new File(FileUtil.getUltrasonicDirectory(), ".nomedia");
|
||||
if (hide && !nomediaDir.exists()) {
|
||||
if (!nomediaDir.mkdir()) {
|
||||
Timber.w("Failed to create %s", nomediaDir);
|
||||
}
|
||||
} else if (nomediaDir.exists()) {
|
||||
if (!nomediaDir.delete()) {
|
||||
Timber.w("Failed to delete %s", nomediaDir);
|
||||
}
|
||||
}
|
||||
Util.toast(getActivity(), R.string.settings_hide_media_toast, false);
|
||||
}
|
||||
|
||||
private void setMediaButtonsEnabled(boolean enabled) {
|
||||
lockScreenEnabled.setEnabled(enabled);
|
||||
mediaSessionHandler.getValue().updateMediaButtonReceiver();
|
||||
}
|
||||
|
||||
private void setBluetoothPreferences(boolean enabled) {
|
||||
sendBluetoothAlbumArt.setEnabled(enabled);
|
||||
}
|
||||
|
||||
private void setCacheLocation(String path) {
|
||||
File dir = new File(path);
|
||||
|
||||
if (!FileUtil.ensureDirectoryExistsAndIsReadWritable(dir)) {
|
||||
permissionUtil.getValue().handlePermissionFailed(new PermissionUtil.PermissionRequestFinishedCallback() {
|
||||
@Override
|
||||
public void onPermissionRequestFinished(boolean hasPermission) {
|
||||
String currentPath = settings.getString(Constants.PREFERENCES_KEY_CACHE_LOCATION,
|
||||
FileUtil.getDefaultMusicDirectory().getPath());
|
||||
cacheLocation.setSummary(currentPath);
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
cacheLocation.setSummary(path);
|
||||
}
|
||||
|
||||
// Clear download queue.
|
||||
mediaPlayerControllerLazy.getValue().clear();
|
||||
}
|
||||
|
||||
private void setDebugLogToFile(boolean writeLog) {
|
||||
if (writeLog) {
|
||||
FileLoggerTree.Companion.plantToTimberForest();
|
||||
Timber.i("Enabled debug logging to file");
|
||||
} else {
|
||||
FileLoggerTree.Companion.uprootFromTimberForest();
|
||||
Timber.i("Disabled debug logging to file");
|
||||
|
||||
int fileNum = FileLoggerTree.Companion.getLogFileNumber();
|
||||
long fileSize = FileLoggerTree.Companion.getLogFileSizes();
|
||||
String message = getString(R.string.settings_debug_log_summary,
|
||||
String.valueOf(fileNum),
|
||||
String.valueOf(Math.ceil(fileSize / 1000000d)),
|
||||
FileUtil.getUltrasonicDirectory());
|
||||
|
||||
new AlertDialog.Builder(getActivity())
|
||||
.setMessage(message)
|
||||
.setIcon(android.R.drawable.ic_dialog_info)
|
||||
.setNegativeButton(R.string.settings_debug_log_keep, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
dialogInterface.cancel();
|
||||
}
|
||||
})
|
||||
.setPositiveButton(R.string.settings_debug_log_delete, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
FileLoggerTree.Companion.deleteLogFiles();
|
||||
Timber.i("Deleted debug log files");
|
||||
dialogInterface.dismiss();
|
||||
new AlertDialog.Builder(getActivity()).setMessage(R.string.settings_debug_log_deleted)
|
||||
.setPositiveButton(R.string.common_ok, new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
dialogInterface.dismiss();
|
||||
}
|
||||
}).create().show();
|
||||
}
|
||||
})
|
||||
.create().show();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
package org.moire.ultrasonic.service;
|
||||
|
||||
/**
|
||||
* Deprecated: Should be replaced with lambdas
|
||||
* Abstract class for consumers with one parameter
|
||||
* @param <T> The type of the object to consume
|
||||
*/
|
||||
@Deprecated
|
||||
public abstract class Consumer<T>
|
||||
{
|
||||
public abstract void accept(T t);
|
||||
}
|
|
@ -193,8 +193,7 @@ class ActiveServerProvider(
|
|||
* Queries the Id of the Active Server
|
||||
*/
|
||||
fun getActiveServerId(): Int {
|
||||
val preferences = Settings.preferences
|
||||
return preferences.getInt(Constants.PREFERENCES_KEY_SERVER_INSTANCE, -1)
|
||||
return Settings.activeServer
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -203,11 +202,7 @@ class ActiveServerProvider(
|
|||
fun setActiveServerId(serverId: Int) {
|
||||
resetMusicService()
|
||||
|
||||
val preferences = Settings.preferences
|
||||
val editor = preferences.edit()
|
||||
editor.putInt(Constants.PREFERENCES_KEY_SERVER_INSTANCE, serverId)
|
||||
editor.apply()
|
||||
|
||||
Settings.activeServer = serverId
|
||||
liveActiveServerId.postValue(serverId)
|
||||
}
|
||||
|
||||
|
@ -229,8 +224,7 @@ class ActiveServerProvider(
|
|||
if (isOffline()) {
|
||||
return false
|
||||
}
|
||||
val preferences = Settings.preferences
|
||||
return preferences.getBoolean(Constants.PREFERENCES_KEY_SERVER_SCALING, false)
|
||||
return Settings.serverScaling
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
package org.moire.ultrasonic.fragment
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.os.Build
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
|
@ -109,10 +107,8 @@ internal class ServerRowAdapter(
|
|||
}
|
||||
|
||||
// Set colors
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
icon?.setTint(ServerColor.getForegroundColor(context, setting?.color))
|
||||
background?.setTint(ServerColor.getBackgroundColor(context, setting?.color))
|
||||
}
|
||||
icon?.setTint(ServerColor.getForegroundColor(context, setting?.color))
|
||||
background?.setTint(ServerColor.getBackgroundColor(context, setting?.color))
|
||||
|
||||
// Set the final drawables
|
||||
image?.setImageDrawable(icon)
|
||||
|
@ -120,32 +116,16 @@ internal class ServerRowAdapter(
|
|||
|
||||
// Highlight the Active Server's row by changing its background
|
||||
if (index == activeServerProvider.getActiveServer().index) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
layout?.background = ContextCompat.getDrawable(context, R.drawable.select_ripple)
|
||||
} else {
|
||||
layout?.setBackgroundResource(
|
||||
Util.getResourceFromAttribute(context, R.attr.list_selector_holo_selected)
|
||||
)
|
||||
}
|
||||
layout?.background = ContextCompat.getDrawable(context, R.drawable.select_ripple)
|
||||
} else {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
layout?.background = ContextCompat.getDrawable(context, R.drawable.default_ripple)
|
||||
} else {
|
||||
layout?.setBackgroundResource(
|
||||
Util.getResourceFromAttribute(context, R.attr.list_selector_holo)
|
||||
)
|
||||
}
|
||||
layout?.background = ContextCompat.getDrawable(context, R.drawable.default_ripple)
|
||||
}
|
||||
|
||||
// Add the context menu for the row
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
serverMenu?.background = ContextCompat.getDrawable(
|
||||
context,
|
||||
R.drawable.select_ripple_circle
|
||||
)
|
||||
} else {
|
||||
serverMenu?.setBackgroundColor(Color.TRANSPARENT)
|
||||
}
|
||||
serverMenu?.background = ContextCompat.getDrawable(
|
||||
context,
|
||||
R.drawable.select_ripple_circle
|
||||
)
|
||||
|
||||
serverMenu?.setOnClickListener { view -> serverMenuClick(view, index) }
|
||||
|
||||
|
|
|
@ -0,0 +1,473 @@
|
|||
package org.moire.ultrasonic.fragment
|
||||
|
||||
import android.app.AlertDialog
|
||||
import android.content.DialogInterface
|
||||
import android.content.SharedPreferences
|
||||
import android.content.SharedPreferences.OnSharedPreferenceChangeListener
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.provider.SearchRecentSuggestions
|
||||
import android.view.View
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.preference.CheckBoxPreference
|
||||
import androidx.preference.EditTextPreference
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceCategory
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import java.io.File
|
||||
import kotlin.math.ceil
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.java.KoinJavaComponent.get
|
||||
import org.koin.java.KoinJavaComponent.inject
|
||||
import org.moire.ultrasonic.R
|
||||
import org.moire.ultrasonic.featureflags.Feature
|
||||
import org.moire.ultrasonic.featureflags.FeatureStorage
|
||||
import org.moire.ultrasonic.filepicker.FilePickerDialog.Companion.createFilePickerDialog
|
||||
import org.moire.ultrasonic.filepicker.OnFileSelectedListener
|
||||
import org.moire.ultrasonic.fragment.FragmentTitle.Companion.setTitle
|
||||
import org.moire.ultrasonic.log.FileLoggerTree
|
||||
import org.moire.ultrasonic.log.FileLoggerTree.Companion.deleteLogFiles
|
||||
import org.moire.ultrasonic.log.FileLoggerTree.Companion.getLogFileNumber
|
||||
import org.moire.ultrasonic.log.FileLoggerTree.Companion.getLogFileSizes
|
||||
import org.moire.ultrasonic.log.FileLoggerTree.Companion.plantToTimberForest
|
||||
import org.moire.ultrasonic.log.FileLoggerTree.Companion.uprootFromTimberForest
|
||||
import org.moire.ultrasonic.provider.SearchSuggestionProvider
|
||||
import org.moire.ultrasonic.service.MediaPlayerController
|
||||
import org.moire.ultrasonic.service.RxBus
|
||||
import org.moire.ultrasonic.util.Constants
|
||||
import org.moire.ultrasonic.util.FileUtil.defaultMusicDirectory
|
||||
import org.moire.ultrasonic.util.FileUtil.ensureDirectoryExistsAndIsReadWritable
|
||||
import org.moire.ultrasonic.util.FileUtil.ultrasonicDirectory
|
||||
import org.moire.ultrasonic.util.MediaSessionHandler
|
||||
import org.moire.ultrasonic.util.PermissionUtil
|
||||
import org.moire.ultrasonic.util.PermissionUtil.Companion.requestInitialPermission
|
||||
import org.moire.ultrasonic.util.Settings
|
||||
import org.moire.ultrasonic.util.Settings.preferences
|
||||
import org.moire.ultrasonic.util.Settings.shareGreeting
|
||||
import org.moire.ultrasonic.util.Settings.shouldUseId3Tags
|
||||
import org.moire.ultrasonic.util.TimeSpanPreference
|
||||
import org.moire.ultrasonic.util.TimeSpanPreferenceDialogFragmentCompat
|
||||
import org.moire.ultrasonic.util.Util.toast
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
* Shows main app settings.
|
||||
*/
|
||||
class SettingsFragment :
|
||||
PreferenceFragmentCompat(),
|
||||
OnSharedPreferenceChangeListener,
|
||||
KoinComponent {
|
||||
private var theme: ListPreference? = null
|
||||
private var maxBitrateWifi: ListPreference? = null
|
||||
private var maxBitrateMobile: ListPreference? = null
|
||||
private var cacheSize: ListPreference? = null
|
||||
private var cacheLocation: Preference? = null
|
||||
private var preloadCount: ListPreference? = null
|
||||
private var bufferLength: ListPreference? = null
|
||||
private var incrementTime: ListPreference? = null
|
||||
private var networkTimeout: ListPreference? = null
|
||||
private var maxAlbums: ListPreference? = null
|
||||
private var maxSongs: ListPreference? = null
|
||||
private var maxArtists: ListPreference? = null
|
||||
private var defaultAlbums: ListPreference? = null
|
||||
private var defaultSongs: ListPreference? = null
|
||||
private var defaultArtists: ListPreference? = null
|
||||
private var chatRefreshInterval: ListPreference? = null
|
||||
private var directoryCacheTime: ListPreference? = null
|
||||
private var mediaButtonsEnabled: CheckBoxPreference? = null
|
||||
private var lockScreenEnabled: CheckBoxPreference? = null
|
||||
private var sendBluetoothNotifications: CheckBoxPreference? = null
|
||||
private var sendBluetoothAlbumArt: CheckBoxPreference? = null
|
||||
private var showArtistPicture: CheckBoxPreference? = null
|
||||
private var viewRefresh: ListPreference? = null
|
||||
private var sharingDefaultDescription: EditTextPreference? = null
|
||||
private var sharingDefaultGreeting: EditTextPreference? = null
|
||||
private var sharingDefaultExpiration: TimeSpanPreference? = null
|
||||
private var resumeOnBluetoothDevice: Preference? = null
|
||||
private var pauseOnBluetoothDevice: Preference? = null
|
||||
private var debugLogToFile: CheckBoxPreference? = null
|
||||
|
||||
private val mediaPlayerControllerLazy = inject<MediaPlayerController>(
|
||||
MediaPlayerController::class.java
|
||||
)
|
||||
|
||||
private val permissionUtil = inject<PermissionUtil>(
|
||||
PermissionUtil::class.java
|
||||
)
|
||||
|
||||
private val mediaSessionHandler = inject<MediaSessionHandler>(
|
||||
MediaSessionHandler::class.java
|
||||
)
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.settings, rootKey)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
setTitle(this, R.string.menu_settings)
|
||||
theme = findPreference(Constants.PREFERENCES_KEY_THEME)
|
||||
maxBitrateWifi = findPreference(Constants.PREFERENCES_KEY_MAX_BITRATE_WIFI)
|
||||
maxBitrateMobile = findPreference(Constants.PREFERENCES_KEY_MAX_BITRATE_MOBILE)
|
||||
cacheSize = findPreference(Constants.PREFERENCES_KEY_CACHE_SIZE)
|
||||
cacheLocation = findPreference(Constants.PREFERENCES_KEY_CACHE_LOCATION)
|
||||
preloadCount = findPreference(Constants.PREFERENCES_KEY_PRELOAD_COUNT)
|
||||
bufferLength = findPreference(Constants.PREFERENCES_KEY_BUFFER_LENGTH)
|
||||
incrementTime = findPreference(Constants.PREFERENCES_KEY_INCREMENT_TIME)
|
||||
networkTimeout = findPreference(Constants.PREFERENCES_KEY_NETWORK_TIMEOUT)
|
||||
maxAlbums = findPreference(Constants.PREFERENCES_KEY_MAX_ALBUMS)
|
||||
maxSongs = findPreference(Constants.PREFERENCES_KEY_MAX_SONGS)
|
||||
maxArtists = findPreference(Constants.PREFERENCES_KEY_MAX_ARTISTS)
|
||||
defaultArtists = findPreference(Constants.PREFERENCES_KEY_DEFAULT_ARTISTS)
|
||||
defaultSongs = findPreference(Constants.PREFERENCES_KEY_DEFAULT_SONGS)
|
||||
defaultAlbums = findPreference(Constants.PREFERENCES_KEY_DEFAULT_ALBUMS)
|
||||
chatRefreshInterval = findPreference(Constants.PREFERENCES_KEY_CHAT_REFRESH_INTERVAL)
|
||||
directoryCacheTime = findPreference(Constants.PREFERENCES_KEY_DIRECTORY_CACHE_TIME)
|
||||
mediaButtonsEnabled = findPreference(Constants.PREFERENCES_KEY_MEDIA_BUTTONS)
|
||||
lockScreenEnabled = findPreference(Constants.PREFERENCES_KEY_SHOW_LOCK_SCREEN_CONTROLS)
|
||||
sendBluetoothAlbumArt = findPreference(Constants.PREFERENCES_KEY_SEND_BLUETOOTH_ALBUM_ART)
|
||||
sendBluetoothNotifications =
|
||||
findPreference(Constants.PREFERENCES_KEY_SEND_BLUETOOTH_NOTIFICATIONS)
|
||||
viewRefresh = findPreference(Constants.PREFERENCES_KEY_VIEW_REFRESH)
|
||||
sharingDefaultDescription =
|
||||
findPreference(Constants.PREFERENCES_KEY_DEFAULT_SHARE_DESCRIPTION)
|
||||
sharingDefaultGreeting = findPreference(Constants.PREFERENCES_KEY_DEFAULT_SHARE_GREETING)
|
||||
sharingDefaultExpiration =
|
||||
findPreference(Constants.PREFERENCES_KEY_DEFAULT_SHARE_EXPIRATION)
|
||||
resumeOnBluetoothDevice =
|
||||
findPreference(Constants.PREFERENCES_KEY_RESUME_ON_BLUETOOTH_DEVICE)
|
||||
pauseOnBluetoothDevice = findPreference(Constants.PREFERENCES_KEY_PAUSE_ON_BLUETOOTH_DEVICE)
|
||||
debugLogToFile = findPreference(Constants.PREFERENCES_KEY_DEBUG_LOG_TO_FILE)
|
||||
showArtistPicture = findPreference(Constants.PREFERENCES_KEY_SHOW_ARTIST_PICTURE)
|
||||
sharingDefaultGreeting!!.text = shareGreeting
|
||||
setupClearSearchPreference()
|
||||
setupFeatureFlagsPreferences()
|
||||
setupCacheLocationPreference()
|
||||
setupBluetoothDevicePreferences()
|
||||
|
||||
// After API26 foreground services must be used for music playback, and they must have a notification
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val notificationsCategory =
|
||||
findPreference<PreferenceCategory>(Constants.PREFERENCES_KEY_CATEGORY_NOTIFICATIONS)
|
||||
var preferenceToRemove =
|
||||
findPreference<Preference>(Constants.PREFERENCES_KEY_SHOW_NOTIFICATION)
|
||||
if (preferenceToRemove != null) notificationsCategory!!.removePreference(
|
||||
preferenceToRemove
|
||||
)
|
||||
preferenceToRemove = findPreference(Constants.PREFERENCES_KEY_ALWAYS_SHOW_NOTIFICATION)
|
||||
if (preferenceToRemove != null) notificationsCategory!!.removePreference(
|
||||
preferenceToRemove
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onActivityCreated(savedInstanceState: Bundle?) {
|
||||
super.onActivityCreated(savedInstanceState)
|
||||
update()
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
val preferences = preferences
|
||||
preferences.registerOnSharedPreferenceChangeListener(this)
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
val prefs = preferences
|
||||
prefs.unregisterOnSharedPreferenceChangeListener(this)
|
||||
}
|
||||
|
||||
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String) {
|
||||
Timber.d("Preference changed: %s", key)
|
||||
update()
|
||||
when (key) {
|
||||
Constants.PREFERENCES_KEY_HIDE_MEDIA -> {
|
||||
setHideMedia(sharedPreferences.getBoolean(key, false))
|
||||
}
|
||||
Constants.PREFERENCES_KEY_MEDIA_BUTTONS -> {
|
||||
setMediaButtonsEnabled(sharedPreferences.getBoolean(key, true))
|
||||
}
|
||||
Constants.PREFERENCES_KEY_SEND_BLUETOOTH_NOTIFICATIONS -> {
|
||||
setBluetoothPreferences(sharedPreferences.getBoolean(key, true))
|
||||
}
|
||||
Constants.PREFERENCES_KEY_DEBUG_LOG_TO_FILE -> {
|
||||
setDebugLogToFile(sharedPreferences.getBoolean(key, false))
|
||||
}
|
||||
Constants.PREFERENCES_KEY_ID3_TAGS -> {
|
||||
showArtistPicture!!.isEnabled = sharedPreferences.getBoolean(key, false)
|
||||
}
|
||||
Constants.PREFERENCES_KEY_THEME -> {
|
||||
RxBus.themeChangedEventPublisher.onNext(Unit)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDisplayPreferenceDialog(preference: Preference) {
|
||||
var dialogFragment: DialogFragment? = null
|
||||
if (preference is TimeSpanPreference) {
|
||||
dialogFragment = TimeSpanPreferenceDialogFragmentCompat()
|
||||
val bundle = Bundle(1)
|
||||
bundle.putString("key", preference.getKey())
|
||||
dialogFragment.setArguments(bundle)
|
||||
}
|
||||
if (dialogFragment != null) {
|
||||
dialogFragment.setTargetFragment(this, 0)
|
||||
dialogFragment.show(
|
||||
this.parentFragmentManager,
|
||||
"android.support.v7.preference.PreferenceFragment.DIALOG"
|
||||
)
|
||||
} else {
|
||||
super.onDisplayPreferenceDialog(preference)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupCacheLocationPreference() {
|
||||
cacheLocation!!.summary = Settings.cacheLocation
|
||||
cacheLocation!!.onPreferenceClickListener =
|
||||
Preference.OnPreferenceClickListener {
|
||||
// If the user tries to change the cache location,
|
||||
// we must first check to see if we have write access.
|
||||
requestInitialPermission(
|
||||
requireActivity()
|
||||
) {
|
||||
if (it) {
|
||||
val filePickerDialog = createFilePickerDialog(
|
||||
requireContext()
|
||||
)
|
||||
filePickerDialog.setDefaultDirectory(defaultMusicDirectory.path)
|
||||
filePickerDialog.setInitialDirectory(cacheLocation!!.summary.toString())
|
||||
filePickerDialog.setOnFileSelectedListener(object :
|
||||
OnFileSelectedListener {
|
||||
override fun onFileSelected(file: File?, path: String?) {
|
||||
if (path != null) {
|
||||
Settings.cacheLocation = path
|
||||
setCacheLocation(path)
|
||||
}
|
||||
}
|
||||
})
|
||||
filePickerDialog.show()
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupBluetoothDevicePreferences() {
|
||||
val resumeSetting = Settings.resumeOnBluetoothDevice
|
||||
val pauseSetting = Settings.pauseOnBluetoothDevice
|
||||
resumeOnBluetoothDevice!!.summary = bluetoothDevicePreferenceToString(resumeSetting)
|
||||
pauseOnBluetoothDevice!!.summary = bluetoothDevicePreferenceToString(pauseSetting)
|
||||
resumeOnBluetoothDevice!!.onPreferenceClickListener =
|
||||
Preference.OnPreferenceClickListener {
|
||||
showBluetoothDevicePreferenceDialog(
|
||||
R.string.settings_playback_resume_on_bluetooth_device,
|
||||
Settings.resumeOnBluetoothDevice
|
||||
) { choice: Int ->
|
||||
val editor = resumeOnBluetoothDevice!!.sharedPreferences.edit()
|
||||
editor.putInt(Constants.PREFERENCES_KEY_RESUME_ON_BLUETOOTH_DEVICE, choice)
|
||||
editor.apply()
|
||||
resumeOnBluetoothDevice!!.summary = bluetoothDevicePreferenceToString(choice)
|
||||
}
|
||||
true
|
||||
}
|
||||
pauseOnBluetoothDevice!!.onPreferenceClickListener =
|
||||
Preference.OnPreferenceClickListener {
|
||||
showBluetoothDevicePreferenceDialog(
|
||||
R.string.settings_playback_pause_on_bluetooth_device,
|
||||
Settings.pauseOnBluetoothDevice
|
||||
) { choice: Int ->
|
||||
Settings.pauseOnBluetoothDevice = choice
|
||||
pauseOnBluetoothDevice!!.summary = bluetoothDevicePreferenceToString(choice)
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
private fun showBluetoothDevicePreferenceDialog(
|
||||
@StringRes title: Int,
|
||||
defaultChoice: Int,
|
||||
onChosen: (Int) -> Unit
|
||||
) {
|
||||
val choice = intArrayOf(defaultChoice)
|
||||
AlertDialog.Builder(activity).setTitle(title)
|
||||
.setSingleChoiceItems(
|
||||
R.array.bluetoothDeviceSettingNames, defaultChoice
|
||||
) { _: DialogInterface?, i: Int -> choice[0] = i }
|
||||
.setNegativeButton(R.string.common_cancel) { dialogInterface: DialogInterface, _: Int ->
|
||||
dialogInterface.cancel()
|
||||
}
|
||||
.setPositiveButton(R.string.common_ok) { dialogInterface: DialogInterface, _: Int ->
|
||||
onChosen(choice[0])
|
||||
dialogInterface.dismiss()
|
||||
}
|
||||
.create().show()
|
||||
}
|
||||
|
||||
private fun bluetoothDevicePreferenceToString(preferenceValue: Int): String {
|
||||
return when (preferenceValue) {
|
||||
Constants.PREFERENCE_VALUE_ALL -> {
|
||||
getString(R.string.settings_playback_bluetooth_all)
|
||||
}
|
||||
Constants.PREFERENCE_VALUE_A2DP -> {
|
||||
getString(R.string.settings_playback_bluetooth_a2dp)
|
||||
}
|
||||
Constants.PREFERENCE_VALUE_DISABLED -> {
|
||||
getString(R.string.settings_playback_bluetooth_disabled)
|
||||
}
|
||||
else -> ""
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupClearSearchPreference() {
|
||||
val clearSearchPreference =
|
||||
findPreference<Preference>(Constants.PREFERENCES_KEY_CLEAR_SEARCH_HISTORY)
|
||||
if (clearSearchPreference != null) {
|
||||
clearSearchPreference.onPreferenceClickListener =
|
||||
Preference.OnPreferenceClickListener {
|
||||
val suggestions = SearchRecentSuggestions(
|
||||
activity,
|
||||
SearchSuggestionProvider.AUTHORITY,
|
||||
SearchSuggestionProvider.MODE
|
||||
)
|
||||
suggestions.clearHistory()
|
||||
toast(activity, R.string.settings_search_history_cleared)
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupFeatureFlagsPreferences() {
|
||||
val featureStorage = get<FeatureStorage>(FeatureStorage::class.java)
|
||||
val useFiveStarRating = findPreference<Preference>(
|
||||
Constants.PREFERENCES_KEY_USE_FIVE_STAR_RATING
|
||||
) as CheckBoxPreference?
|
||||
if (useFiveStarRating != null) {
|
||||
useFiveStarRating.isChecked = featureStorage.isFeatureEnabled(Feature.FIVE_STAR_RATING)
|
||||
useFiveStarRating.onPreferenceChangeListener =
|
||||
Preference.OnPreferenceChangeListener { _: Preference?, o: Any? ->
|
||||
featureStorage.changeFeatureFlag(Feature.FIVE_STAR_RATING, (o as Boolean?)!!)
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun update() {
|
||||
theme!!.summary = theme!!.entry
|
||||
maxBitrateWifi!!.summary = maxBitrateWifi!!.entry
|
||||
maxBitrateMobile!!.summary = maxBitrateMobile!!.entry
|
||||
cacheSize!!.summary = cacheSize!!.entry
|
||||
preloadCount!!.summary = preloadCount!!.entry
|
||||
bufferLength!!.summary = bufferLength!!.entry
|
||||
incrementTime!!.summary = incrementTime!!.entry
|
||||
networkTimeout!!.summary = networkTimeout!!.entry
|
||||
maxAlbums!!.summary = maxAlbums!!.entry
|
||||
maxArtists!!.summary = maxArtists!!.entry
|
||||
maxSongs!!.summary = maxSongs!!.entry
|
||||
defaultAlbums!!.summary = defaultAlbums!!.entry
|
||||
defaultArtists!!.summary = defaultArtists!!.entry
|
||||
defaultSongs!!.summary = defaultSongs!!.entry
|
||||
chatRefreshInterval!!.summary = chatRefreshInterval!!.entry
|
||||
directoryCacheTime!!.summary = directoryCacheTime!!.entry
|
||||
viewRefresh!!.summary = viewRefresh!!.entry
|
||||
sharingDefaultExpiration!!.summary = sharingDefaultExpiration!!.text
|
||||
sharingDefaultDescription!!.summary = sharingDefaultDescription!!.text
|
||||
sharingDefaultGreeting!!.summary = sharingDefaultGreeting!!.text
|
||||
cacheLocation!!.summary = Settings.cacheLocation
|
||||
if (!mediaButtonsEnabled!!.isChecked) {
|
||||
lockScreenEnabled!!.isChecked = false
|
||||
lockScreenEnabled!!.isEnabled = false
|
||||
}
|
||||
if (!sendBluetoothNotifications!!.isChecked) {
|
||||
sendBluetoothAlbumArt!!.isChecked = false
|
||||
sendBluetoothAlbumArt!!.isEnabled = false
|
||||
}
|
||||
if (debugLogToFile!!.isChecked) {
|
||||
debugLogToFile!!.summary = getString(
|
||||
R.string.settings_debug_log_path,
|
||||
ultrasonicDirectory, FileLoggerTree.FILENAME
|
||||
)
|
||||
} else {
|
||||
debugLogToFile!!.summary = ""
|
||||
}
|
||||
showArtistPicture!!.isEnabled = shouldUseId3Tags
|
||||
}
|
||||
|
||||
private fun setHideMedia(hide: Boolean) {
|
||||
val nomediaDir = File(ultrasonicDirectory, ".nomedia")
|
||||
if (hide && !nomediaDir.exists()) {
|
||||
if (!nomediaDir.mkdir()) {
|
||||
Timber.w("Failed to create %s", nomediaDir)
|
||||
}
|
||||
} else if (nomediaDir.exists()) {
|
||||
if (!nomediaDir.delete()) {
|
||||
Timber.w("Failed to delete %s", nomediaDir)
|
||||
}
|
||||
}
|
||||
toast(activity, R.string.settings_hide_media_toast, false)
|
||||
}
|
||||
|
||||
private fun setMediaButtonsEnabled(enabled: Boolean) {
|
||||
lockScreenEnabled!!.isEnabled = enabled
|
||||
mediaSessionHandler.value.updateMediaButtonReceiver()
|
||||
}
|
||||
|
||||
private fun setBluetoothPreferences(enabled: Boolean) {
|
||||
sendBluetoothAlbumArt!!.isEnabled = enabled
|
||||
}
|
||||
|
||||
private fun setCacheLocation(path: String) {
|
||||
val dir = File(path)
|
||||
if (!ensureDirectoryExistsAndIsReadWritable(dir)) {
|
||||
permissionUtil.value.handlePermissionFailed {
|
||||
val currentPath = Settings.cacheLocation
|
||||
cacheLocation!!.summary = currentPath
|
||||
}
|
||||
} else {
|
||||
cacheLocation!!.summary = path
|
||||
}
|
||||
|
||||
// Clear download queue.
|
||||
mediaPlayerControllerLazy.value.clear()
|
||||
}
|
||||
|
||||
private fun setDebugLogToFile(writeLog: Boolean) {
|
||||
if (writeLog) {
|
||||
plantToTimberForest()
|
||||
Timber.i("Enabled debug logging to file")
|
||||
} else {
|
||||
uprootFromTimberForest()
|
||||
Timber.i("Disabled debug logging to file")
|
||||
val fileNum = getLogFileNumber()
|
||||
val fileSize = getLogFileSizes()
|
||||
val message = getString(
|
||||
R.string.settings_debug_log_summary,
|
||||
fileNum.toString(),
|
||||
ceil(fileSize / 1000 * 1000.0).toString(),
|
||||
ultrasonicDirectory
|
||||
)
|
||||
val keep = R.string.settings_debug_log_keep
|
||||
val delete = R.string.settings_debug_log_delete
|
||||
AlertDialog.Builder(activity)
|
||||
.setMessage(message)
|
||||
.setIcon(android.R.drawable.ic_dialog_info)
|
||||
.setNegativeButton(keep) { dIf: DialogInterface, _: Int ->
|
||||
dIf.cancel()
|
||||
}
|
||||
.setPositiveButton(delete) { dIf: DialogInterface, _: Int ->
|
||||
deleteLogFiles()
|
||||
Timber.i("Deleted debug log files")
|
||||
dIf.dismiss()
|
||||
AlertDialog.Builder(activity)
|
||||
.setMessage(R.string.settings_debug_log_deleted)
|
||||
.setPositiveButton(R.string.common_ok) { dIf2: DialogInterface, _: Int ->
|
||||
dIf2.dismiss()
|
||||
}
|
||||
.create().show()
|
||||
}
|
||||
.create().show()
|
||||
}
|
||||
}
|
||||
}
|
|
@ -4,14 +4,11 @@ import android.content.Context
|
|||
import android.media.AudioAttributes
|
||||
import android.media.AudioManager
|
||||
import android.media.AudioManager.OnAudioFocusChangeListener
|
||||
import android.os.Build
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.media.AudioAttributesCompat
|
||||
import androidx.media.AudioFocusRequestCompat
|
||||
import androidx.media.AudioManagerCompat
|
||||
import org.koin.java.KoinJavaComponent.inject
|
||||
import org.moire.ultrasonic.domain.PlayerState
|
||||
import org.moire.ultrasonic.util.Constants
|
||||
import org.moire.ultrasonic.util.Settings
|
||||
import timber.log.Timber
|
||||
|
||||
|
@ -25,12 +22,8 @@ class AudioFocusHandler(private val context: Context) {
|
|||
context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
||||
}
|
||||
|
||||
private val preferences by lazy {
|
||||
Settings.preferences
|
||||
}
|
||||
|
||||
private val lossPref: Int
|
||||
get() = preferences.getString(Constants.PREFERENCES_KEY_TEMP_LOSS, "1")!!.toInt()
|
||||
get() = Settings.tempLoss
|
||||
|
||||
private val audioAttributesCompat by lazy {
|
||||
AudioAttributesCompat.Builder()
|
||||
|
@ -114,7 +107,6 @@ class AudioFocusHandler(private val context: Context) {
|
|||
private var lowerFocus = false
|
||||
|
||||
// TODO: This can be removed if we switch to androidx.media2.player
|
||||
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
|
||||
fun getAudioAttributes(): AudioAttributes {
|
||||
return AudioAttributes.Builder()
|
||||
.setUsage(AudioAttributes.USAGE_MEDIA)
|
||||
|
|
|
@ -10,11 +10,9 @@ package org.moire.ultrasonic.service
|
|||
import android.content.Context
|
||||
import android.content.Context.POWER_SERVICE
|
||||
import android.content.Intent
|
||||
import android.media.AudioManager
|
||||
import android.media.MediaPlayer
|
||||
import android.media.MediaPlayer.OnCompletionListener
|
||||
import android.media.audiofx.AudioEffect
|
||||
import android.os.Build
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.os.PowerManager
|
||||
|
@ -447,12 +445,7 @@ class LocalMediaPlayer : KoinComponent {
|
|||
}
|
||||
|
||||
private fun setAudioAttributes(player: MediaPlayer) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
player.setAudioAttributes(AudioFocusHandler.getAudioAttributes())
|
||||
} else {
|
||||
@Suppress("DEPRECATION")
|
||||
player.setAudioStreamType(AudioManager.STREAM_MUSIC)
|
||||
}
|
||||
player.setAudioAttributes(AudioFocusHandler.getAudioAttributes())
|
||||
}
|
||||
|
||||
@Suppress("ComplexCondition")
|
||||
|
|
|
@ -12,7 +12,6 @@ import android.content.Context
|
|||
import android.content.Intent
|
||||
import android.content.IntentFilter
|
||||
import android.media.AudioManager
|
||||
import android.os.Build
|
||||
import android.view.KeyEvent
|
||||
import io.reactivex.rxjava3.disposables.Disposable
|
||||
import org.koin.core.component.KoinComponent
|
||||
|
|
|
@ -22,10 +22,6 @@ import timber.log.Timber
|
|||
class PermissionUtil(private val applicationContext: Context) {
|
||||
private var activityContext: Context? = null
|
||||
|
||||
interface PermissionRequestFinishedCallback {
|
||||
fun onPermissionRequestFinished(hasPermission: Boolean)
|
||||
}
|
||||
|
||||
fun onForegroundApplicationStarted(context: Context?) {
|
||||
activityContext = context
|
||||
}
|
||||
|
@ -42,7 +38,7 @@ class PermissionUtil(private val applicationContext: Context) {
|
|||
*
|
||||
* @param callback callback function to execute after the permission request is finished
|
||||
*/
|
||||
fun handlePermissionFailed(callback: PermissionRequestFinishedCallback?) {
|
||||
fun handlePermissionFailed(callback: ((Boolean) -> Unit)?) {
|
||||
val currentCachePath = Settings.cacheLocation
|
||||
val defaultCachePath = defaultMusicDirectory.path
|
||||
|
||||
|
@ -76,7 +72,7 @@ class PermissionUtil(private val applicationContext: Context) {
|
|||
)
|
||||
}
|
||||
}
|
||||
callback?.onPermissionRequestFinished(false)
|
||||
callback?.invoke(false)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -91,7 +87,7 @@ class PermissionUtil(private val applicationContext: Context) {
|
|||
@JvmStatic
|
||||
fun requestInitialPermission(
|
||||
context: Context,
|
||||
callback: PermissionRequestFinishedCallback?
|
||||
callback: ((Boolean) -> Unit)?
|
||||
) {
|
||||
Dexter.withContext(context)
|
||||
.withPermissions(
|
||||
|
@ -102,7 +98,7 @@ class PermissionUtil(private val applicationContext: Context) {
|
|||
override fun onPermissionsChecked(report: MultiplePermissionsReport) {
|
||||
if (report.areAllPermissionsGranted()) {
|
||||
Timber.i("R/W permission granted for external storage")
|
||||
callback?.onPermissionRequestFinished(true)
|
||||
callback?.invoke(true)
|
||||
return
|
||||
}
|
||||
if (report.isAnyPermissionPermanentlyDenied) {
|
||||
|
@ -110,7 +106,7 @@ class PermissionUtil(private val applicationContext: Context) {
|
|||
"R/W permission is permanently denied for external storage"
|
||||
)
|
||||
showSettingsDialog(context)
|
||||
callback?.onPermissionRequestFinished(false)
|
||||
callback?.invoke(false)
|
||||
return
|
||||
}
|
||||
Timber.i("R/W permission is missing for external storage")
|
||||
|
@ -120,7 +116,7 @@ class PermissionUtil(private val applicationContext: Context) {
|
|||
context.getString(R.string.permissions_rationale_description_initial),
|
||||
null
|
||||
)
|
||||
callback?.onPermissionRequestFinished(false)
|
||||
callback?.invoke(false)
|
||||
}
|
||||
|
||||
override fun onPermissionRationaleShouldBeShown(
|
||||
|
@ -146,7 +142,7 @@ class PermissionUtil(private val applicationContext: Context) {
|
|||
private fun requestFailedPermission(
|
||||
context: Context,
|
||||
cacheLocation: String?,
|
||||
callback: PermissionRequestFinishedCallback?
|
||||
callback: ((Boolean) -> Unit)?
|
||||
) {
|
||||
Dexter.withContext(context)
|
||||
.withPermissions(
|
||||
|
@ -161,7 +157,7 @@ class PermissionUtil(private val applicationContext: Context) {
|
|||
if (cacheLocation != null) {
|
||||
Settings.cacheLocation = cacheLocation
|
||||
}
|
||||
callback?.onPermissionRequestFinished(true)
|
||||
callback?.invoke(true)
|
||||
return
|
||||
}
|
||||
if (report.isAnyPermissionPermanentlyDenied) {
|
||||
|
@ -170,7 +166,7 @@ class PermissionUtil(private val applicationContext: Context) {
|
|||
cacheLocation
|
||||
)
|
||||
showSettingsDialog(context)
|
||||
callback?.onPermissionRequestFinished(false)
|
||||
callback?.invoke(false)
|
||||
return
|
||||
}
|
||||
Timber.i(
|
||||
|
@ -182,7 +178,7 @@ class PermissionUtil(private val applicationContext: Context) {
|
|||
context, context.getString(R.string.permissions_message_box_title),
|
||||
context.getString(R.string.permissions_permission_missing), null
|
||||
)
|
||||
callback?.onPermissionRequestFinished(false)
|
||||
callback?.invoke(false)
|
||||
}
|
||||
|
||||
override fun onPermissionRationaleShouldBeShown(
|
||||
|
|
|
@ -185,6 +185,15 @@ object Settings {
|
|||
var shouldUseId3Tags
|
||||
by BooleanSetting(Constants.PREFERENCES_KEY_ID3_TAGS, false)
|
||||
|
||||
@JvmStatic
|
||||
var tempLoss by StringIntSetting(Constants.PREFERENCES_KEY_TEMP_LOSS, "1")
|
||||
|
||||
var activeServer by IntSetting(Constants.PREFERENCES_KEY_SERVER_INSTANCE, -1)
|
||||
|
||||
var serverScaling by BooleanSetting(Constants.PREFERENCES_KEY_SERVER_SCALING, false)
|
||||
|
||||
var firstRunExecuted by BooleanSetting(Constants.PREFERENCES_KEY_FIRST_RUN_EXECUTED, false)
|
||||
|
||||
val shouldShowArtistPicture: Boolean
|
||||
get() {
|
||||
val preferences = preferences
|
||||
|
|
|
@ -95,21 +95,18 @@ object Util {
|
|||
|
||||
@JvmStatic
|
||||
fun applyTheme(context: Context?) {
|
||||
val theme = Settings.theme
|
||||
if (Constants.PREFERENCES_KEY_THEME_DARK.equals(
|
||||
theme,
|
||||
ignoreCase = true
|
||||
) || "fullscreen".equals(theme, ignoreCase = true)
|
||||
) {
|
||||
context!!.setTheme(R.style.UltrasonicTheme)
|
||||
} else if (Constants.PREFERENCES_KEY_THEME_BLACK.equals(theme, ignoreCase = true)) {
|
||||
context!!.setTheme(R.style.UltrasonicTheme_Black)
|
||||
} else if (Constants.PREFERENCES_KEY_THEME_LIGHT.equals(
|
||||
theme,
|
||||
ignoreCase = true
|
||||
) || "fullscreenlight".equals(theme, ignoreCase = true)
|
||||
) {
|
||||
context!!.setTheme(R.style.UltrasonicTheme_Light)
|
||||
when (Settings.theme.lowercase()) {
|
||||
Constants.PREFERENCES_KEY_THEME_DARK,
|
||||
"fullscreen" -> {
|
||||
context!!.setTheme(R.style.UltrasonicTheme)
|
||||
}
|
||||
Constants.PREFERENCES_KEY_THEME_BLACK -> {
|
||||
context!!.setTheme(R.style.UltrasonicTheme_Black)
|
||||
}
|
||||
Constants.PREFERENCES_KEY_THEME_LIGHT,
|
||||
"fullscreenlight" -> {
|
||||
context!!.setTheme(R.style.UltrasonicTheme_Light)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -794,13 +791,9 @@ object Util {
|
|||
}
|
||||
|
||||
fun isFirstRun(): Boolean {
|
||||
val preferences = Settings.preferences
|
||||
val firstExecuted =
|
||||
preferences.getBoolean(Constants.PREFERENCES_KEY_FIRST_RUN_EXECUTED, false)
|
||||
if (firstExecuted) return false
|
||||
val editor = preferences.edit()
|
||||
editor.putBoolean(Constants.PREFERENCES_KEY_FIRST_RUN_EXECUTED, true)
|
||||
editor.apply()
|
||||
if (Settings.firstRunExecuted) return false
|
||||
|
||||
Settings.firstRunExecuted = true
|
||||
return true
|
||||
}
|
||||
|
||||
|
@ -925,7 +918,7 @@ object Util {
|
|||
}
|
||||
|
||||
fun getConnectivityManager(): ConnectivityManager {
|
||||
val context = Util.appContext()
|
||||
val context = appContext()
|
||||
return context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||
}
|
||||
}
|
||||
|
|
|
@ -400,7 +400,7 @@
|
|||
<string name="settings.playback.bluetooth_all">All Bluetooth devices</string>
|
||||
<string name="settings.playback.bluetooth_a2dp">Only audio (A2DP) devices</string>
|
||||
<string name="settings.playback.bluetooth_disabled">Disabled</string>
|
||||
<string name="settings.playback.single_button_bluetooth_device">Single button Play/Pause on Bluetooth device</string>
|
||||
<string name="settings.playback.single_button_bluetooth_device">Bluetooth device with only a single Play/Pause button</string>
|
||||
<string name="settings.playback.single_button_bluetooth_device_summary">Enabling this may help with older Bluetooth devices when Play/Pause doesn\'t work correctly</string>
|
||||
<string name="settings.debug.title">Debug options</string>
|
||||
<string name="settings.debug.log_to_file">Write debug log to file</string>
|
||||
|
|
Loading…
Reference in New Issue