Merge branch 'develop' into ready/dialogs

This commit is contained in:
Nite 2021-12-20 09:22:39 +01:00 committed by GitHub
commit a3ad17692b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 407 additions and 914 deletions

View File

@ -1,316 +0,0 @@
/**
Copyright (c) 2008-2009 CommonsWare, LLC
Portions (c) 2009 Google, Inc.
Licensed under the Apache License, Version 2.0 (the "License"); you may
not use this file except in compliance with the License. You may obtain
a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package org.moire.ultrasonic.util;
import android.database.DataSetObserver;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ListAdapter;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Adapter that merges multiple child adapters and views
* into a single contiguous whole.
* <p/>
* Adapters used as pieces within MergeAdapter must
* have view type IDs monotonically increasing from 0. Ideally,
* adapters also have distinct ranges for their row ids, as
* returned by getItemId().
*/
public class MergeAdapter extends BaseAdapter
{
private final CascadeDataSetObserver observer = new CascadeDataSetObserver();
private final AbstractList<ListAdapter> pieces = new ArrayList<ListAdapter>();
/**
* Adds a new adapter to the roster of things to appear
* in the aggregate list.
*
* @param adapter Source for row views for this section
*/
public void addAdapter(ListAdapter adapter)
{
pieces.add(adapter);
adapter.registerDataSetObserver(observer);
}
public void removeAdapter(ListAdapter adapter)
{
adapter.unregisterDataSetObserver(observer);
pieces.remove(adapter);
}
/**
* Adds a new View to the roster of things to appear
* in the aggregate list.
*
* @param view Single view to add
*/
public ListAdapter addView(View view)
{
return addView(view, false);
}
/**
* Adds a new View to the roster of things to appear
* in the aggregate list.
*
* @param view Single view to add
* @param enabled false if views are disabled, true if enabled
*/
public ListAdapter addView(View view, boolean enabled)
{
return addViews(Collections.singletonList(view), enabled);
}
/**
* Adds a list of views to the roster of things to appear
* in the aggregate list.
*
* @param views List of views to add
*/
public ListAdapter addViews(List<View> views)
{
return addViews(views, false);
}
/**
* Adds a list of views to the roster of things to appear
* in the aggregate list.
*
* @param views List of views to add
* @param enabled false if views are disabled, true if enabled
*/
public ListAdapter addViews(List<View> views, boolean enabled)
{
ListAdapter adapter = enabled ? new EnabledSackAdapter(views) : new SackOfViewsAdapter(views);
addAdapter(adapter);
return adapter;
}
/**
* Get the data item associated with the specified
* position in the data set.
*
* @param position Position of the item whose data we want
*/
@Override
public Object getItem(int position)
{
for (ListAdapter piece : pieces)
{
int size = piece.getCount();
if (position < size)
{
return (piece.getItem(position));
}
position -= size;
}
return (null);
}
/**
* How many items are in the data set represented by this
* Adapter.
*/
@Override
public int getCount()
{
int total = 0;
for (ListAdapter piece : pieces)
{
total += piece.getCount();
}
return (total);
}
/**
* Returns the number of types of Views that will be
* created by getView().
*/
@Override
public int getViewTypeCount()
{
int total = 0;
for (ListAdapter piece : pieces)
{
total += piece.getViewTypeCount();
}
return (Math.max(total, 1)); // needed for setListAdapter() before content add'
}
/**
* Get the type of View that will be created by getView()
* for the specified item.
*
* @param position Position of the item whose data we want
*/
@Override
public int getItemViewType(int position)
{
int typeOffset = 0;
int result = -1;
for (ListAdapter piece : pieces)
{
int size = piece.getCount();
if (position < size)
{
result = typeOffset + piece.getItemViewType(position);
break;
}
position -= size;
typeOffset += piece.getViewTypeCount();
}
return (result);
}
/**
* Are all items in this ListAdapter enabled? If yes it
* means all items are selectable and clickable.
*/
@Override
public boolean areAllItemsEnabled()
{
return (false);
}
/**
* Returns true if the item at the specified position is
* not a separator.
*
* @param position Position of the item whose data we want
*/
@Override
public boolean isEnabled(int position)
{
for (ListAdapter piece : pieces)
{
int size = piece.getCount();
if (position < size)
{
return (piece.isEnabled(position));
}
position -= size;
}
return (false);
}
/**
* Get a View that displays the data at the specified
* position in the data set.
*
* @param position Position of the item whose data we want
* @param convertView View to recycle, if not null
* @param parent ViewGroup containing the returned View
*/
@Override
public View getView(int position, View convertView, ViewGroup parent)
{
for (ListAdapter piece : pieces)
{
int size = piece.getCount();
if (position < size)
{
return (piece.getView(position, convertView, parent));
}
position -= size;
}
return (null);
}
/**
* Get the row id associated with the specified position
* in the list.
*
* @param position Position of the item whose data we want
*/
@Override
public long getItemId(int position)
{
for (ListAdapter piece : pieces)
{
int size = piece.getCount();
if (position < size)
{
return (piece.getItemId(position));
}
position -= size;
}
return (-1);
}
private static class EnabledSackAdapter extends SackOfViewsAdapter
{
public EnabledSackAdapter(List<View> views)
{
super(views);
}
@Override
public boolean areAllItemsEnabled()
{
return (true);
}
@Override
public boolean isEnabled(int position)
{
return (true);
}
}
private class CascadeDataSetObserver extends DataSetObserver
{
@Override
public void onChanged()
{
notifyDataSetChanged();
}
@Override
public void onInvalidated()
{
notifyDataSetInvalidated();
}
}
}

View File

