Merge branch 'develop' into refactor-events

This commit is contained in:
Nite 2021-10-31 17:20:03 +01:00
commit fec2d78d30
No known key found for this signature in database
GPG Key ID: 1D1AD59B1C6386C1
12 changed files with 522 additions and 609 deletions

View File

@ -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();
}
}
}

View File

@ -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);
}

View File

@ -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
}
}
}

View File

@ -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) }

View File

@ -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()
}
}
}

View File

@ -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)

View File

@ -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")

View File

@ -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

View File

@ -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(

View File

@ -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

View File

@ -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
}
}

View File

@ -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>