diff --git a/dependencies.gradle b/dependencies.gradle index 3ca7ba9a..a18b5d87 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -1,5 +1,5 @@ ext.versions = [ - minSdk : 17, + minSdk : 21, targetSdk : 29, compileSdk : 29, // You need to run ./gradlew wrapper after updating the version @@ -42,6 +42,7 @@ ext.versions = [ dexter : "6.2.3", timber : "4.7.1", fastScroll : "2.0.1", + colorPicker : "2.2.3", ] ext.gradlePlugins = [ @@ -89,6 +90,7 @@ ext.other = [ timber : "com.jakewharton.timber:timber:$versions.timber", fastScroll : "com.simplecityapps:recyclerview-fastscroll:$versions.fastScroll", sortListView : "com.github.tzugen:drag-sort-listview:$versions.sortListView", + colorPickerView : "com.github.skydoves:colorpickerview:$versions.colorPicker", ] ext.testing = [ diff --git a/ultrasonic/build.gradle b/ultrasonic/build.gradle index bc04848a..7835f863 100644 --- a/ultrasonic/build.gradle +++ b/ultrasonic/build.gradle @@ -49,7 +49,7 @@ android { baselineFile file("lint-baseline.xml") ignore 'MissingTranslation' warning 'ImpliedQuantity' - disable 'IconMissingDensityFolder' + disable 'IconMissingDensityFolder', "VectorPath" abortOnError true warningsAsErrors true } @@ -105,6 +105,7 @@ dependencies { implementation other.okhttpLogging implementation other.fastScroll implementation other.sortListView + implementation other.colorPickerView kapt androidSupport.room diff --git a/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/SettingsFragment.java b/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/SettingsFragment.java index fc5802ab..c1fb0466 100644 --- a/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/SettingsFragment.java +++ b/ultrasonic/src/main/java/org/moire/ultrasonic/fragment/SettingsFragment.java @@ -55,7 +55,6 @@ import static org.moire.ultrasonic.fragment.ServerSelectorFragment.SERVER_SELECT public class SettingsFragment extends PreferenceFragmentCompat implements SharedPreferences.OnSharedPreferenceChangeListener { - private Preference addServerPreference; private ListPreference theme; private ListPreference maxBitrateWifi; private ListPreference maxBitrateMobile; @@ -110,7 +109,6 @@ public class SettingsFragment extends PreferenceFragmentCompat super.onViewCreated(view, savedInstanceState); FragmentTitle.Companion.setTitle(this, R.string.menu_settings); - addServerPreference = findPreference(Constants.PREFERENCES_KEY_SERVERS_EDIT); theme = findPreference(Constants.PREFERENCES_KEY_THEME); maxBitrateWifi = findPreference(Constants.PREFERENCES_KEY_MAX_BITRATE_WIFI); maxBitrateMobile = findPreference(Constants.PREFERENCES_KEY_MAX_BITRATE_MOBILE); @@ -141,7 +139,6 @@ public class SettingsFragment extends PreferenceFragmentCompat debugLogToFile = findPreference(Constants.PREFERENCES_KEY_DEBUG_LOG_TO_FILE); showArtistPicture = findPreference(Constants.PREFERENCES_KEY_SHOW_ARTIST_PICTURE); - setupServersCategory(); sharingDefaultGreeting.setText(Settings.getShareGreeting()); setupClearSearchPreference(); setupFeatureFlagsPreferences(); @@ -374,22 +371,6 @@ public class SettingsFragment extends PreferenceFragmentCompat } - private void setupServersCategory() { - addServerPreference.setPersistent(false); - addServerPreference.setTitle(getResources().getString(R.string.settings_server_manage_servers)); - addServerPreference.setEnabled(true); - - addServerPreference.setOnPreferenceClickListener(new Preference.OnPreferenceClickListener() { - @Override - public boolean onPreferenceClick(Preference preference) { - Bundle bundle = new Bundle(); - bundle.putBoolean(SERVER_SELECTOR_MANAGE_MODE, true); - Navigation.findNavController(getView()).navigate(R.id.settingsToServerSelector, bundle); - return true; - } - }); - } - private void update() { theme.setSummary(theme.getEntry()); maxBitrateWifi.setSummary(maxBitrateWifi.getEntry()); diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/activity/NavigationActivity.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/activity/NavigationActivity.kt index 30aa0797..a92bfcc4 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/activity/NavigationActivity.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/activity/NavigationActivity.kt @@ -3,6 +3,7 @@ package org.moire.ultrasonic.activity import android.app.AlertDialog import android.app.SearchManager import android.content.Intent +import android.content.res.ColorStateList import android.content.res.Resources import android.media.AudioManager import android.os.Bundle @@ -12,8 +13,10 @@ import android.view.KeyEvent import android.view.Menu import android.view.MenuItem import android.view.View +import android.widget.ImageView import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.widget.Toolbar +import androidx.core.content.ContextCompat import androidx.core.view.GravityCompat import androidx.drawerlayout.widget.DrawerLayout import androidx.fragment.app.FragmentContainerView @@ -26,12 +29,13 @@ import androidx.navigation.ui.onNavDestinationSelected import androidx.navigation.ui.setupActionBarWithNavController import androidx.navigation.ui.setupWithNavController import androidx.preference.PreferenceManager +import com.google.android.material.button.MaterialButton import com.google.android.material.navigation.NavigationView import org.koin.android.ext.android.inject import org.koin.androidx.viewmodel.ext.android.viewModel import org.moire.ultrasonic.R import org.moire.ultrasonic.data.ActiveServerProvider -import org.moire.ultrasonic.data.ActiveServerProvider.Companion.isOffline +import org.moire.ultrasonic.data.ServerSettingDao import org.moire.ultrasonic.domain.PlayerState import org.moire.ultrasonic.fragment.OnBackPressedHandler import org.moire.ultrasonic.fragment.ServerSettingsModel @@ -45,6 +49,7 @@ import org.moire.ultrasonic.util.FileUtil import org.moire.ultrasonic.util.NowPlayingEventDistributor import org.moire.ultrasonic.util.NowPlayingEventListener import org.moire.ultrasonic.util.PermissionUtil +import org.moire.ultrasonic.util.ServerColor import org.moire.ultrasonic.util.Settings import org.moire.ultrasonic.util.SubsonicUncaughtExceptionHandler import org.moire.ultrasonic.util.ThemeChangedEventDistributor @@ -65,6 +70,8 @@ class NavigationActivity : AppCompatActivity() { private var navigationView: NavigationView? = null private var drawerLayout: DrawerLayout? = null private var host: NavHostFragment? = null + private var selectServerButton: MaterialButton? = null + private var headerBackgroundImage: ImageView? = null private lateinit var appBarConfiguration: AppBarConfiguration private lateinit var nowPlayingEventListener: NowPlayingEventListener @@ -77,9 +84,12 @@ class NavigationActivity : AppCompatActivity() { private val nowPlayingEventDistributor: NowPlayingEventDistributor by inject() private val themeChangedEventDistributor: ThemeChangedEventDistributor by inject() private val permissionUtil: PermissionUtil by inject() + private val activeServerProvider: ActiveServerProvider by inject() + private val serverRepository: ServerSettingDao by inject() private var infoDialogDisplayed = false private var currentFragmentId: Int = 0 + private var cachedServerCount: Int = 0 override fun onCreate(savedInstanceState: Bundle?) { setUncaughtExceptionHandler() @@ -141,7 +151,7 @@ class NavigationActivity : AppCompatActivity() { } // Hides menu items for Offline mode - setMenuForServerSetting() + setMenuForServerCapabilities() } // Determine first run and migrate server settings to DB as early as possible @@ -180,12 +190,43 @@ class NavigationActivity : AppCompatActivity() { nowPlayingEventDistributor.subscribe(nowPlayingEventListener) themeChangedEventDistributor.subscribe(themeChangedEventListener) + + serverRepository.liveServerCount().observe( + this, + { count -> + cachedServerCount = count ?: 0 + updateNavigationHeaderForServer() + } + ) + ActiveServerProvider.liveActiveServerId.observe(this, { updateNavigationHeaderForServer() }) + } + + private fun updateNavigationHeaderForServer() { + val activeServer = activeServerProvider.getActiveServer() + + if (cachedServerCount == 0) + selectServerButton?.text = getString(R.string.main_setup_server, activeServer.name) + else selectServerButton?.text = activeServer.name + + val foregroundColor = ServerColor.getForegroundColor(this, activeServer.color) + val backgroundColor = ServerColor.getBackgroundColor(this, activeServer.color) + + if (activeServer.index == 0) + selectServerButton?.icon = + ContextCompat.getDrawable(this, R.drawable.ic_menu_screen_on_off_dark) + else + selectServerButton?.icon = + ContextCompat.getDrawable(this, R.drawable.ic_menu_select_server_dark) + + selectServerButton?.iconTint = ColorStateList.valueOf(foregroundColor) + selectServerButton?.setTextColor(foregroundColor) + headerBackgroundImage?.setBackgroundColor(backgroundColor) } override fun onResume() { super.onResume() - setMenuForServerSetting() + setMenuForServerCapabilities() // Lifecycle support's constructor registers some event receivers so it should be created early lifecycleSupport.onCreate() @@ -233,6 +274,15 @@ class NavigationActivity : AppCompatActivity() { bookmarksMenuItem = navigationView?.menu?.findItem(R.id.bookmarksFragment) sharesMenuItem = navigationView?.menu?.findItem(R.id.sharesFragment) podcastsMenuItem = navigationView?.menu?.findItem(R.id.podcastFragment) + selectServerButton = + navigationView?.getHeaderView(0)?.findViewById(R.id.header_select_server) + selectServerButton?.setOnClickListener { + if (drawerLayout?.isDrawerVisible(GravityCompat.START) == true) + this.drawerLayout?.closeDrawer(GravityCompat.START) + navController.navigate(R.id.serverSelectorFragment) + } + headerBackgroundImage = + navigationView?.getHeaderView(0)?.findViewById(R.id.img_header_bg) } private fun setupActionBar(navController: NavController, appBarConfig: AppBarConfiguration) { @@ -325,7 +375,7 @@ class NavigationActivity : AppCompatActivity() { .setNegativeButton(R.string.main_welcome_cancel) { dialog, _ -> // Go to the settings screen dialog.dismiss() - findNavController(R.id.nav_host_fragment).navigate(R.id.settingsFragment) + findNavController(R.id.nav_host_fragment).navigate(R.id.serverSelectorFragment) } .setPositiveButton(R.string.common_ok) { dialog, _ -> // Add the demo server @@ -377,15 +427,14 @@ class NavigationActivity : AppCompatActivity() { nowPlayingView?.visibility = View.GONE } - private fun setMenuForServerSetting() { - if (isOffline()) { + private fun setMenuForServerCapabilities() { + if (ActiveServerProvider.isOffline()) { chatMenuItem?.isVisible = false bookmarksMenuItem?.isVisible = false sharesMenuItem?.isVisible = false podcastsMenuItem?.isVisible = false return } - val activeServerProvider: ActiveServerProvider by inject() val activeServer = activeServerProvider.getActiveServer() chatMenuItem?.isVisible = activeServer.chatSupport != false bookmarksMenuItem?.isVisible = activeServer.bookmarkSupport != false 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 6b7d6d5f..1d768071 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/ActiveServerProvider.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/ActiveServerProvider.kt @@ -1,5 +1,6 @@ package org.moire.ultrasonic.data +import androidx.lifecycle.MutableLiveData import androidx.room.Room import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.GlobalScope @@ -178,6 +179,7 @@ class ActiveServerProvider( companion object { const val METADATA_DB = "$DB_FILENAME-meta-" + val liveActiveServerId: MutableLiveData = MutableLiveData(getActiveServerId()) /** * Queries if the Active Server is the "Offline" mode of Ultrasonic @@ -205,6 +207,8 @@ class ActiveServerProvider( val editor = preferences.edit() editor.putInt(Constants.PREFERENCES_KEY_SERVER_INSTANCE, serverId) editor.apply() + + liveActiveServerId.postValue(serverId) } /** diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/AppDatabase.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/AppDatabase.kt index e8c05cc7..ddbb62fb 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/AppDatabase.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/AppDatabase.kt @@ -9,7 +9,7 @@ import androidx.sqlite.db.SupportSQLiteDatabase * Room Database to be used to store global data for the whole app. * This could be settings or data that are not specific to any remote music database */ -@Database(entities = [ServerSetting::class], version = 3) +@Database(entities = [ServerSetting::class], version = 4) abstract class AppDatabase : RoomDatabase() { /** @@ -42,3 +42,11 @@ val MIGRATION_2_3: Migration = object : Migration(2, 3) { ) } } + +val MIGRATION_3_4: Migration = object : Migration(3, 4) { + override fun migrate(database: SupportSQLiteDatabase) { + database.execSQL( + "ALTER TABLE ServerSetting ADD COLUMN color INTEGER" + ) + } +} diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/ServerSetting.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/ServerSetting.kt index 705f526a..e3fb9b04 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/ServerSetting.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/ServerSetting.kt @@ -23,6 +23,7 @@ data class ServerSetting( @ColumnInfo(name = "index") var index: Int, @ColumnInfo(name = "name") var name: String, @ColumnInfo(name = "url") var url: String, + @ColumnInfo(name = "color") var color: Int? = null, @ColumnInfo(name = "userName") var userName: String, @ColumnInfo(name = "password") var password: String, @ColumnInfo(name = "jukeboxByDefault") var jukeboxByDefault: Boolean, @@ -36,9 +37,9 @@ data class ServerSetting( @ColumnInfo(name = "podcastSupport") var podcastSupport: Boolean? = null ) { constructor() : this ( - -1, 0, "", "", "", "", false, false, false, null, null + -1, 0, "", "", null, "", "", false, false, false, null, null ) constructor(name: String, url: String) : this( - -1, 0, name, url, "", "", false, false, false, null, null + -1, 0, name, url, null, "", "", false, false, false, null, null ) } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/ServerSettingDao.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/ServerSettingDao.kt index c715892e..b09149cc 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/ServerSettingDao.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/data/ServerSettingDao.kt @@ -63,6 +63,12 @@ interface ServerSettingDao { @Query("SELECT COUNT(*) FROM serverSetting") suspend fun count(): Int? + /** + * Retrieves the count of rows in the table as a LiveData + */ + @Query("SELECT COUNT(*) FROM serverSetting") + fun liveServerCount(): LiveData + /** * Retrieves the greatest value of the Id column in the table */ diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/AppPermanentStorageModule.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/AppPermanentStorageModule.kt index 26b3df43..992141e1 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/AppPermanentStorageModule.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/di/AppPermanentStorageModule.kt @@ -8,6 +8,7 @@ import org.koin.dsl.module import org.moire.ultrasonic.data.AppDatabase import org.moire.ultrasonic.data.MIGRATION_1_2 import org.moire.ultrasonic.data.MIGRATION_2_3 +import org.moire.ultrasonic.data.MIGRATION_3_4 import org.moire.ultrasonic.fragment.ServerSettingsModel import org.moire.ultrasonic.util.Settings @@ -28,6 +29,7 @@ val appPermanentStorage = module { ) .addMigrations(MIGRATION_1_2) .addMigrations(MIGRATION_2_3) + .addMigrations(MIGRATION_3_4) .build() } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/filepicker/FilePickerAdapter.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/filepicker/FilePickerAdapter.kt index 91ff2c52..5c4dcbc7 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/filepicker/FilePickerAdapter.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/filepicker/FilePickerAdapter.kt @@ -14,7 +14,6 @@ import androidx.appcompat.widget.AppCompatEditText import androidx.recyclerview.widget.RecyclerView import java.io.File import java.util.LinkedList -import kotlin.Comparator import org.moire.ultrasonic.R import org.moire.ultrasonic.util.Util import timber.log.Timber @@ -24,7 +23,8 @@ import timber.log.Timber * @author this implementation is loosely based on the work of Yogesh Sundaresan, * original license: http://www.apache.org/licenses/LICENSE-2.0 */ -internal class FilePickerAdapter : RecyclerView.Adapter { +internal class FilePickerAdapter(view: FilePickerView) : + RecyclerView.Adapter() { private var data: MutableList = LinkedList() var defaultDirectory: File = Environment.getExternalStorageDirectory() @@ -34,32 +34,15 @@ internal class FilePickerAdapter : RecyclerView.Adapter - get() = arrayOf( - "/storage/sdcard0", "/storage/sdcard1", "/storage/extsdcard", - "/storage/sdcard0/external_sdcard", "/mnt/extsdcard", "/mnt/sdcard/external_sd", - "/mnt/external_sd", "/mnt/media_rw/sdcard1", "/removable/microsd", "/mnt/emmc", - "/storage/external_SD", "/storage/ext_sd", "/storage/removable/sdcard1", - "/data/sdext", "/data/sdext2", "/data/sdext3", "/data/sdext4", "/sdcard1", - "/sdcard2", "/storage/microsd", "/data/user" - ) - private var folderIcon: Drawable? = null private var upIcon: Drawable? = null private var sdIcon: Drawable? = null - constructor(defaultDir: File, view: FilePickerView) : this(view) { - this.defaultDirectory = defaultDir - selectedDirectory = defaultDir - } - - constructor(view: FilePickerView) { + init { this.context = view.context - listerView = view - upIcon = Util.getDrawableFromAttribute(context, R.attr.filepicker_subdirectory_up) folderIcon = Util.getDrawableFromAttribute(context, R.attr.filepicker_folder) sdIcon = Util.getDrawableFromAttribute(context, R.attr.filepicker_sd_card) @@ -71,12 +54,10 @@ internal class FilePickerAdapter : RecyclerView.Adapter() - var storages: List? = null - var storagePaths: List? = null - if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) { - storages = context!!.getExternalFilesDirs(null).filterNotNull() - storagePaths = storages.map { i -> i.absolutePath } - } + val storages: List? + val storagePaths: List? + storages = context!!.getExternalFilesDirs(null).filterNotNull() + storagePaths = storages.map { i -> i.absolutePath } if (currentDirectory.absolutePath == "/" || currentDirectory.absolutePath == "/storage" || @@ -84,13 +65,7 @@ internal class FilePickerAdapter : RecyclerView.Adapter= android.os.Build.VERSION_CODES.KITKAT - ) { - getKitKatStorageItems(storages!!) - } else { - getStorageItems() - } + fileList = getKitKatStorageItems(storages) } else { isRealDirectory = true val files = currentDirectory.listFiles() @@ -103,20 +78,18 @@ internal class FilePickerAdapter : RecyclerView.Adapter - if (f1.file!!.isDirectory && f2.file!!.isDirectory) - f1.name.compareTo(f2.name, ignoreCase = true) - else if (f1.file!!.isDirectory && !f2.file!!.isDirectory) - -1 - else if (!f1.file!!.isDirectory && f2.file!!.isDirectory) - 1 - else if (!f1.file!!.isDirectory && !f2.file!!.isDirectory) - f1.name.compareTo(f2.name, ignoreCase = true) - else - 0 - } - ) + data.sortWith { f1, f2 -> + if (f1.file!!.isDirectory && f2.file!!.isDirectory) + f1.name.compareTo(f2.name, ignoreCase = true) + else if (f1.file!!.isDirectory && !f2.file!!.isDirectory) + -1 + else if (!f1.file!!.isDirectory && f2.file!!.isDirectory) + 1 + else if (!f1.file!!.isDirectory && !f2.file!!.isDirectory) + f1.name.compareTo(f2.name, ignoreCase = true) + else + 0 + } selectedDirectory = currentDirectory selectedDirectoryChanged.invoke( @@ -129,47 +102,16 @@ internal class FilePickerAdapter : RecyclerView.Adapter= android.os.Build.VERSION_CODES.KITKAT) { - if (storagePaths!!.indexOf(currentDirectory.absolutePath) > 0) - data.add(0, FileListItem(File("/"), "..", upIcon!!)) - else - data.add(0, FileListItem(selectedDirectory.parentFile!!, "..", upIcon!!)) - } else { + if (storagePaths.indexOf(currentDirectory.absolutePath) > 0) + data.add(0, FileListItem(File("/"), "..", upIcon!!)) + else data.add(0, FileListItem(selectedDirectory.parentFile!!, "..", upIcon!!)) - } } notifyDataSetChanged() listerView!!.scrollToPosition(0) } - private fun getStorageItems(): LinkedList { - val fileList = LinkedList() - var s = System.getenv("EXTERNAL_STORAGE") - if (!TextUtils.isEmpty(s)) { - val f = File(s!!) - fileList.add(FileListItem(f, f.name, sdIcon!!)) - } else { - val paths = physicalPaths - for (path in paths) { - val f = File(path) - if (f.exists()) - fileList.add(FileListItem(f, f.name, sdIcon!!)) - } - } - s = System.getenv("SECONDARY_STORAGE") - if (s != null && !TextUtils.isEmpty(s)) { - val rawSecondaryStorages = - s.split(File.pathSeparator.toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() - for (path in rawSecondaryStorages) { - val f = File(path) - if (f.exists()) - fileList.add(FileListItem(f, f.name, sdIcon!!)) - } - } - return fileList - } - private fun getKitKatStorageItems(storages: List): LinkedList { val fileList = LinkedList() if (storages.isNotEmpty()) { diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/EditServerFragment.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/EditServerFragment.kt index 57e75832..a95838cd 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/EditServerFragment.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/EditServerFragment.kt @@ -6,11 +6,16 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.Button +import android.widget.ImageView +import androidx.core.content.ContextCompat import androidx.fragment.app.Fragment -import androidx.lifecycle.Observer import androidx.navigation.fragment.findNavController import com.google.android.material.switchmaterial.SwitchMaterial import com.google.android.material.textfield.TextInputLayout +import com.skydoves.colorpickerview.ColorPickerDialog +import com.skydoves.colorpickerview.flag.BubbleFlag +import com.skydoves.colorpickerview.flag.FlagMode +import com.skydoves.colorpickerview.listeners.ColorEnvelopeListener import java.io.IOException import java.net.MalformedURLException import java.net.URL @@ -32,10 +37,13 @@ import org.moire.ultrasonic.service.MusicServiceFactory import org.moire.ultrasonic.util.Constants import org.moire.ultrasonic.util.ErrorDialog import org.moire.ultrasonic.util.ModalBackgroundTask +import org.moire.ultrasonic.util.ServerColor import org.moire.ultrasonic.util.Util import retrofit2.Response import timber.log.Timber +private const val DIALOG_PADDING = 12 + /** * Displays a form where server settings can be created / edited */ @@ -51,6 +59,7 @@ class EditServerFragment : Fragment(), OnBackPressedHandler { private var serverNameEditText: TextInputLayout? = null private var serverAddressEditText: TextInputLayout? = null + private var serverColorImageView: ImageView? = null private var userNameEditText: TextInputLayout? = null private var passwordEditText: TextInputLayout? = null private var selfSignedSwitch: SwitchMaterial? = null @@ -59,6 +68,8 @@ class EditServerFragment : Fragment(), OnBackPressedHandler { private var saveButton: Button? = null private var testButton: Button? = null private var isInstanceStateSaved: Boolean = false + private var currentColor: Int = 0 + private var selectedColor: Int? = null @Override override fun onCreate(savedInstanceState: Bundle?) { @@ -79,6 +90,7 @@ class EditServerFragment : Fragment(), OnBackPressedHandler { serverNameEditText = view.findViewById(R.id.edit_server_name) serverAddressEditText = view.findViewById(R.id.edit_server_address) + serverColorImageView = view.findViewById(R.id.edit_server_color_picker) userNameEditText = view.findViewById(R.id.edit_server_username) passwordEditText = view.findViewById(R.id.edit_server_password) selfSignedSwitch = view.findViewById(R.id.edit_self_signed) @@ -98,7 +110,7 @@ class EditServerFragment : Fragment(), OnBackPressedHandler { val serverSetting = serverSettingsModel.getServerSetting(index) serverSetting.observe( viewLifecycleOwner, - Observer { t -> + { t -> if (t != null) { currentServerSetting = t if (!isInstanceStateSaved) setFields() @@ -134,6 +146,7 @@ class EditServerFragment : Fragment(), OnBackPressedHandler { } else { // Creating a new server FragmentTitle.setTitle(this, R.string.server_editor_new_label) + updateColor(null) currentServerSetting = ServerSetting() saveButton!!.setOnClickListener { if (getFields()) { @@ -148,6 +161,36 @@ class EditServerFragment : Fragment(), OnBackPressedHandler { testConnection() } } + + serverColorImageView!!.setOnClickListener { + val bubbleFlag = BubbleFlag(context) + bubbleFlag.flagMode = FlagMode.LAST + ColorPickerDialog.Builder(context).apply { + this.colorPickerView.setInitialColor(currentColor) + this.colorPickerView.flagView = bubbleFlag + } + .attachAlphaSlideBar(false) + .setPositiveButton( + getString(R.string.common_ok), + ColorEnvelopeListener { envelope, _ -> + selectedColor = envelope.color + updateColor(envelope.color) + } + ) + .setNegativeButton(getString(R.string.common_cancel)) { + dialogInterface, i -> + dialogInterface.dismiss() + } + .setBottomSpace(DIALOG_PADDING) + .show() + } + } + + private fun updateColor(color: Int?) { + val image = ContextCompat.getDrawable(requireContext(), R.drawable.thumb_drawable) + currentColor = ServerColor.getBackgroundColor(requireContext(), color) + image?.setTint(currentColor) + serverColorImageView?.background = image } override fun onBackPressed() { @@ -176,6 +219,13 @@ class EditServerFragment : Fragment(), OnBackPressedHandler { savedInstanceState.putBoolean( ::jukeboxSwitch.name, jukeboxSwitch!!.isChecked ) + savedInstanceState.putInt( + ::serverColorImageView.name, currentColor + ) + if (selectedColor != null) + savedInstanceState.putInt( + ::selectedColor.name, selectedColor!! + ) savedInstanceState.putBoolean( ::isInstanceStateSaved.name, true ) @@ -203,6 +253,9 @@ class EditServerFragment : Fragment(), OnBackPressedHandler { selfSignedSwitch!!.isChecked = savedInstanceState.getBoolean(::selfSignedSwitch.name) ldapSwitch!!.isChecked = savedInstanceState.getBoolean(::ldapSwitch.name) jukeboxSwitch!!.isChecked = savedInstanceState.getBoolean(::jukeboxSwitch.name) + updateColor(savedInstanceState.getInt(::serverColorImageView.name)) + if (savedInstanceState.containsKey(::selectedColor.name)) + selectedColor = savedInstanceState.getInt(::selectedColor.name) isInstanceStateSaved = savedInstanceState.getBoolean(::isInstanceStateSaved.name) } @@ -219,6 +272,7 @@ class EditServerFragment : Fragment(), OnBackPressedHandler { selfSignedSwitch!!.isChecked = currentServerSetting!!.allowSelfSignedCertificate ldapSwitch!!.isChecked = currentServerSetting!!.ldapSupport jukeboxSwitch!!.isChecked = currentServerSetting!!.jukeboxByDefault + updateColor(currentServerSetting!!.color) } /** @@ -267,6 +321,7 @@ class EditServerFragment : Fragment(), OnBackPressedHandler { if (isValid) { currentServerSetting!!.name = serverNameEditText!!.editText?.text.toString() currentServerSetting!!.url = serverAddressEditText!!.editText?.text.toString() + currentServerSetting!!.color = selectedColor currentServerSetting!!.userName = userNameEditText!!.editText?.text.toString() currentServerSetting!!.password = passwordEditText!!.editText?.text.toString() currentServerSetting!!.allowSelfSignedCertificate = selfSignedSwitch!!.isChecked diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/MainFragment.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/MainFragment.kt index abb65951..56d33651 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/MainFragment.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/MainFragment.kt @@ -7,14 +7,10 @@ import android.view.ViewGroup import android.widget.AdapterView import android.widget.AdapterView.OnItemClickListener import android.widget.ListView -import android.widget.TextView import androidx.fragment.app.Fragment import androidx.navigation.Navigation -import java.util.Locale import org.koin.core.component.KoinComponent -import org.koin.core.component.inject import org.moire.ultrasonic.R -import org.moire.ultrasonic.data.ActiveServerProvider import org.moire.ultrasonic.data.ActiveServerProvider.Companion.isOffline import org.moire.ultrasonic.util.Constants import org.moire.ultrasonic.util.MergeAdapter @@ -27,8 +23,6 @@ import org.moire.ultrasonic.util.Util class MainFragment : Fragment(), KoinComponent { private var list: ListView? = null - private lateinit var serverButton: View - private lateinit var serverTextView: TextView private lateinit var musicTitle: View private lateinit var artistsButton: View private lateinit var albumsButton: View @@ -48,8 +42,6 @@ class MainFragment : Fragment(), KoinComponent { private lateinit var albumsAlphaByArtistButton: View private lateinit var videosButton: View - private val activeServerProvider: ActiveServerProvider by inject() - override fun onCreate(savedInstanceState: Bundle?) { Util.applyTheme(this.context) super.onCreate(savedInstanceState) @@ -65,7 +57,6 @@ class MainFragment : Fragment(), KoinComponent { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { list = view.findViewById(R.id.main_list) - cachedActiveServerProperties = getActiveServerProperties() setupButtons() @@ -78,7 +69,6 @@ class MainFragment : Fragment(), KoinComponent { super.onResume() var shouldRestart = false val currentId3Setting = Settings.shouldUseId3Tags - val currentActiveServerProperties = getActiveServerProperties() // If setting has changed... if (currentId3Setting != shouldUseId3) { @@ -86,12 +76,6 @@ class MainFragment : Fragment(), KoinComponent { shouldRestart = true } - // or the server has changed... - if (currentActiveServerProperties != cachedActiveServerProperties) { - cachedActiveServerProperties = currentActiveServerProperties - shouldRestart = true - } - // then setup the list anew. if (shouldRestart) { if (list != null) setupMenuList(list!!) @@ -100,8 +84,6 @@ class MainFragment : Fragment(), KoinComponent { private fun setupButtons() { val buttons = layoutInflater.inflate(R.layout.main_buttons, list, false) - serverButton = buttons.findViewById(R.id.main_select_server) - serverTextView = serverButton.findViewById(R.id.main_select_server_2) musicTitle = buttons.findViewById(R.id.main_music) artistsButton = buttons.findViewById(R.id.main_artists_button) albumsButton = buttons.findViewById(R.id.main_albums_button) @@ -123,15 +105,10 @@ class MainFragment : Fragment(), KoinComponent { } private fun setupMenuList(list: ListView) { - // Set title - val activeServerName = activeServerProvider.getActiveServer().name - serverTextView.text = activeServerName // TODO: Should use RecyclerView val adapter = MergeAdapter() - adapter.addView(serverButton, true) - shouldUseId3 = Settings.shouldUseId3Tags if (!isOffline()) { @@ -177,9 +154,6 @@ class MainFragment : Fragment(), KoinComponent { private val listListener = OnItemClickListener { _: AdapterView<*>?, view: View, _: Int, _: Long -> when { - view === serverButton -> { - showServers() - } view === albumsNewestButton -> { showAlbumList("newest", R.string.main_albums_newest) } @@ -225,20 +199,6 @@ class MainFragment : Fragment(), KoinComponent { } } - private fun getActiveServerProperties(): String { - val server = activeServerProvider.getActiveServer() - return String.format( - Locale.ROOT, - "%s;%s;%s;%s;%s;%s", - server.url, - server.userName, - server.password, - server.allowSelfSignedCertificate, - server.ldapSupport, - server.minimumApiVersion - ) - } - private fun showStarredSongs() { val bundle = Bundle() bundle.putInt(Constants.INTENT_EXTRA_NAME_STARRED, 1) @@ -281,12 +241,7 @@ class MainFragment : Fragment(), KoinComponent { Navigation.findNavController(requireView()).navigate(R.id.mainToTrackCollection, bundle) } - private fun showServers() { - Navigation.findNavController(requireView()).navigate(R.id.mainToServerSelector) - } - companion object { private var shouldUseId3 = false - private var cachedActiveServerProperties: String? = null } } 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 5bc27b0c..85190acb 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/ServerRowAdapter.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/ServerRowAdapter.kt @@ -2,6 +2,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 @@ -12,12 +13,13 @@ import android.widget.BaseAdapter import android.widget.ImageButton import android.widget.ImageView import android.widget.PopupMenu -import android.widget.RelativeLayout import android.widget.TextView +import androidx.constraintlayout.widget.ConstraintLayout import androidx.core.content.ContextCompat import org.moire.ultrasonic.R import org.moire.ultrasonic.data.ActiveServerProvider import org.moire.ultrasonic.data.ServerSetting +import org.moire.ultrasonic.util.ServerColor import org.moire.ultrasonic.util.Util /** @@ -67,8 +69,10 @@ internal class ServerRowAdapter( /** * Creates the Row representation of a Server Setting */ + @Suppress("LongMethod") override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View? { var index = position + // Skip "Offline" in manage mode if (manageMode) index++ @@ -77,28 +81,43 @@ internal class ServerRowAdapter( val text = vi?.findViewById(R.id.server_name) val description = vi?.findViewById(R.id.server_description) - val layout = vi?.findViewById(R.id.server_layout) + val layout = vi?.findViewById(R.id.server_layout) val image = vi?.findViewById(R.id.server_image) val serverMenu = vi?.findViewById(R.id.server_menu) + val setting = data.singleOrNull { t -> t.index == index } if (index == 0) { text?.text = context.getString(R.string.main_offline) description?.text = "" } else { - val setting = data.singleOrNull { t -> t.index == index } text?.text = setting?.name ?: "" description?.text = setting?.url ?: "" if (setting == null) serverMenu?.visibility = View.INVISIBLE } - // Provide icons for the row + val icon: Drawable? + val background: Drawable? + + // Configure icons for the row if (index == 0) { serverMenu?.visibility = View.INVISIBLE - image?.setImageDrawable(Util.getDrawableFromAttribute(context, R.attr.screen_on_off)) + icon = Util.getDrawableFromAttribute(context, R.attr.screen_on_off) + background = ContextCompat.getDrawable(context, R.drawable.circle) } else { - image?.setImageDrawable(Util.getDrawableFromAttribute(context, R.attr.server)) + icon = ContextCompat.getDrawable(context, R.drawable.ic_menu_server_dark) + background = ContextCompat.getDrawable(context, R.drawable.circle) } + // 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)) + } + + // Set the final drawables + image?.setImageDrawable(icon) + image?.background = background + // Highlight the Active Server's row by changing its background if (index == activeServerProvider.getActiveServer().index) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/ServerSelectorFragment.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/ServerSelectorFragment.kt index 9d66f6ef..05d7b568 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/ServerSelectorFragment.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/ServerSelectorFragment.kt @@ -88,7 +88,7 @@ class ServerSelectorFragment : Fragment() { editServer(position + 1) } else { setActiveServer(position) - findNavController().navigateUp() + findNavController().popBackStack(R.id.mainFragment, false) } } diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/ServerSettingsModel.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/ServerSettingsModel.kt index ad8499bf..65c2c6e6 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/ServerSettingsModel.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/fragment/ServerSettingsModel.kt @@ -212,6 +212,7 @@ class ServerSettingsModel( serverId, settings.getString(PREFERENCES_KEY_SERVER_NAME + preferenceId, "")!!, url, + null, userName, settings.getString(PREFERENCES_KEY_PASSWORD + preferenceId, "")!!, settings.getBoolean(PREFERENCES_KEY_JUKEBOX_BY_DEFAULT + preferenceId, false), diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/BitmapUtils.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/BitmapUtils.kt index fddbec66..348e0e6f 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/BitmapUtils.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/imageloader/BitmapUtils.kt @@ -65,10 +65,6 @@ class BitmapUtils { opt.inPreferQualityOverSpeed = true } - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { - opt.inPurgeable = true - } - opt.inSampleSize = Util.calculateInSampleSize( opt, size, @@ -103,10 +99,6 @@ class BitmapUtils { opt.inPreferQualityOverSpeed = true } - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { - opt.inPurgeable = true - } - opt.inSampleSize = Util.calculateInSampleSize( opt, size, diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/Constants.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/Constants.kt index 6206378f..45bde7fa 100644 --- a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/Constants.kt +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/Constants.kt @@ -54,7 +54,6 @@ object Constants { // Preferences keys. const val PREFERENCES_KEY_SERVER_INSTANCE = "serverInstanceId" - const val PREFERENCES_KEY_SERVERS_EDIT = "editServers" const val PREFERENCES_KEY_THEME = "theme" const val PREFERENCES_KEY_THEME_LIGHT = "light" const val PREFERENCES_KEY_THEME_DARK = "dark" diff --git a/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/ServerColor.kt b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/ServerColor.kt new file mode 100644 index 00000000..565e3aa7 --- /dev/null +++ b/ultrasonic/src/main/kotlin/org/moire/ultrasonic/util/ServerColor.kt @@ -0,0 +1,36 @@ +/* + * ServerColor.kt + * Copyright (C) 2009-2021 Ultrasonic developers + * + * Distributed under terms of the GNU GPLv3 license. + */ + +package org.moire.ultrasonic.util + +import android.content.Context +import androidx.core.content.ContextCompat +import androidx.core.graphics.ColorUtils +import org.moire.ultrasonic.R + +private const val LUMINANCE_LIMIT = 0.5 + +/** + * Contains functions for computing server display colors + */ +object ServerColor { + fun getBackgroundColor(context: Context, serverColor: Int?): Int { + return serverColor ?: ContextCompat.getColor( + context, Util.getResourceFromAttribute(context, R.attr.colorPrimary) + ) + } + + fun getForegroundColor(context: Context, serverColor: Int?): Int { + val backgroundColor = getBackgroundColor(context, serverColor) + val luminance = ColorUtils.calculateLuminance(backgroundColor) + return if (luminance < LUMINANCE_LIMIT) { + ContextCompat.getColor(context, R.color.selected_menu_dark) + } else { + ContextCompat.getColor(context, R.color.selected_menu_light) + } + } +} diff --git a/ultrasonic/src/main/res/drawable/circle.xml b/ultrasonic/src/main/res/drawable/circle.xml new file mode 100644 index 00000000..f42d2b06 --- /dev/null +++ b/ultrasonic/src/main/res/drawable/circle.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/ultrasonic/src/main/res/drawable/ic_header_bg.xml b/ultrasonic/src/main/res/drawable/ic_header_bg.xml new file mode 100644 index 00000000..35c11969 --- /dev/null +++ b/ultrasonic/src/main/res/drawable/ic_header_bg.xml @@ -0,0 +1,30 @@ + + + + + + diff --git a/ultrasonic/src/main/res/drawable/rounded_border.xml b/ultrasonic/src/main/res/drawable/rounded_border.xml new file mode 100644 index 00000000..5c60efee --- /dev/null +++ b/ultrasonic/src/main/res/drawable/rounded_border.xml @@ -0,0 +1,12 @@ + + + + + + + \ No newline at end of file diff --git a/ultrasonic/src/main/res/layout/main_buttons.xml b/ultrasonic/src/main/res/layout/main_buttons.xml index d5be2f11..ef1395c7 100644 --- a/ultrasonic/src/main/res/layout/main_buttons.xml +++ b/ultrasonic/src/main/res/layout/main_buttons.xml @@ -2,38 +2,6 @@ - - - - - - - - - - - - - - + a:orientation="vertical"> + a:src="@drawable/ic_header_bg" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintTop_toTopOf="parent" + a:importantForAccessibility="no" /> - + a:layout_marginTop="0dp" + a:background="@drawable/default_ripple" + a:gravity="center_vertical" + a:paddingHorizontal="22dp" + a:paddingTop="16dp" + a:paddingBottom="16dp" + a:text="@string/main.offline" + a:textAppearance="@style/MenuDrawer.Widget" + a:textColor="?attr/colorOnPrimary" + a:textSize="17sp" + a:textStyle="bold" + app:icon="@drawable/ic_menu_select_server_dark" + app:iconPadding="12dp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + tools:iconTint="@color/selected_menu_dark" + tools:textColor="@color/selected_menu_dark" /> - - - - - - \ No newline at end of file + \ No newline at end of file diff --git a/ultrasonic/src/main/res/layout/server_edit.xml b/ultrasonic/src/main/res/layout/server_edit.xml index a90ae7f3..6727d7f9 100644 --- a/ultrasonic/src/main/res/layout/server_edit.xml +++ b/ultrasonic/src/main/res/layout/server_edit.xml @@ -1,9 +1,10 @@ + a:fillViewport="true"> @@ -33,7 +33,6 @@ a:layout_height="wrap_content" a:layout_marginBottom="20dp" a:hint="@string/settings.server_address" - app:layout_constraintBottom_toTopOf="@id/edit_authentication_header" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/edit_server_name"> @@ -46,15 +45,38 @@ + + + + + app:layout_constraintTop_toBottomOf="@id/edit_server_color_picker" /> - + a:layout_margin="10dp" + a:background="@drawable/circle" + a:focusable="false" + a:paddingHorizontal="8dp" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + tools:src="@drawable/ic_menu_server_dark" + a:importantForAccessibility="no" /> + app:layout_constraintStart_toEndOf="@id/server_image" + app:layout_constraintTop_toTopOf="@id/server_image" + app:layout_constraintEnd_toStartOf="@id/server_menu" + tools:text="Server name" /> + app:layout_constraintBottom_toBottomOf="@id/server_image" + app:layout_constraintStart_toEndOf="@id/server_image" + app:layout_constraintTop_toBottomOf="@id/server_name" + app:layout_constraintEnd_toStartOf="@id/server_menu" + tools:text="Server description" /> - + diff --git a/ultrasonic/src/main/res/layout/server_selector.xml b/ultrasonic/src/main/res/layout/server_selector.xml index ba6de341..0577e548 100644 --- a/ultrasonic/src/main/res/layout/server_selector.xml +++ b/ultrasonic/src/main/res/layout/server_selector.xml @@ -7,18 +7,18 @@ + a:layout_height="fill_parent"/> + a:src="@drawable/ic_add_white" + a:contentDescription="@string/server_editor.new_label" /> \ No newline at end of file diff --git a/ultrasonic/src/main/res/layout/song_list_item.xml b/ultrasonic/src/main/res/layout/song_list_item.xml index ad7a325a..820bc3bb 100644 --- a/ultrasonic/src/main/res/layout/song_list_item.xml +++ b/ultrasonic/src/main/res/layout/song_list_item.xml @@ -4,8 +4,7 @@ a:layout_width="fill_parent" a:layout_height="wrap_content" a:minHeight="?android:attr/listPreferredItemHeight" - a:orientation="horizontal" - a:background="?attr/color_menu_background"> + a:orientation="horizontal"> - - Žánry Hudba Bez připojení - Vybrat server Náhodné přehrávání Náhodné Označené hvězdičkou @@ -277,7 +276,6 @@ Škálování obrázků alb na serveru Nepoužitý Uživatelské jméno - Servery Zobrazit ovládání na zamknuté obrazovce Zobrazí ovládání přehrávače na zamknuté obrazovce Zobrazení upozornění diff --git a/ultrasonic/src/main/res/values-de/strings.xml b/ultrasonic/src/main/res/values-de/strings.xml index 36a98998..0182cf3e 100644 --- a/ultrasonic/src/main/res/values-de/strings.xml +++ b/ultrasonic/src/main/res/values-de/strings.xml @@ -94,7 +94,6 @@ Genres Musik Offline - Server wählen Gemischte Wiedergabe Zufällig Mit Stern @@ -276,7 +275,6 @@ Serverseitige Skalierung der Cover Unbenutzt Benutzername - Server Steuerelemente auf Sperrbildschirm Wiedergabeelemente auf dem Sperrbildschirm anzeigen Benachrichtigungen anzeigen diff --git a/ultrasonic/src/main/res/values-es/strings.xml b/ultrasonic/src/main/res/values-es/strings.xml index d49d7dad..c176ee3f 100644 --- a/ultrasonic/src/main/res/values-es/strings.xml +++ b/ultrasonic/src/main/res/values-es/strings.xml @@ -108,7 +108,6 @@ Géneros Música Sin conexión - Seleccionar Servidor Reproducción aleatoria Aleatorio Me gusta @@ -296,7 +295,6 @@ Escalado de caratulas en el servidor Sin usar Nombre de usuario - Servidores Mostrar controles en la pantalla de bloqueo Mostrar controles de reproducción en la pantalla de bloqueo Mostrar notificación diff --git a/ultrasonic/src/main/res/values-fr/strings.xml b/ultrasonic/src/main/res/values-fr/strings.xml index 85981347..9b75c926 100644 --- a/ultrasonic/src/main/res/values-fr/strings.xml +++ b/ultrasonic/src/main/res/values-fr/strings.xml @@ -105,7 +105,6 @@ Genres Musique Hors-ligne - Sélectionner un serveur Lecture aléatoire Aléatoire Favoris @@ -291,7 +290,6 @@ Mise à l\'échelle des pochettes d\'album sur le serveur Inutilisé Nom d\'utilisateur - Serveurs Boutons de contrôle sur l\'écran de verrouillage Afficher les contrôles de lecture sur l\'écran de verrouillage Notifications diff --git a/ultrasonic/src/main/res/values-hu/strings.xml b/ultrasonic/src/main/res/values-hu/strings.xml index 67a8bed9..f1aa2a85 100644 --- a/ultrasonic/src/main/res/values-hu/strings.xml +++ b/ultrasonic/src/main/res/values-hu/strings.xml @@ -105,7 +105,6 @@ Műfajok Zenék Kapcsolat nélküli - Kiszolgáló kiválasztása Véletlen sorrendű Véletlenszerű Csillaggal megjelölt @@ -289,7 +288,6 @@ Albumborító átméretezés (Kiszolgáló-oldali) Kiszolgáló Felhasználónév - Kiszolgálók Képernyőzár kezelése Lejátszó-kezelőpanel megjelenítése a képernyőzáron. Értesítések megjelenítése diff --git a/ultrasonic/src/main/res/values-it/strings.xml b/ultrasonic/src/main/res/values-it/strings.xml index 2a97aab8..a37ab73c 100644 --- a/ultrasonic/src/main/res/values-it/strings.xml +++ b/ultrasonic/src/main/res/values-it/strings.xml @@ -92,7 +92,6 @@ Generi Musica Disconnesso - Seleziona Server Riproduzione casuale Casuale Preferiti @@ -270,7 +269,6 @@ Ridimensionamento copertine Album lato server Inutilizzato Username - Server Mostra i controlli del blocco schermo Mostra i controlli di riproduzione sulla schermata di blocco Mostra notifica diff --git a/ultrasonic/src/main/res/values-nl/strings.xml b/ultrasonic/src/main/res/values-nl/strings.xml index 2ef3be91..0876e987 100644 --- a/ultrasonic/src/main/res/values-nl/strings.xml +++ b/ultrasonic/src/main/res/values-nl/strings.xml @@ -108,7 +108,6 @@ Genres Muziek Offline - Kies een server Willekeurig afspelen Willekeurig Favorieten @@ -296,7 +295,6 @@ Verkleinde afbeeldingen ophalen van server Ongebruikt Gebruikersnaam - Servers Vergrendelschermbediening tonen Toont afspeelbediening op het vergrendelscherm Melding tonen diff --git a/ultrasonic/src/main/res/values-pl/strings.xml b/ultrasonic/src/main/res/values-pl/strings.xml index 4f69a283..e794e991 100644 --- a/ultrasonic/src/main/res/values-pl/strings.xml +++ b/ultrasonic/src/main/res/values-pl/strings.xml @@ -94,7 +94,6 @@ Gatunki Muzyka Offline - Wybierz serwer Losowo Losowe Ulubione @@ -275,7 +274,6 @@ Skalowanie okładek po stronie serwera Bez nazwy Nazwa użytkownika - Serwery Wyświetlaj na ekranie blokady Wyświetla widżet odtwarzacza na ekranie blokady Wyświetlaj powiadomienia diff --git a/ultrasonic/src/main/res/values-pt-rBR/strings.xml b/ultrasonic/src/main/res/values-pt-rBR/strings.xml index da2c4844..41dd11fa 100644 --- a/ultrasonic/src/main/res/values-pt-rBR/strings.xml +++ b/ultrasonic/src/main/res/values-pt-rBR/strings.xml @@ -105,7 +105,6 @@ Gêneros Música Offline - Selecione o Servidor Misturar Músicas Aleatórias Favoritas @@ -293,7 +292,6 @@ Reduzir Arte dos Álbuns Não usado Login - Servidores Controles na Tela de Bloqueio Mostrar controles de reprodução na tela de bloqueio Mostrar Notificações diff --git a/ultrasonic/src/main/res/values-pt/strings.xml b/ultrasonic/src/main/res/values-pt/strings.xml index 10141412..3706c9ee 100644 --- a/ultrasonic/src/main/res/values-pt/strings.xml +++ b/ultrasonic/src/main/res/values-pt/strings.xml @@ -94,7 +94,6 @@ Gêneros Música Offline - Selecione o Servidor Misturar Músicas Aleatórias Favoritas @@ -275,7 +274,6 @@ Reduzir Arte dos Álbuns Não usado Login - Servidores Controles no Ecrã de Bloqueio Mostra controles de reprodução no ecrã de bloqueio Mostrar Notificações diff --git a/ultrasonic/src/main/res/values-ru/strings.xml b/ultrasonic/src/main/res/values-ru/strings.xml index fb23d5d6..2c4091ea 100644 --- a/ultrasonic/src/main/res/values-ru/strings.xml +++ b/ultrasonic/src/main/res/values-ru/strings.xml @@ -105,7 +105,6 @@ Жанры Музыка Не в сети - Выбрать сервер Играть в случайном порядке Случайный Отмеченные @@ -291,7 +290,6 @@ Серверное масштабирование обложек альбомов Неиспользуемый Имя пользователя - Серверы Показать блокировку экрана Показать элементы управления воспроизведением на экране блокировки Показывать уведомления diff --git a/ultrasonic/src/main/res/values-zh-rCN/strings.xml b/ultrasonic/src/main/res/values-zh-rCN/strings.xml index 1de230a9..00860b89 100644 --- a/ultrasonic/src/main/res/values-zh-rCN/strings.xml +++ b/ultrasonic/src/main/res/values-zh-rCN/strings.xml @@ -104,7 +104,6 @@ 流派 音乐 离线 - 选择服务器 随机播放 随机 收藏夹 @@ -289,7 +288,6 @@ 服务器端专辑图片缩放 未启用 用户名 - 服务器 锁屏显示控制器 在锁定屏幕上显示播放控件 显示通知 diff --git a/ultrasonic/src/main/res/values/colors.xml b/ultrasonic/src/main/res/values/colors.xml index b89791a1..9d5bf7c3 100644 --- a/ultrasonic/src/main/res/values/colors.xml +++ b/ultrasonic/src/main/res/values/colors.xml @@ -3,17 +3,20 @@ #555555 #FFFFFF #00000000 - #ff0099cc + #0099cc + #6200EE + #BB86FC #8033b5e5 #00000000 #80000000 - #ff000000 - #fff3f3f3 + #000000 + #333333 + #ffffff #424242 - #B1B1B1 - #fff3f3f3 - #ff000000 - #ff333333 - #ff111111 - #fff3f3f3 + #B6B6B6 + #F3F3F3 + #000000 + #333333 + #111111 + #f3f3f3 \ No newline at end of file diff --git a/ultrasonic/src/main/res/values/strings.xml b/ultrasonic/src/main/res/values/strings.xml index e7ed3793..f9666771 100644 --- a/ultrasonic/src/main/res/values/strings.xml +++ b/ultrasonic/src/main/res/values/strings.xml @@ -108,7 +108,7 @@ Genres Music Offline - Select Server + %s - Set up Server Shuffle Play Random Starred @@ -299,7 +299,7 @@ Server-Side Album Art Scaling Unused Username - Servers + Server color Show Lock Screen Controls Show playback controls on the lock screen Show Notification diff --git a/ultrasonic/src/main/res/values/styles.xml b/ultrasonic/src/main/res/values/styles.xml index b90b338a..d1d05648 100644 --- a/ultrasonic/src/main/res/values/styles.xml +++ b/ultrasonic/src/main/res/values/styles.xml @@ -1,6 +1,6 @@ - @@ -30,7 +30,7 @@ 2dp - @@ -86,6 +86,7 @@ + diff --git a/ultrasonic/src/main/res/values/themes.xml b/ultrasonic/src/main/res/values/themes.xml index 05bb7cc5..286b548c 100644 --- a/ultrasonic/src/main/res/values/themes.xml +++ b/ultrasonic/src/main/res/values/themes.xml @@ -7,6 +7,7 @@ @color/selected_color_dark @color/selected_menu_dark @color/selected_menu_background_black + @color/navigation_header_dark @drawable/ic_star_hollow_dark @drawable/ic_star_full_dark @drawable/ic_menu_about_dark @@ -63,13 +64,15 @@ @drawable/list_selector_holo_dark_selected - -