@ -1,195 +0,0 @@
/**
Copyright (c) 2008-2009 CommonsWare, LLC
Portions (c) 2009 Google, Inc.
Licensed under the Apache License, Version 2.0 (the "License"); you may
not use this file except in compliance with the License. You may obtain
a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package org.moire.ultrasonic.util;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ListView;
import java.util.ArrayList;
import java.util.List;
/**
* Adapter that simply returns row views from a list.
* <p/>
* If you supply a size, you must implement newView(), to
* create a required view. The adapter will then cache these
* views.
* <p/>
* If you supply a list of views in the constructor, that
* list will be used directly. If any elements in the list
* are null, then newView() will be called just for those
* slots.
* <p/>
* Subclasses may also wish to override areAllItemsEnabled()
* (default: false) and isEnabled() (default: false), if some
* of their rows should be selectable.
* <p/>
* It is assumed each view is unique, and therefore will not
* get recycled.
* <p/>
* Note that this adapter is not designed for long lists. It
* is more for screens that should behave like a list. This
* is particularly useful if you combine this with other
* adapters (e.g., SectionedAdapter) that might have an
* arbitrary number of rows, so it all appears seamless.
*/
public class SackOfViewsAdapter extends BaseAdapter
{
private List<View> views;
/**
* Constructor creating an empty list of views, but with
* a specified count. Subclasses must override newView().
*/
public SackOfViewsAdapter(int count)
{
super();
views = new ArrayList<View>(count);
for (int i = 0; i < count; i++)
{
views.add(null);
}
}
/**
* Constructor wrapping a supplied list of views.
* Subclasses must override newView() if any of the elements
* in the list are null.
*/
public SackOfViewsAdapter(List<View> views)
{
for (View view : views)
{
view.setLayoutParams(new ListView.LayoutParams(ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
}
this.views = views;
}
/**
* Get the data item associated with the specified
* position in the data set.
*
* @param position Position of the item whose data we want
*/
@Override
public Object getItem(int position)
{
return (views.get(position));
}
/**
* How many items are in the data set represented by this
* Adapter.
*/
@Override
public int getCount()
{
return (views.size());
}
/**
* Returns the number of types of Views that will be
* created by getView().
*/
@Override
public int getViewTypeCount()
{
return (getCount());
}
/**
* Get the type of View that will be created by getView()
* for the specified item.
*
* @param position Position of the item whose data we want
*/
@Override
public int getItemViewType(int position)
{
return (position);
}
/**
* Are all items in this ListAdapter enabled? If yes it
* means all items are selectable and clickable.
*/
@Override
public boolean areAllItemsEnabled()
{
return (false);
}
/**
* Returns true if the item at the specified position is
* not a separator.
*
* @param position Position of the item whose data we want
*/
@Override
public boolean isEnabled(int position)
{
return (false);
}
/**
* Get a View that displays the data at the specified
* position in the data set.
*
* @param position Position of the item whose data we want
* @param convertView View to recycle, if not null
* @param parent ViewGroup containing the returned View
*/
@Override
public View getView(int position, View convertView, ViewGroup parent)
{
View result = views.get(position);
if (result == null)
{
result = newView(position, parent);
views.set(position, result);
}
return (result);
}
/**
* Get the row id associated with the specified position
* in the list.
*
* @param position Position of the item whose data we want
*/
@Override
public long getItemId(int position)
{
return (position);
}
/**
* Create a new View to go into the list at the specified
* position.
*
* @param position Position of the item whose data we want
* @param parent ViewGroup containing the returned View
*/
protected static View newView(int position, ViewGroup parent)
{
throw new RuntimeException("You must override newView()!");
}
}

View File

@ -158,6 +158,12 @@ class NavigationActivity : AppCompatActivity() {
var showWelcomeScreen = Util.isFirstRun()
val areServersMigrated: Boolean = serverSettingsModel.migrateFromPreferences()
// Migrate Feature storage if needed
// TODO: Remove in December 2022
if (!Settings.hasKey(Constants.PREFERENCES_KEY_USE_FIVE_STAR_RATING)) {
Settings.migrateFeatureStorage()
}
// If there are any servers in the DB, do not show the welcome screen
showWelcomeScreen = showWelcomeScreen and !areServersMigrated

View File

@ -13,12 +13,9 @@ import androidx.lifecycle.MutableLiveData
import androidx.recyclerview.widget.RecyclerView
import io.reactivex.rxjava3.disposables.Disposable
import org.koin.core.component.KoinComponent
import org.koin.core.component.get
import org.moire.ultrasonic.R
import org.moire.ultrasonic.data.ActiveServerProvider
import org.moire.ultrasonic.domain.MusicDirectory
import org.moire.ultrasonic.featureflags.Feature
import org.moire.ultrasonic.featureflags.FeatureStorage
import org.moire.ultrasonic.service.DownloadFile
import org.moire.ultrasonic.service.DownloadStatus
import org.moire.ultrasonic.service.MusicServiceFactory
@ -61,11 +58,6 @@ class TrackViewHolder(val view: View) : RecyclerView.ViewHolder(view), Checkable
var observableChecked = MutableLiveData(false)
private val useFiveStarRating: Boolean by lazy {
val features: FeatureStorage = get()
features.isFeatureEnabled(Feature.FIVE_STAR_RATING)
}
lateinit var imageHelper: Utils.ImageHelper
fun setSong(
@ -74,6 +66,7 @@ class TrackViewHolder(val view: View) : RecyclerView.ViewHolder(view), Checkable
draggable: Boolean,
isSelected: Boolean = false
) {
val useFiveStarRating = Settings.useFiveStarRating
val song = file.song
downloadFile = file
entry = song
@ -98,7 +91,7 @@ class TrackViewHolder(val view: View) : RecyclerView.ViewHolder(view), Checkable
star.isVisible = false
rating.isVisible = false
} else {
setupStarButtons(song)
setupStarButtons(song, useFiveStarRating)
}
updateProgress(downloadFile!!.progress.value!!)
@ -138,7 +131,7 @@ class TrackViewHolder(val view: View) : RecyclerView.ViewHolder(view), Checkable
}
}
private fun setupStarButtons(song: MusicDirectory.Entry) {
private fun setupStarButtons(song: MusicDirectory.Entry, useFiveStarRating: Boolean) {
if (useFiveStarRating) {
// Hide single star
star.isVisible = false

View File

@ -9,7 +9,6 @@ import org.moire.ultrasonic.BuildConfig
import org.moire.ultrasonic.di.appPermanentStorage
import org.moire.ultrasonic.di.applicationModule
import org.moire.ultrasonic.di.baseNetworkModule
import org.moire.ultrasonic.di.featureFlagsModule
import org.moire.ultrasonic.di.mediaPlayerModule
import org.moire.ultrasonic.di.musicServiceModule
import org.moire.ultrasonic.log.FileLoggerTree
@ -47,7 +46,6 @@ class UApp : MultiDexApplication() {
applicationModule,
appPermanentStorage,
baseNetworkModule,
featureFlagsModule,
musicServiceModule,
mediaPlayerModule
)

View File

@ -1,12 +0,0 @@
package org.moire.ultrasonic.di
import org.koin.android.ext.koin.androidContext
import org.koin.dsl.module
import org.moire.ultrasonic.featureflags.FeatureStorage
/**
* This Koin module contains the registration for the Feature Flags
*/
val featureFlagsModule = module {
factory { FeatureStorage(androidContext()) }
}

View File

@ -1,18 +0,0 @@
package org.moire.ultrasonic.featureflags
/**
* Contains a list of new features/implementations that are not yet finished,
* but possible to try it out.
*/
enum class Feature(
val defaultValue: Boolean
) {
/**
* Enables new image downloader implementation.
*/
NEW_IMAGE_DOWNLOADER(false),
/**
* Enables five star rating system.
*/
FIVE_STAR_RATING(false)
}

View File

@ -1,31 +0,0 @@
package org.moire.ultrasonic.featureflags
import android.content.Context
private const val SP_NAME = "feature_flags"
/**
* Provides storage for current feature flag state.
*/
class FeatureStorage(
context: Context
) {
private val sp = context.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE)
/**
* Get [feature] current enabled state.
*/
fun isFeatureEnabled(feature: Feature): Boolean {
return sp.getBoolean(feature.name, feature.defaultValue)
}
/**
* Update [feature] enabled state to [isEnabled].
*/
fun changeFeatureFlag(
feature: Feature,
isEnabled: Boolean
) {
sp.edit().putBoolean(feature.name, isEnabled).apply()
}
}

View File

@ -4,16 +4,15 @@ import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.AdapterView
import android.widget.AdapterView.OnItemClickListener
import android.widget.ListView
import android.widget.LinearLayout
import android.widget.TextView
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.navigation.Navigation
import org.koin.core.component.KoinComponent
import org.moire.ultrasonic.R
import org.moire.ultrasonic.data.ActiveServerProvider.Companion.isOffline
import org.moire.ultrasonic.util.Constants
import org.moire.ultrasonic.util.MergeAdapter
import org.moire.ultrasonic.util.Settings
import org.moire.ultrasonic.util.Util
@ -21,26 +20,26 @@ import org.moire.ultrasonic.util.Util
* Displays the Main screen of Ultrasonic, where the music library can be browsed
*/
class MainFragment : Fragment(), KoinComponent {
private var list: ListView? = null
private lateinit var musicTitle: View
private lateinit var artistsButton: View
private lateinit var albumsButton: View
private lateinit var genresButton: View
private lateinit var videosTitle: View
private lateinit var songsTitle: View
private lateinit var randomSongsButton: View
private lateinit var songsStarredButton: View
private lateinit var albumsTitle: View
private lateinit var albumsNewestButton: View
private lateinit var albumsRandomButton: View
private lateinit var albumsHighestButton: View
private lateinit var albumsStarredButton: View
private lateinit var albumsRecentButton: View
private lateinit var albumsFrequentButton: View
private lateinit var albumsAlphaByNameButton: View
private lateinit var albumsAlphaByArtistButton: View
private lateinit var videosButton: View
private lateinit var list: LinearLayout
private lateinit var musicTitle: TextView
private lateinit var artistsButton: TextView
private lateinit var albumsButton: TextView
private lateinit var genresButton: TextView
private lateinit var videosTitle: TextView
private lateinit var songsTitle: TextView
private lateinit var randomSongsButton: TextView
private lateinit var songsStarredButton: TextView
private lateinit var albumsTitle: TextView
private lateinit var albumsNewestButton: TextView
private lateinit var albumsRandomButton: TextView
private lateinit var albumsHighestButton: TextView
private lateinit var albumsStarredButton: TextView
private lateinit var albumsRecentButton: TextView
private lateinit var albumsFrequentButton: TextView
private lateinit var albumsAlphaByNameButton: TextView
private lateinit var albumsAlphaByArtistButton: TextView
private lateinit var videosButton: TextView
override fun onCreate(savedInstanceState: Bundle?) {
Util.applyTheme(this.context)
@ -59,8 +58,8 @@ class MainFragment : Fragment(), KoinComponent {
list = view.findViewById(R.id.main_list)
setupButtons()
if (list != null) setupMenuList(list!!)
setupClickListener()
setupItemVisibility()
super.onViewCreated(view, savedInstanceState)
}
@ -71,134 +70,128 @@ class MainFragment : Fragment(), KoinComponent {
val currentId3Setting = Settings.shouldUseId3Tags
// If setting has changed...
if (currentId3Setting != shouldUseId3) {
shouldUseId3 = currentId3Setting
if (currentId3Setting != cachedId3Setting) {
cachedId3Setting = currentId3Setting
shouldRestart = true
}
// then setup the list anew.
if (shouldRestart) {
if (list != null) setupMenuList(list!!)
setupItemVisibility()
}
}
private fun setupButtons() {
val buttons = layoutInflater.inflate(R.layout.main_buttons, list, false)
musicTitle = buttons.findViewById(R.id.main_music)
artistsButton = buttons.findViewById(R.id.main_artists_button)
albumsButton = buttons.findViewById(R.id.main_albums_button)
genresButton = buttons.findViewById(R.id.main_genres_button)
videosTitle = buttons.findViewById(R.id.main_videos_title)
songsTitle = buttons.findViewById(R.id.main_songs)
randomSongsButton = buttons.findViewById(R.id.main_songs_button)
songsStarredButton = buttons.findViewById(R.id.main_songs_starred)
albumsTitle = buttons.findViewById(R.id.main_albums)
albumsNewestButton = buttons.findViewById(R.id.main_albums_newest)
albumsRandomButton = buttons.findViewById(R.id.main_albums_random)
albumsHighestButton = buttons.findViewById(R.id.main_albums_highest)
albumsStarredButton = buttons.findViewById(R.id.main_albums_starred)
albumsRecentButton = buttons.findViewById(R.id.main_albums_recent)
albumsFrequentButton = buttons.findViewById(R.id.main_albums_frequent)
albumsAlphaByNameButton = buttons.findViewById(R.id.main_albums_alphaByName)
albumsAlphaByArtistButton = buttons.findViewById(R.id.main_albums_alphaByArtist)
videosButton = buttons.findViewById(R.id.main_videos)
musicTitle = list.findViewById(R.id.main_music)
artistsButton = list.findViewById(R.id.main_artists_button)
albumsButton = list.findViewById(R.id.main_albums_button)
genresButton = list.findViewById(R.id.main_genres_button)
videosTitle = list.findViewById(R.id.main_videos_title)
songsTitle = list.findViewById(R.id.main_songs)
randomSongsButton = list.findViewById(R.id.main_songs_button)
songsStarredButton = list.findViewById(R.id.main_songs_starred)
albumsTitle = list.findViewById(R.id.main_albums)
albumsNewestButton = list.findViewById(R.id.main_albums_newest)
albumsRandomButton = list.findViewById(R.id.main_albums_random)
albumsHighestButton = list.findViewById(R.id.main_albums_highest)
albumsStarredButton = list.findViewById(R.id.main_albums_starred)
albumsRecentButton = list.findViewById(R.id.main_albums_recent)
albumsFrequentButton = list.findViewById(R.id.main_albums_frequent)
albumsAlphaByNameButton = list.findViewById(R.id.main_albums_alphaByName)
albumsAlphaByArtistButton = list.findViewById(R.id.main_albums_alphaByArtist)
videosButton = list.findViewById(R.id.main_videos)
}
private fun setupMenuList(list: ListView) {
private fun setupItemVisibility() {
// Cache some values
cachedId3Setting = Settings.shouldUseId3Tags
val isOnline = !isOffline()
// TODO: Should use RecyclerView
val adapter = MergeAdapter()
// Music
musicTitle.isVisible = true
artistsButton.isVisible = true
albumsButton.isVisible = isOnline
genresButton.isVisible = true
shouldUseId3 = Settings.shouldUseId3Tags
// Songs
songsTitle.isVisible = isOnline
randomSongsButton.isVisible = true
songsStarredButton.isVisible = isOnline
if (!isOffline()) {
adapter.addView(musicTitle, false)
adapter.addViews(listOf(artistsButton, albumsButton, genresButton), true)
adapter.addView(songsTitle, false)
adapter.addViews(listOf(randomSongsButton, songsStarredButton), true)
adapter.addView(albumsTitle, false)
adapter.addViews(
listOf(
albumsNewestButton,
albumsRecentButton,
albumsFrequentButton
),
true
)
if (!shouldUseId3) {
adapter.addView(albumsHighestButton, true)
}
adapter.addViews(
listOf(
albumsRandomButton,
albumsStarredButton,
albumsAlphaByNameButton,
albumsAlphaByArtistButton
),
true
)
adapter.addView(videosTitle, false)
adapter.addViews(listOf(videosButton), true)
} else {
// Offline supported calls
adapter.addView(musicTitle, false)
adapter.addViews(listOf(artistsButton, genresButton), true)
adapter.addView(songsTitle, false)
adapter.addView(randomSongsButton, true)
}
// Albums
albumsTitle.isVisible = isOnline
albumsNewestButton.isVisible = isOnline
albumsRecentButton.isVisible = isOnline
albumsFrequentButton.isVisible = isOnline
albumsHighestButton.isVisible = isOnline && !cachedId3Setting
albumsRandomButton.isVisible = isOnline
albumsStarredButton.isVisible = isOnline
albumsAlphaByNameButton.isVisible = isOnline
albumsAlphaByArtistButton.isVisible = isOnline
list.adapter = adapter
list.onItemClickListener = listListener
// Videos
videosTitle.isVisible = isOnline
videosButton.isVisible = isOnline
}
private val listListener =
OnItemClickListener { _: AdapterView<*>?, view: View, _: Int, _: Long ->
when {
view === albumsNewestButton -> {
showAlbumList("newest", R.string.main_albums_newest)
}
view === albumsRandomButton -> {
showAlbumList("random", R.string.main_albums_random)
}
view === albumsHighestButton -> {
showAlbumList("highest", R.string.main_albums_highest)
}
view === albumsRecentButton -> {
showAlbumList("recent", R.string.main_albums_recent)
}
view === albumsFrequentButton -> {
showAlbumList("frequent", R.string.main_albums_frequent)
}
view === albumsStarredButton -> {
showAlbumList(Constants.STARRED, R.string.main_albums_starred)
}
view === albumsAlphaByNameButton -> {
showAlbumList(Constants.ALPHABETICAL_BY_NAME, R.string.main_albums_alphaByName)
}
view === albumsAlphaByArtistButton -> {
showAlbumList("alphabeticalByArtist", R.string.main_albums_alphaByArtist)
}
view === songsStarredButton -> {
showStarredSongs()
}
view === artistsButton -> {
showArtists()
}
view === albumsButton -> {
showAlbumList(Constants.ALPHABETICAL_BY_NAME, R.string.main_albums_title)
}
view === randomSongsButton -> {
showRandomSongs()
}
view === genresButton -> {
showGenres()
}
view === videosButton -> {
showVideos()
}
}
private fun setupClickListener() {
albumsNewestButton.setOnClickListener {
showAlbumList("newest", R.string.main_albums_newest)
}
albumsRandomButton.setOnClickListener {
showAlbumList("random", R.string.main_albums_random)
}
albumsHighestButton.setOnClickListener {
showAlbumList("highest", R.string.main_albums_highest)
}
albumsRecentButton.setOnClickListener {
showAlbumList("recent", R.string.main_albums_recent)
}
albumsFrequentButton.setOnClickListener {
showAlbumList("frequent", R.string.main_albums_frequent)
}
albumsStarredButton.setOnClickListener {
showAlbumList(Constants.STARRED, R.string.main_albums_starred)
}
albumsAlphaByNameButton.setOnClickListener {
showAlbumList(Constants.ALPHABETICAL_BY_NAME, R.string.main_albums_alphaByName)
}
albumsAlphaByArtistButton.setOnClickListener {
showAlbumList("alphabeticalByArtist", R.string.main_albums_alphaByArtist)
}
songsStarredButton.setOnClickListener {
showStarredSongs()
}
artistsButton.setOnClickListener {
showArtists()
}
albumsButton.setOnClickListener {
showAlbumList(Constants.ALPHABETICAL_BY_NAME, R.string.main_albums_title)
}
randomSongsButton.setOnClickListener {
showRandomSongs()
}
genresButton.setOnClickListener {
showGenres()
}
videosButton.setOnClickListener {
showVideos()
}
}
private fun showStarredSongs() {
val bundle = Bundle()
bundle.putInt(Constants.INTENT_STARRED, 1)
@ -242,6 +235,6 @@ class MainFragment : Fragment(), KoinComponent {
}
companion object {
private var shouldUseId3 = false
private var cachedId3Setting = false
}
}

View File

@ -70,8 +70,6 @@ import org.moire.ultrasonic.domain.Identifiable
import org.moire.ultrasonic.domain.MusicDirectory
import org.moire.ultrasonic.domain.PlayerState
import org.moire.ultrasonic.domain.RepeatMode
import org.moire.ultrasonic.featureflags.Feature
import org.moire.ultrasonic.featureflags.FeatureStorage
import org.moire.ultrasonic.fragment.FragmentTitle.Companion.setTitle
import org.moire.ultrasonic.service.DownloadFile
import org.moire.ultrasonic.service.LocalMediaPlayer
@ -210,7 +208,7 @@ class PlayerFragment :
val width = size.x
val height = size.y
setHasOptionsMenu(true)
useFiveStarRating = get<FeatureStorage>().isFeatureEnabled(Feature.FIVE_STAR_RATING)
useFiveStarRating = Settings.useFiveStarRating
swipeDistance = (width + height) * PERCENTAGE_OF_SCREEN_FOR_SWIPE / 100
swipeVelocity = swipeDistance
gestureScanner = GestureDetector(context, this)

View File

@ -23,12 +23,9 @@ 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.app.UApp
import org.moire.ultrasonic.featureflags.Feature
import org.moire.ultrasonic.featureflags.FeatureStorage
import org.moire.ultrasonic.fragment.FragmentTitle.Companion.setTitle
import org.moire.ultrasonic.log.FileLoggerTree
import org.moire.ultrasonic.log.FileLoggerTree.Companion.deleteLogFiles
@ -144,7 +141,6 @@ class SettingsFragment :
sharingDefaultGreeting!!.text = shareGreeting
setupClearSearchPreference()
setupFeatureFlagsPreferences()
setupCacheLocationPreference()
setupBluetoothDevicePreferences()
@ -377,21 +373,6 @@ class SettingsFragment :
}
}
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

View File

@ -8,15 +8,12 @@ package org.moire.ultrasonic.service
import android.content.Intent
import org.koin.core.component.KoinComponent
import org.koin.core.component.get
import org.koin.core.component.inject
import org.moire.ultrasonic.app.UApp
import org.moire.ultrasonic.data.ActiveServerProvider
import org.moire.ultrasonic.domain.MusicDirectory
import org.moire.ultrasonic.domain.PlayerState
import org.moire.ultrasonic.domain.RepeatMode
import org.moire.ultrasonic.featureflags.Feature
import org.moire.ultrasonic.featureflags.FeatureStorage
import org.moire.ultrasonic.service.MediaPlayerService.Companion.executeOnStartedMediaPlayerService
import org.moire.ultrasonic.service.MediaPlayerService.Companion.getInstance
import org.moire.ultrasonic.service.MediaPlayerService.Companion.runningInstance
@ -473,8 +470,7 @@ class MediaPlayerController(
@Suppress("TooGenericExceptionCaught") // The interface throws only generic exceptions
fun setSongRating(rating: Int) {
val features: FeatureStorage = get()
if (!features.isFeatureEnabled(Feature.FIVE_STAR_RATING)) return
if (!Settings.useFiveStarRating) return
if (localMediaPlayer.currentPlaying == null) return
val song = localMediaPlayer.currentPlaying!!.song
song.userRating = rating

View File

@ -297,11 +297,6 @@ object Settings {
return 0
}
var shouldShowAllSongsByArtist by BooleanSetting(
Constants.PREFERENCES_KEY_SHOW_ALL_SONGS_BY_ARTIST,
false
)
@JvmStatic
var resumeOnBluetoothDevice by IntSetting(
Constants.PREFERENCES_KEY_RESUME_ON_BLUETOOTH_DEVICE,
@ -320,6 +315,18 @@ object Settings {
val preferences: SharedPreferences
get() = PreferenceManager.getDefaultSharedPreferences(Util.appContext())
var useFiveStarRating by BooleanSetting(Constants.PREFERENCES_KEY_USE_FIVE_STAR_RATING, false)
// TODO: Remove in December 2022
fun migrateFeatureStorage() {
val sp = appContext.getSharedPreferences("feature_flags", Context.MODE_PRIVATE)
useFiveStarRating = sp.getBoolean("FIVE_STAR_RATING", false)
}
fun hasKey(key: String): Boolean {
return preferences.contains(key)
}
private val appContext: Context
get() {
return UApp.applicationContext()

View File

@ -1,18 +1,211 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:a="http://schemas.android.com/apk/res/android"
a:id="@+id/main_list"
a:layout_width="fill_parent"
a:layout_height="fill_parent"
a:orientation="vertical" >
a:orientation="vertical">
<ListView
a:id="@+id/main_list"
<TextView
a:id="@+id/main_music"
a:layout_width="fill_parent"
a:layout_height="0dp"
a:layout_weight="1" />
a:layout_height="wrap_content"
a:gravity="center_vertical"
a:paddingStart="6dp"
a:text="@string/main.music"
a:textAllCaps="true"
a:textAppearance="?android:attr/textAppearanceSmall"
a:textColor="@color/cyan"
a:textStyle="bold" />
<View
a:id="@+id/main_dummy"
a:layout_width="0dp"
a:layout_height="0dp" />
<TextView
a:id="@+id/main_artists_button"
a:layout_width="fill_parent"
a:layout_height="wrap_content"
a:gravity="center_vertical"
a:minHeight="40dip"
a:paddingStart="6dip"
a:paddingEnd="6dip"
a:text="@string/main.artists_title"
a:textAppearance="?android:attr/textAppearanceMedium" />
<TextView
a:id="@+id/main_albums_button"
a:layout_width="fill_parent"
a:layout_height="wrap_content"
a:gravity="center_vertical"
a:minHeight="40dip"
a:paddingStart="6dip"
a:paddingEnd="6dip"
a:text="@string/main.albums_title"
a:textAppearance="?android:attr/textAppearanceMedium" />
<TextView
a:id="@+id/main_genres_button"
a:layout_width="fill_parent"
a:layout_height="wrap_content"
a:gravity="center_vertical"
a:minHeight="40dip"
a:paddingStart="6dip"
a:paddingEnd="6dip"
a:text="@string/main.genres_title"
a:textAppearance="?android:attr/textAppearanceMedium" />
<TextView
a:id="@+id/main_songs"
a:layout_width="fill_parent"
a:layout_height="wrap_content"
a:gravity="center_vertical"
a:paddingStart="6dp"
a:text="@string/main.songs_title"
a:textAllCaps="true"
a:textAppearance="?android:attr/textAppearanceSmall"
a:textColor="@color/cyan"
a:textStyle="bold" />
<TextView
a:id="@+id/main_songs_button"
a:layout_width="fill_parent"
a:layout_height="wrap_content"
a:gravity="center_vertical"
a:minHeight="40dip"
a:paddingStart="6dip"
a:paddingEnd="6dip"
a:text="@string/main.songs_random"
a:textAppearance="?android:attr/textAppearanceMedium" />
<TextView
a:id="@+id/main_songs_starred"
a:layout_width="fill_parent"
a:layout_height="wrap_content"
a:gravity="center_vertical"
a:minHeight="40dip"
a:paddingStart="6dip"
a:paddingEnd="6dip"
a:text="@string/main.songs_starred"
a:textAppearance="?android:attr/textAppearanceMedium" />
<TextView
a:id="@+id/main_albums"
a:layout_width="fill_parent"
a:layout_height="wrap_content"
a:gravity="center_vertical"
a:paddingStart="6dp"
a:text="@string/main.albums_title"
a:textAllCaps="true"
a:textAppearance="?android:attr/textAppearanceSmall"
a:textColor="@color/cyan"
a:textStyle="bold" />
<TextView
a:id="@+id/main_albums_newest"
a:layout_width="fill_parent"
a:layout_height="wrap_content"
a:gravity="center_vertical"
a:minHeight="40dip"
a:paddingStart="6dip"
a:paddingEnd="6dip"
a:text="@string/main.albums_newest"
a:textAppearance="?android:attr/textAppearanceMedium" />
<TextView
a:id="@+id/main_albums_recent"
a:layout_width="fill_parent"
a:layout_height="wrap_content"
a:gravity="center_vertical"
a:minHeight="40dip"
a:paddingStart="6dip"
a:paddingEnd="6dip"
a:text="@string/main.albums_recent"
a:textAppearance="?android:attr/textAppearanceMedium" />
<TextView
a:id="@+id/main_albums_frequent"
a:layout_width="fill_parent"
a:layout_height="wrap_content"
a:gravity="center_vertical"
a:minHeight="40dip"
a:paddingStart="6dip"
a:paddingEnd="6dip"
a:text="@string/main.albums_frequent"
a:textAppearance="?android:attr/textAppearanceMedium" />
<TextView
a:id="@+id/main_albums_highest"
a:layout_width="fill_parent"
a:layout_height="wrap_content"
a:gravity="center_vertical"
a:minHeight="40dip"
a:paddingStart="6dip"
a:paddingEnd="6dip"
a:text="@string/main.albums_highest"
a:textAppearance="?android:attr/textAppearanceMedium" />
<TextView
a:id="@+id/main_albums_random"
a:layout_width="fill_parent"
a:layout_height="wrap_content"
a:gravity="center_vertical"
a:minHeight="40dip"
a:paddingStart="6dip"
a:paddingEnd="6dip"
a:text="@string/main.albums_random"
a:textAppearance="?android:attr/textAppearanceMedium" />
<TextView
a:id="@+id/main_albums_starred"
a:layout_width="fill_parent"
a:layout_height="wrap_content"
a:gravity="center_vertical"
a:minHeight="40dip"
a:paddingStart="6dip"
a:paddingEnd="6dip"
a:text="@string/main.albums_starred"
a:textAppearance="?android:attr/textAppearanceMedium" />
<TextView
a:id="@+id/main_albums_alphaByName"
a:layout_width="fill_parent"
a:layout_height="wrap_content"
a:gravity="center_vertical"
a:minHeight="40dip"
a:paddingStart="6dip"
a:paddingEnd="6dip"
a:text="@string/main.albums_alphaByName"
a:textAppearance="?android:attr/textAppearanceMedium" />
<TextView
a:id="@+id/main_albums_alphaByArtist"
a:layout_width="fill_parent"
a:layout_height="wrap_content"
a:gravity="center_vertical"
a:minHeight="40dip"
a:paddingStart="6dip"
a:paddingEnd="6dip"
a:text="@string/main.albums_alphaByArtist"
a:textAppearance="?android:attr/textAppearanceMedium" />
<TextView
a:id="@+id/main_videos_title"
a:layout_width="fill_parent"
a:layout_height="wrap_content"
a:gravity="center_vertical"
a:paddingStart="6dp"
a:text="@string/main.videos"
a:textAllCaps="true"
a:textAppearance="?android:attr/textAppearanceSmall"
a:textColor="@color/cyan"
a:textStyle="bold" />
<TextView
a:id="@+id/main_videos"
a:layout_width="fill_parent"
a:layout_height="wrap_content"
a:gravity="center_vertical"
a:minHeight="40dip"
a:paddingStart="6dip"
a:paddingEnd="6dip"
a:text="@string/main.videos"
a:textAppearance="?android:attr/textAppearanceMedium" />
</LinearLayout>
</LinearLayout>

View File

@ -1,100 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:a="http://schemas.android.com/apk/res/android"
a:orientation="vertical" a:layout_width="fill_parent" a:layout_height="fill_parent">
<TextView a:id="@+id/main_music" a:text="@string/main.music"
a:layout_width="fill_parent" a:layout_height="wrap_content"
a:textAppearance="?android:attr/textAppearanceSmall" a:textColor="@color/cyan"
a:gravity="center_vertical" a:paddingStart="6dp" a:textAllCaps="true"
a:textStyle="bold" />
<TextView a:id="@+id/main_artists_button" a:text="@string/main.artists_title"
a:layout_width="fill_parent" a:layout_height="wrap_content"
a:textAppearance="?android:attr/textAppearanceMedium" a:gravity="center_vertical"
a:paddingStart="6dip" a:paddingEnd="6dip" a:minHeight="40dip" />
<TextView a:id="@+id/main_albums_button" a:text="@string/main.albums_title"
a:layout_width="fill_parent" a:layout_height="wrap_content"
a:textAppearance="?android:attr/textAppearanceMedium" a:gravity="center_vertical"
a:paddingStart="6dip" a:paddingEnd="6dip" a:minHeight="40dip" />
<TextView a:id="@+id/main_genres_button" a:text="@string/main.genres_title"
a:layout_width="fill_parent" a:layout_height="wrap_content"
a:textAppearance="?android:attr/textAppearanceMedium" a:gravity="center_vertical"
a:paddingStart="6dip" a:paddingEnd="6dip" a:minHeight="40dip" />
<TextView a:id="@+id/main_songs" a:text="@string/main.songs_title"
a:layout_width="fill_parent" a:layout_height="wrap_content"
a:textAppearance="?android:attr/textAppearanceSmall" a:textColor="@color/cyan"
a:gravity="center_vertical" a:paddingStart="6dp" a:textAllCaps="true"
a:textStyle="bold" />
<TextView a:id="@+id/main_songs_button" a:text="@string/main.songs_random"
a:layout_width="fill_parent" a:layout_height="wrap_content"
a:textAppearance="?android:attr/textAppearanceMedium" a:gravity="center_vertical"
a:paddingStart="6dip" a:paddingEnd="6dip" a:minHeight="40dip" />
<TextView a:id="@+id/main_songs_starred" a:text="@string/main.songs_starred"
a:layout_width="fill_parent" a:layout_height="wrap_content"
a:textAppearance="?android:attr/textAppearanceMedium" a:gravity="center_vertical"
a:paddingStart="6dip" a:paddingEnd="6dip" a:minHeight="40dip" />
<TextView a:id="@+id/main_albums" a:text="@string/main.albums_title"
a:layout_width="fill_parent" a:layout_height="wrap_content"
a:textAppearance="?android:attr/textAppearanceSmall" a:textColor="@color/cyan"
a:gravity="center_vertical" a:paddingStart="6dp" a:textAllCaps="true"
a:textStyle="bold" />
<TextView a:id="@+id/main_albums_newest" a:text="@string/main.albums_newest"
a:layout_width="fill_parent" a:layout_height="wrap_content"
a:textAppearance="?android:attr/textAppearanceMedium" a:gravity="center_vertical"
a:paddingStart="6dip" a:paddingEnd="6dip" a:minHeight="40dip" />
<TextView a:id="@+id/main_albums_recent" a:text="@string/main.albums_recent"
a:layout_width="fill_parent" a:layout_height="wrap_content"
a:textAppearance="?android:attr/textAppearanceMedium" a:gravity="center_vertical"
a:paddingStart="6dip" a:paddingEnd="6dip" a:minHeight="40dip" />
<TextView a:id="@+id/main_albums_frequent" a:text="@string/main.albums_frequent"
a:layout_width="fill_parent" a:layout_height="wrap_content"
a:textAppearance="?android:attr/textAppearanceMedium" a:gravity="center_vertical"
a:paddingStart="6dip" a:paddingEnd="6dip" a:minHeight="40dip" />
<TextView a:id="@+id/main_albums_highest" a:text="@string/main.albums_highest"
a:layout_width="fill_parent" a:layout_height="wrap_content"
a:textAppearance="?android:attr/textAppearanceMedium" a:gravity="center_vertical"
a:paddingStart="6dip" a:paddingEnd="6dip" a:minHeight="40dip" />
<TextView a:id="@+id/main_albums_random" a:text="@string/main.albums_random"
a:layout_width="fill_parent" a:layout_height="wrap_content"
a:textAppearance="?android:attr/textAppearanceMedium" a:gravity="center_vertical"
a:paddingStart="6dip" a:paddingEnd="6dip" a:minHeight="40dip" />
<TextView a:id="@+id/main_albums_starred" a:layout_width="fill_parent"
a:layout_height="wrap_content" a:gravity="center_vertical"
a:minHeight="40dip" a:paddingStart="6dip" a:paddingEnd="6dip"
a:text="@string/main.albums_starred" a:textAppearance="?android:attr/textAppearanceMedium" />
<TextView a:id="@+id/main_albums_alphaByName" a:layout_width="fill_parent"
a:layout_height="wrap_content" a:gravity="center_vertical"
a:minHeight="40dip" a:paddingStart="6dip" a:paddingEnd="6dip"
a:text="@string/main.albums_alphaByName" a:textAppearance="?android:attr/textAppearanceMedium" />
<TextView a:id="@+id/main_albums_alphaByArtist" a:layout_width="fill_parent"
a:layout_height="wrap_content" a:gravity="center_vertical"
a:minHeight="40dip" a:paddingStart="6dip" a:paddingEnd="6dip"
a:text="@string/main.albums_alphaByArtist" a:textAppearance="?android:attr/textAppearanceMedium" />
<TextView a:id="@+id/main_videos_title" a:text="@string/main.videos"
a:layout_width="fill_parent" a:layout_height="wrap_content"
a:textAppearance="?android:attr/textAppearanceSmall" a:textColor="@color/cyan"
a:gravity="center_vertical" a:paddingStart="6dp" a:textAllCaps="true"
a:textStyle="bold" />
<TextView a:id="@+id/main_videos" a:layout_width="fill_parent"
a:layout_height="wrap_content" a:gravity="center_vertical"
a:minHeight="40dip" a:paddingStart="6dip" a:paddingEnd="6dip"
a:text="@string/main.videos" a:textAppearance="?android:attr/textAppearanceMedium" />
</LinearLayout>

View File

@ -428,10 +428,10 @@
<string name="api.subsonic.upgrade_client">Nekompatibilní verze. Aktualizujte prosím Ultrasonic Android aplikaci.</string>
<string name="api.subsonic.upgrade_server">Nekompatibilní verze. Aktualizujte prosím Subsonic server.</string>
<!-- Subsonic feature flags -->
<string name="feature_flags_category_title">Příznaky funkcí</string>
<string name="feature_flags_five_star_rating_title">Používat pět hvězdiček pro hodnocení skladeb</string>
<string name="feature_flags_five_star_rating_description">Používat pět hvězdiček pro hodnocení skladeb
<!-- Subsonic features -->
<string name="settings.features_title">Příznaky funkcí</string>
<string name="settings.five_star_rating_title">Používat pět hvězdiček pro hodnocení skladeb</string>
<string name="settings.five_star_rating_description">Používat pět hvězdiček pro hodnocení skladeb
namísto jednoduchého jednohvězdičkového hodnocení.
</string>

View File

@ -360,10 +360,10 @@
<string name="api.subsonic.upgrade_client">Inkompatible Versionen. Bitte die Ultrasonic App aktualisieren.</string>
<string name="api.subsonic.upgrade_server">Inkompatible Versionen. Bitte den subsonic Server aktualisieren.</string>
<!-- Subsonic feature flags -->
<string name="feature_flags_category_title">Funktionseinstellungem</string>
<string name="feature_flags_five_star_rating_title">Verwenden Sie Fünf-Sterne-Bewertung für Songs</string>
<string name="feature_flags_five_star_rating_description">Verwenden Sie Fünf-Sterne-Bewertungssystem für Songs
<!-- Subsonic features -->
<string name="settings.features_title">Funktionseinstellungem</string>
<string name="settings.five_star_rating_title">Verwenden Sie Fünf-Sterne-Bewertung für Songs</string>
<string name="settings.five_star_rating_description">Verwenden Sie Fünf-Sterne-Bewertungssystem für Songs
         anstatt einfach Elemente zu markieren / zu entfernen.
</string>

View File

@ -450,10 +450,10 @@
<string name="api.subsonic.upgrade_client">Versiones incompatibles. Por favor actualiza la aplicación de Android Ultrasonic.</string>
<string name="api.subsonic.upgrade_server">Versiones incompatibles. Por favor actualiza el servidor de Subsonic.</string>
<!-- Subsonic feature flags -->
<string name="feature_flags_category_title">Funciones experimentales</string>
<string name="feature_flags_five_star_rating_title">Use cinco estrellas para las canciones</string>
<string name="feature_flags_five_star_rating_description">Utilice el sistema de calificación de cinco estrellas para canciones
<!-- Subsonic features -->
<string name="settings.features_title">Funciones experimentales</string>
<string name="settings.five_star_rating_title">Use cinco estrellas para las canciones</string>
<string name="settings.five_star_rating_description">Utilice el sistema de calificación de cinco estrellas para canciones
en lugar de simplemente destacar / desestimar elementos.
</string>

View File

@ -439,10 +439,10 @@
<string name="api.subsonic.upgrade_client">Versions incompatibles. Veuillez mette à jour l\'application Android Ultrasonic.</string>
<string name="api.subsonic.upgrade_server">Versions incompatibles. Veuillez mette à jour le serveur Subsonic.</string>
<!-- Subsonic feature flags -->
<string name="feature_flags_category_title">Drapeaux des fonctionnalités</string>
<string name="feature_flags_five_star_rating_title">Utiliser les étoiles pour noter les morceaux</string>
<string name="feature_flags_five_star_rating_description">Utiliser un système de notation à base d\'étoiles pour les morceaux
<!-- Subsonic features -->
<string name="settings.features_title">Drapeaux des fonctionnalités</string>
<string name="settings.five_star_rating_title">Utiliser les étoiles pour noter les morceaux</string>
<string name="settings.five_star_rating_description">Utiliser un système de notation à base d\'étoiles pour les morceaux
au lieu de simplement mettre en avant les morceaux.
</string>

View File

@ -434,10 +434,10 @@
<string name="api.subsonic.upgrade_client">Nem kompatibilis verzió. Kérjük, frissítse az Ultrasonic Android alkalmazást!</string>
<string name="api.subsonic.upgrade_server">Nem kompatibilis verzió. Kérjük, frissítse a Subsonic kiszolgálót!</string>
<!-- Subsonic feature flags -->
<string name="feature_flags_category_title">Jellemzők Zászlók</string>
<string name="feature_flags_five_star_rating_title">Öt csillagos értékelés használata a dalokhoz</string>
<string name="feature_flags_five_star_rating_description">Öt csillag használata az értékeléshez az egyszerű
<!-- Subsonic features -->
<string name="settings.features_title">Jellemzők Zászlók</string>
<string name="settings.five_star_rating_title">Öt csillagos értékelés használata a dalokhoz</string>
<string name="settings.five_star_rating_description">Öt csillag használata az értékeléshez az egyszerű
csillaggal jelölés helyett.
</string>

View File

@ -450,10 +450,10 @@
<string name="api.subsonic.upgrade_client">Incompatibele versies. Werk de Ultrasonic Android-app bij.</string>
<string name="api.subsonic.upgrade_server">Incompatibele versies. Werk je Subsonic-server bij.</string>
<!-- Subsonic feature flags -->
<string name="feature_flags_category_title">Experimentele functies</string>
<string name="feature_flags_five_star_rating_title">Gebruik vijf sterren voor nummers</string>
<string name="feature_flags_five_star_rating_description">Gebruik vijf sterren ratingsysteem voor liedjes
<!-- Subsonic features -->
<string name="settings.features_title">Experimentele functies</string>
<string name="settings.five_star_rating_title">Gebruik vijf sterren voor nummers</string>
<string name="settings.five_star_rating_description">Gebruik vijf sterren ratingsysteem voor liedjes
in plaats van items simpelweg in de hoofdrol te zetten / niet te verwijderen.
</string>

View File

@ -404,10 +404,10 @@ ponieważ api Subsonic nie wspiera nowego sposobu autoryzacji dla użytkowników
<string name="api.subsonic.upgrade_client">Brak zgodności wersji. Uaktualnij aplikację Ultrasonic na Androida.</string>
<string name="api.subsonic.upgrade_server">Brak zgodności wersji. Uaktualnij serwer Subsonic.</string>
<!-- Subsonic feature flags -->
<string name="feature_flags_category_title">Flagi funkcji</string>
<string name="feature_flags_five_star_rating_title">Użyj pięciu gwiazdek dla utworów</string>
<string name="feature_flags_five_star_rating_description">W przypadku utworów użyj systemu pięciu gwiazdek
<!-- Subsonic features -->
<string name="settings.features_title">Flagi funkcji</string>
<string name="settings.five_star_rating_title">Użyj pięciu gwiazdek dla utworów</string>
<string name="settings.five_star_rating_description">W przypadku utworów użyj systemu pięciu gwiazdek
zamiast po prostu grać gwiazdkami / bez gwiazd.
</string>

View File

@ -443,10 +443,10 @@
<string name="api.subsonic.upgrade_client">Versões incompativeis. Atualize o aplicativo Ultrasonic para Android.</string>
<string name="api.subsonic.upgrade_server">Versões incompativeis. Atualize o servidor Ultrasonic.</string>
<!-- Subsonic feature flags -->
<string name="feature_flags_category_title">Sinalização de Recursos</string>
<string name="feature_flags_five_star_rating_title">Usar Classif. 5 Estrelas para Músicas</string>
<string name="feature_flags_five_star_rating_description">Usar o sistema de classificação de 5 estrelas para músicas
<!-- Subsonic features -->
<string name="settings.features_title">Sinalização de Recursos</string>
<string name="settings.five_star_rating_title">Usar Classif. 5 Estrelas para Músicas</string>
<string name="settings.five_star_rating_description">Usar o sistema de classificação de 5 estrelas para músicas
em vez de simplesmente estrelar/não estrelar itens
</string>

View File

@ -389,10 +389,10 @@
<string name="api.subsonic.upgrade_client">Versões incompativeis. Atualize o aplicativo Ultrasonic para Android.</string>
<string name="api.subsonic.upgrade_server">Versões incompativeis. Atualize o servidor Ultrasonic.</string>
<!-- Subsonic feature flags -->
<string name="feature_flags_category_title">Bandeiras de recursos</string>
<string name="feature_flags_five_star_rating_title">Use classificação de cinco estrelas para músicas</string>
<string name="feature_flags_five_star_rating_description">Use o sistema de classificação de cinco estrelas para músicas
<!-- Subsonic features -->
<string name="settings.features_title">Bandeiras de recursos</string>
<string name="settings.five_star_rating_title">Use classificação de cinco estrelas para músicas</string>
<string name="settings.five_star_rating_description">Use o sistema de classificação de cinco estrelas para músicas
em vez de simplesmente estrelar / não estrelar itens.
</string>

View File

@ -453,10 +453,10 @@
<string name="api.subsonic.upgrade_client">Несовместимые версии. Пожалуйста, обновите приложение Ultrasonic для Android.</string>
<string name="api.subsonic.upgrade_server">Несовместимые версии. Пожалуйста, обновите Subsonic сервер.</string>
<!-- Subsonic feature flags -->
<string name="feature_flags_category_title">Флаги</string>
<string name="feature_flags_five_star_rating_title">Использовать пятизвездочный рейтинг для песен</string>
<string name="feature_flags_five_star_rating_description">Использовать пятизвездочную систему рейтинга для песен
<!-- Subsonic features -->
<string name="settings.features_title">Флаги</string>
<string name="settings.five_star_rating_title">Использовать пятизвездочный рейтинг для песен</string>
<string name="settings.five_star_rating_description">Использовать пятизвездочную систему рейтинга для песен
вместо того, чтобы просто ставить/не ставить звезды.</string>
</resources>

View File

@ -432,10 +432,10 @@
<string name="api.subsonic.upgrade_client">版本不兼容,请升级 Ultrasonic 应用。</string>
<string name="api.subsonic.upgrade_server">不兼容的版本。请升级Subsonic 服务。</string>
<!-- Subsonic feature flags -->
<string name="feature_flags_category_title">特性标志</string>
<string name="feature_flags_five_star_rating_title">为歌曲使用五星评分</string>
<string name="feature_flags_five_star_rating_description">对歌曲使用五星级评级系统
<!-- Subsonic features -->
<string name="settings.features_title">特性标志</string>
<string name="settings.five_star_rating_title">为歌曲使用五星评分</string>
<string name="settings.five_star_rating_description">对歌曲使用五星级评级系统
而不是简单地为项目加星标/取消星标。</string>
</resources>

View File

@ -461,10 +461,10 @@
<string name="api.subsonic.upgrade_client">Incompatible versions. Please upgrade Ultrasonic Android app.</string>
<string name="api.subsonic.upgrade_server">Incompatible versions. Please upgrade Subsonic server.</string>
<!-- Subsonic feature flags -->
<string name="feature_flags_category_title">Feature Flags</string>
<string name="feature_flags_five_star_rating_title">Use five star rating for songs</string>
<string name="feature_flags_five_star_rating_description">Use five star rating system for songs
<!-- Subsonic features -->
<string name="settings.features_title">Features</string>
<string name="settings.five_star_rating_title">Use five star rating for songs</string>
<string name="settings.five_star_rating_description">Use five star rating system for songs
instead of simply starring/unstarring items.
</string>

View File

@ -345,14 +345,14 @@
app:iconSpaceReserved="false"/>
</PreferenceCategory>
<PreferenceCategory
a:title="@string/feature_flags_category_title"
a:title="@string/settings.features_title"
app:iconSpaceReserved="false">
<CheckBoxPreference
a:defaultValue="false"
a:key="use_five_star_rating"
a:persistent="false"
a:title="@string/feature_flags_five_star_rating_title"
a:summary="@string/feature_flags_five_star_rating_description"
app:iconSpaceReserved="false"/>
a:summary="@string/settings.five_star_rating_description"
a:title="@string/settings.five_star_rating_title"
app:iconSpaceReserved="false" />
</PreferenceCategory>
<PreferenceCategory
a:title="@string/settings.debug.title"