diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/SettingsFragment.java b/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/SettingsFragment.java deleted file mode 100644 index c1fb0466..00000000 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/SettingsFragment.java +++ /dev/null @@ -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.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.ThemeChangedEventDistributor; -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 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 mediaPlayerControllerLazy = inject(MediaPlayerController.class); - private final Lazy permissionUtil = inject(PermissionUtil.class); - private final Lazy themeChangedEventDistributor = inject(ThemeChangedEventDistributor.class); - private final Lazy 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)) { - themeChangedEventDistributor.getValue().RaiseThemeChangedEvent(); - } - } - - @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() { - @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() { - @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 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(); - } - } -} diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/service/Consumer.java b/ultrasonic/src/main/java/org/moire/ultrasonic/service/Consumer.java deleted file mode 100644 index 6b8ca564..00000000 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/service/Consumer.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.moire.ultrasonic.service; - -/** - * Deprecated: Should be replaced with lambdas - * Abstract class for consumers with one parameter - * @param The type of the object to consume - */ -@Deprecated -public abstract class Consumer -{ - public abstract void accept(T t); -} diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/ActiveServerProvider.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/ActiveServerProvider.kt index 1d768071..4cfab1a9 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/ActiveServerProvider.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/ActiveServerProvider.kt @@ -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 } } } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/ServerRowAdapter.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/ServerRowAdapter.kt index 85190acb..2b9e4be1 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/ServerRowAdapter.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/ServerRowAdapter.kt @@ -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) } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/SettingsFragment.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/SettingsFragment.kt new file mode 100644 index 00000000..392c04a4 --- /dev/null +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/SettingsFragment.kt @@ -0,0 +1,474 @@ +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.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.ThemeChangedEventDistributor +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::class.java + ) + private val permissionUtil = inject( + PermissionUtil::class.java + ) + private val themeChangedEventDistributor = inject( + ThemeChangedEventDistributor::class.java + ) + private val mediaSessionHandler = inject( + 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(Constants.PREFERENCES_KEY_CATEGORY_NOTIFICATIONS) + var 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 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 -> { + themeChangedEventDistributor.value.RaiseThemeChangedEvent() + } + } + } + + 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(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::class.java) + val useFiveStarRating = findPreference( + 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() + } + } +} diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/AudioFocusHandler.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/AudioFocusHandler.kt index c49df186..34ad87cc 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/AudioFocusHandler.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/AudioFocusHandler.kt @@ -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) diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/LocalMediaPlayer.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/LocalMediaPlayer.kt index d30b5d00..0b0a1b42 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/LocalMediaPlayer.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/LocalMediaPlayer.kt @@ -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") diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerLifecycleSupport.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerLifecycleSupport.kt index 1bcdfce5..c69b820b 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerLifecycleSupport.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/service/MediaPlayerLifecycleSupport.kt @@ -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 org.koin.core.component.KoinComponent import org.koin.core.component.inject @@ -165,12 +164,7 @@ class MediaPlayerLifecycleSupport : KoinComponent { } } - val headsetIntentFilter: IntentFilter = - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - IntentFilter(AudioManager.ACTION_HEADSET_PLUG) - } else { - IntentFilter(Intent.ACTION_HEADSET_PLUG) - } + val headsetIntentFilter = IntentFilter(AudioManager.ACTION_HEADSET_PLUG) applicationContext().registerReceiver(headsetEventReceiver, headsetIntentFilter) } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/PermissionUtil.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/PermissionUtil.kt index 92813226..e39154c0 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/PermissionUtil.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/PermissionUtil.kt @@ -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( diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/Settings.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/Settings.kt index 73043a0b..7a45b123 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/Settings.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/Settings.kt @@ -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 diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/Util.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/Util.kt index 3c5d2b91..c6a8af0b 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/Util.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/Util.kt @@ -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 } } diff --git a/ultrasonic/src/main/res/values/strings.xml b/ultrasonic/src/main/res/values/strings.xml index f9666771..a4f4e6f2 100644 --- a/ultrasonic/src/main/res/values/strings.xml +++ b/ultrasonic/src/main/res/values/strings.xml @@ -400,7 +400,7 @@ All Bluetooth devices Only audio (A2DP) devices Disabled - Single button Play/Pause on Bluetooth device + Bluetooth device with only a single Play/Pause button Enabling this may help with older Bluetooth devices when Play/Pause doesn\'t work correctly Debug options Write debug log to file