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() var showWelcomeScreen = Util.isFirstRun()
val areServersMigrated: Boolean = serverSettingsModel.migrateFromPreferences() 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 // If there are any servers in the DB, do not show the welcome screen
showWelcomeScreen = showWelcomeScreen and !areServersMigrated showWelcomeScreen = showWelcomeScreen and !areServersMigrated

View File

@ -13,12 +13,9 @@ import androidx.lifecycle.MutableLiveData
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import io.reactivex.rxjava3.disposables.Disposable import io.reactivex.rxjava3.disposables.Disposable
import org.koin.core.component.KoinComponent import org.koin.core.component.KoinComponent
import org.koin.core.component.get
import org.moire.ultrasonic.R import org.moire.ultrasonic.R
import org.moire.ultrasonic.data.ActiveServerProvider import org.moire.ultrasonic.data.ActiveServerProvider
import org.moire.ultrasonic.domain.MusicDirectory 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.DownloadFile
import org.moire.ultrasonic.service.DownloadStatus import org.moire.ultrasonic.service.DownloadStatus
import org.moire.ultrasonic.service.MusicServiceFactory import org.moire.ultrasonic.service.MusicServiceFactory
@ -61,11 +58,6 @@ class TrackViewHolder(val view: View) : RecyclerView.ViewHolder(view), Checkable
var observableChecked = MutableLiveData(false) 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 lateinit var imageHelper: Utils.ImageHelper
fun setSong( fun setSong(
@ -74,6 +66,7 @@ class TrackViewHolder(val view: View) : RecyclerView.ViewHolder(view), Checkable
draggable: Boolean, draggable: Boolean,
isSelected: Boolean = false isSelected: Boolean = false
) { ) {
val useFiveStarRating = Settings.useFiveStarRating
val song = file.song val song = file.song
downloadFile = file downloadFile = file
entry = song entry = song
@ -98,7 +91,7 @@ class TrackViewHolder(val view: View) : RecyclerView.ViewHolder(view), Checkable
star.isVisible = false star.isVisible = false
rating.isVisible = false rating.isVisible = false
} else { } else {
setupStarButtons(song) setupStarButtons(song, useFiveStarRating)
} }
updateProgress(downloadFile!!.progress.value!!) 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) { if (useFiveStarRating) {
// Hide single star // Hide single star
star.isVisible = false 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.appPermanentStorage
import org.moire.ultrasonic.di.applicationModule import org.moire.ultrasonic.di.applicationModule
import org.moire.ultrasonic.di.baseNetworkModule import org.moire.ultrasonic.di.baseNetworkModule
import org.moire.ultrasonic.di.featureFlagsModule
import org.moire.ultrasonic.di.mediaPlayerModule import org.moire.ultrasonic.di.mediaPlayerModule
import org.moire.ultrasonic.di.musicServiceModule import org.moire.ultrasonic.di.musicServiceModule
import org.moire.ultrasonic.log.FileLoggerTree import org.moire.ultrasonic.log.FileLoggerTree
@ -47,7 +46,6 @@ class UApp : MultiDexApplication() {
applicationModule, applicationModule,
appPermanentStorage, appPermanentStorage,
baseNetworkModule, baseNetworkModule,
featureFlagsModule,
musicServiceModule, musicServiceModule,
mediaPlayerModule 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.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.AdapterView import android.widget.LinearLayout
import android.widget.AdapterView.OnItemClickListener import android.widget.TextView
import android.widget.ListView import androidx.core.view.isVisible
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.navigation.Navigation import androidx.navigation.Navigation
import org.koin.core.component.KoinComponent import org.koin.core.component.KoinComponent
import org.moire.ultrasonic.R import org.moire.ultrasonic.R
import org.moire.ultrasonic.data.ActiveServerProvider.Companion.isOffline import org.moire.ultrasonic.data.ActiveServerProvider.Companion.isOffline
import org.moire.ultrasonic.util.Constants import org.moire.ultrasonic.util.Constants
import org.moire.ultrasonic.util.MergeAdapter
import org.moire.ultrasonic.util.Settings import org.moire.ultrasonic.util.Settings
import org.moire.ultrasonic.util.Util 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 * Displays the Main screen of Ultrasonic, where the music library can be browsed
*/ */
class MainFragment : Fragment(), KoinComponent { class MainFragment : Fragment(), KoinComponent {
private var list: ListView? = null
private lateinit var musicTitle: View private lateinit var list: LinearLayout
private lateinit var artistsButton: View private lateinit var musicTitle: TextView
private lateinit var albumsButton: View private lateinit var artistsButton: TextView
private lateinit var genresButton: View private lateinit var albumsButton: TextView
private lateinit var videosTitle: View private lateinit var genresButton: TextView
private lateinit var songsTitle: View private lateinit var videosTitle: TextView
private lateinit var randomSongsButton: View private lateinit var songsTitle: TextView
private lateinit var songsStarredButton: View private lateinit var randomSongsButton: TextView
private lateinit var albumsTitle: View private lateinit var songsStarredButton: TextView
private lateinit var albumsNewestButton: View private lateinit var albumsTitle: TextView
private lateinit var albumsRandomButton: View private lateinit var albumsNewestButton: TextView
private lateinit var albumsHighestButton: View private lateinit var albumsRandomButton: TextView
private lateinit var albumsStarredButton: View private lateinit var albumsHighestButton: TextView
private lateinit var albumsRecentButton: View private lateinit var albumsStarredButton: TextView
private lateinit var albumsFrequentButton: View private lateinit var albumsRecentButton: TextView
private lateinit var albumsAlphaByNameButton: View private lateinit var albumsFrequentButton: TextView
private lateinit var albumsAlphaByArtistButton: View private lateinit var albumsAlphaByNameButton: TextView
private lateinit var videosButton: View private lateinit var albumsAlphaByArtistButton: TextView
private lateinit var videosButton: TextView
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
Util.applyTheme(this.context) Util.applyTheme(this.context)
@ -59,8 +58,8 @@ class MainFragment : Fragment(), KoinComponent {
list = view.findViewById(R.id.main_list) list = view.findViewById(R.id.main_list)
setupButtons() setupButtons()
setupClickListener()
if (list != null) setupMenuList(list!!) setupItemVisibility()
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
} }
@ -71,134 +70,128 @@ class MainFragment : Fragment(), KoinComponent {
val currentId3Setting = Settings.shouldUseId3Tags val currentId3Setting = Settings.shouldUseId3Tags
// If setting has changed... // If setting has changed...
if (currentId3Setting != shouldUseId3) { if (currentId3Setting != cachedId3Setting) {
shouldUseId3 = currentId3Setting cachedId3Setting = currentId3Setting
shouldRestart = true shouldRestart = true
} }
// then setup the list anew. // then setup the list anew.
if (shouldRestart) { if (shouldRestart) {
if (list != null) setupMenuList(list!!) setupItemVisibility()
} }
} }
private fun setupButtons() { private fun setupButtons() {
val buttons = layoutInflater.inflate(R.layout.main_buttons, list, false) musicTitle = list.findViewById(R.id.main_music)
musicTitle = buttons.findViewById(R.id.main_music) artistsButton = list.findViewById(R.id.main_artists_button)
artistsButton = buttons.findViewById(R.id.main_artists_button) albumsButton = list.findViewById(R.id.main_albums_button)
albumsButton = buttons.findViewById(R.id.main_albums_button) genresButton = list.findViewById(R.id.main_genres_button)
genresButton = buttons.findViewById(R.id.main_genres_button) videosTitle = list.findViewById(R.id.main_videos_title)
videosTitle = buttons.findViewById(R.id.main_videos_title) songsTitle = list.findViewById(R.id.main_songs)
songsTitle = buttons.findViewById(R.id.main_songs) randomSongsButton = list.findViewById(R.id.main_songs_button)
randomSongsButton = buttons.findViewById(R.id.main_songs_button) songsStarredButton = list.findViewById(R.id.main_songs_starred)
songsStarredButton = buttons.findViewById(R.id.main_songs_starred) albumsTitle = list.findViewById(R.id.main_albums)
albumsTitle = buttons.findViewById(R.id.main_albums) albumsNewestButton = list.findViewById(R.id.main_albums_newest)
albumsNewestButton = buttons.findViewById(R.id.main_albums_newest) albumsRandomButton = list.findViewById(R.id.main_albums_random)
albumsRandomButton = buttons.findViewById(R.id.main_albums_random) albumsHighestButton = list.findViewById(R.id.main_albums_highest)
albumsHighestButton = buttons.findViewById(R.id.main_albums_highest) albumsStarredButton = list.findViewById(R.id.main_albums_starred)
albumsStarredButton = buttons.findViewById(R.id.main_albums_starred) albumsRecentButton = list.findViewById(R.id.main_albums_recent)
albumsRecentButton = buttons.findViewById(R.id.main_albums_recent) albumsFrequentButton = list.findViewById(R.id.main_albums_frequent)
albumsFrequentButton = buttons.findViewById(R.id.main_albums_frequent) albumsAlphaByNameButton = list.findViewById(R.id.main_albums_alphaByName)
albumsAlphaByNameButton = buttons.findViewById(R.id.main_albums_alphaByName) albumsAlphaByArtistButton = list.findViewById(R.id.main_albums_alphaByArtist)
albumsAlphaByArtistButton = buttons.findViewById(R.id.main_albums_alphaByArtist) videosButton = list.findViewById(R.id.main_videos)
videosButton = buttons.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 // Music
val adapter = MergeAdapter() 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()) { // Albums
adapter.addView(musicTitle, false) albumsTitle.isVisible = isOnline
adapter.addViews(listOf(artistsButton, albumsButton, genresButton), true) albumsNewestButton.isVisible = isOnline
adapter.addView(songsTitle, false) albumsRecentButton.isVisible = isOnline
adapter.addViews(listOf(randomSongsButton, songsStarredButton), true) albumsFrequentButton.isVisible = isOnline
adapter.addView(albumsTitle, false) albumsHighestButton.isVisible = isOnline && !cachedId3Setting
adapter.addViews( albumsRandomButton.isVisible = isOnline
listOf( albumsStarredButton.isVisible = isOnline
albumsNewestButton, albumsAlphaByNameButton.isVisible = isOnline
albumsRecentButton, albumsAlphaByArtistButton.isVisible = isOnline
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)
}
list.adapter = adapter // Videos
list.onItemClickListener = listListener videosTitle.isVisible = isOnline
videosButton.isVisible = isOnline
} }
private val listListener = private fun setupClickListener() {
OnItemClickListener { _: AdapterView<*>?, view: View, _: Int, _: Long -> albumsNewestButton.setOnClickListener {
when { showAlbumList("newest", R.string.main_albums_newest)
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()
}
}
} }
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() { private fun showStarredSongs() {
val bundle = Bundle() val bundle = Bundle()
bundle.putInt(Constants.INTENT_STARRED, 1) bundle.putInt(Constants.INTENT_STARRED, 1)
@ -242,6 +235,6 @@ class MainFragment : Fragment(), KoinComponent {
} }
companion object { 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.MusicDirectory
import org.moire.ultrasonic.domain.PlayerState import org.moire.ultrasonic.domain.PlayerState
import org.moire.ultrasonic.domain.RepeatMode 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.fragment.FragmentTitle.Companion.setTitle
import org.moire.ultrasonic.service.DownloadFile import org.moire.ultrasonic.service.DownloadFile
import org.moire.ultrasonic.service.LocalMediaPlayer import org.moire.ultrasonic.service.LocalMediaPlayer
@ -210,7 +208,7 @@ class PlayerFragment :
val width = size.x val width = size.x
val height = size.y val height = size.y
setHasOptionsMenu(true) setHasOptionsMenu(true)
useFiveStarRating = get<FeatureStorage>().isFeatureEnabled(Feature.FIVE_STAR_RATING) useFiveStarRating = Settings.useFiveStarRating
swipeDistance = (width + height) * PERCENTAGE_OF_SCREEN_FOR_SWIPE / 100 swipeDistance = (width + height) * PERCENTAGE_OF_SCREEN_FOR_SWIPE / 100
swipeVelocity = swipeDistance swipeVelocity = swipeDistance
gestureScanner = GestureDetector(context, this) gestureScanner = GestureDetector(context, this)

View File

@ -23,12 +23,9 @@ import androidx.preference.PreferenceFragmentCompat
import java.io.File import java.io.File
import kotlin.math.ceil import kotlin.math.ceil
import org.koin.core.component.KoinComponent import org.koin.core.component.KoinComponent
import org.koin.java.KoinJavaComponent.get
import org.koin.java.KoinJavaComponent.inject import org.koin.java.KoinJavaComponent.inject
import org.moire.ultrasonic.R import org.moire.ultrasonic.R
import org.moire.ultrasonic.app.UApp 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.fragment.FragmentTitle.Companion.setTitle
import org.moire.ultrasonic.log.FileLoggerTree import org.moire.ultrasonic.log.FileLoggerTree
import org.moire.ultrasonic.log.FileLoggerTree.Companion.deleteLogFiles import org.moire.ultrasonic.log.FileLoggerTree.Companion.deleteLogFiles
@ -144,7 +141,6 @@ class SettingsFragment :
sharingDefaultGreeting!!.text = shareGreeting sharingDefaultGreeting!!.text = shareGreeting
setupClearSearchPreference() setupClearSearchPreference()
setupFeatureFlagsPreferences()
setupCacheLocationPreference() setupCacheLocationPreference()
setupBluetoothDevicePreferences() 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() { private fun update() {
theme!!.summary = theme!!.entry theme!!.summary = theme!!.entry
maxBitrateWifi!!.summary = maxBitrateWifi!!.entry maxBitrateWifi!!.summary = maxBitrateWifi!!.entry

View File

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

View File

@ -297,11 +297,6 @@ object Settings {
return 0 return 0
} }
var shouldShowAllSongsByArtist by BooleanSetting(
Constants.PREFERENCES_KEY_SHOW_ALL_SONGS_BY_ARTIST,
false
)
@JvmStatic @JvmStatic
var resumeOnBluetoothDevice by IntSetting( var resumeOnBluetoothDevice by IntSetting(
Constants.PREFERENCES_KEY_RESUME_ON_BLUETOOTH_DEVICE, Constants.PREFERENCES_KEY_RESUME_ON_BLUETOOTH_DEVICE,
@ -320,6 +315,18 @@ object Settings {
val preferences: SharedPreferences val preferences: SharedPreferences
get() = PreferenceManager.getDefaultSharedPreferences(Util.appContext()) 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 private val appContext: Context
get() { get() {
return UApp.applicationContext() return UApp.applicationContext()

View File

@ -1,18 +1,211 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:a="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:a="http://schemas.android.com/apk/res/android"
a:id="@+id/main_list"
a:layout_width="fill_parent" a:layout_width="fill_parent"
a:layout_height="fill_parent" a:layout_height="fill_parent"
a:orientation="vertical" > a:orientation="vertical">
<ListView <TextView
a:id="@+id/main_list" a:id="@+id/main_music"
a:layout_width="fill_parent" a:layout_width="fill_parent"
a:layout_height="0dp" a:layout_height="wrap_content"
a:layout_weight="1" /> 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 <TextView
a:id="@+id/main_dummy" a:id="@+id/main_artists_button"
a:layout_width="0dp" a:layout_width="fill_parent"
a:layout_height="0dp" /> 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_client">Nekompatibilní verze. Aktualizujte prosím Ultrasonic Android aplikaci.</string>
<string name="api.subsonic.upgrade_server">Nekompatibilní verze. Aktualizujte prosím Subsonic server.</string> <string name="api.subsonic.upgrade_server">Nekompatibilní verze. Aktualizujte prosím Subsonic server.</string>
<!-- Subsonic feature flags --> <!-- Subsonic features -->
<string name="feature_flags_category_title">Příznaky funkcí</string> <string name="settings.features_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="settings.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 <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í. namísto jednoduchého jednohvězdičkového hodnocení.
</string> </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_client">Inkompatible Versionen. Bitte die Ultrasonic App aktualisieren.</string>
<string name="api.subsonic.upgrade_server">Inkompatible Versionen. Bitte den subsonic Server aktualisieren.</string> <string name="api.subsonic.upgrade_server">Inkompatible Versionen. Bitte den subsonic Server aktualisieren.</string>
<!-- Subsonic feature flags --> <!-- Subsonic features -->
<string name="feature_flags_category_title">Funktionseinstellungem</string> <string name="settings.features_title">Funktionseinstellungem</string>
<string name="feature_flags_five_star_rating_title">Verwenden Sie Fünf-Sterne-Bewertung für Songs</string> <string name="settings.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 <string name="settings.five_star_rating_description">Verwenden Sie Fünf-Sterne-Bewertungssystem für Songs
         anstatt einfach Elemente zu markieren / zu entfernen.          anstatt einfach Elemente zu markieren / zu entfernen.
</string> </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_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> <string name="api.subsonic.upgrade_server">Versiones incompatibles. Por favor actualiza el servidor de Subsonic.</string>
<!-- Subsonic feature flags --> <!-- Subsonic features -->
<string name="feature_flags_category_title">Funciones experimentales</string> <string name="settings.features_title">Funciones experimentales</string>
<string name="feature_flags_five_star_rating_title">Use cinco estrellas para las canciones</string> <string name="settings.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 <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. en lugar de simplemente destacar / desestimar elementos.
</string> </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_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> <string name="api.subsonic.upgrade_server">Versions incompatibles. Veuillez mette à jour le serveur Subsonic.</string>
<!-- Subsonic feature flags --> <!-- Subsonic features -->
<string name="feature_flags_category_title">Drapeaux des fonctionnalités</string> <string name="settings.features_title">Drapeaux des fonctionnalités</string>
<string name="feature_flags_five_star_rating_title">Utiliser les étoiles pour noter les morceaux</string> <string name="settings.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 <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. au lieu de simplement mettre en avant les morceaux.
</string> </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_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> <string name="api.subsonic.upgrade_server">Nem kompatibilis verzió. Kérjük, frissítse a Subsonic kiszolgálót!</string>
<!-- Subsonic feature flags --> <!-- Subsonic features -->
<string name="feature_flags_category_title">Jellemzők Zászlók</string> <string name="settings.features_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="settings.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ű <string name="settings.five_star_rating_description">Öt csillag használata az értékeléshez az egyszerű
csillaggal jelölés helyett. csillaggal jelölés helyett.
</string> </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_client">Incompatibele versies. Werk de Ultrasonic Android-app bij.</string>
<string name="api.subsonic.upgrade_server">Incompatibele versies. Werk je Subsonic-server bij.</string> <string name="api.subsonic.upgrade_server">Incompatibele versies. Werk je Subsonic-server bij.</string>
<!-- Subsonic feature flags --> <!-- Subsonic features -->
<string name="feature_flags_category_title">Experimentele functies</string> <string name="settings.features_title">Experimentele functies</string>
<string name="feature_flags_five_star_rating_title">Gebruik vijf sterren voor nummers</string> <string name="settings.five_star_rating_title">Gebruik vijf sterren voor nummers</string>
<string name="feature_flags_five_star_rating_description">Gebruik vijf sterren ratingsysteem voor liedjes <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. in plaats van items simpelweg in de hoofdrol te zetten / niet te verwijderen.
</string> </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_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> <string name="api.subsonic.upgrade_server">Brak zgodności wersji. Uaktualnij serwer Subsonic.</string>
<!-- Subsonic feature flags --> <!-- Subsonic features -->
<string name="feature_flags_category_title">Flagi funkcji</string> <string name="settings.features_title">Flagi funkcji</string>
<string name="feature_flags_five_star_rating_title">Użyj pięciu gwiazdek dla utworów</string> <string name="settings.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 <string name="settings.five_star_rating_description">W przypadku utworów użyj systemu pięciu gwiazdek
zamiast po prostu grać gwiazdkami / bez gwiazd. zamiast po prostu grać gwiazdkami / bez gwiazd.
</string> </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_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> <string name="api.subsonic.upgrade_server">Versões incompativeis. Atualize o servidor Ultrasonic.</string>
<!-- Subsonic feature flags --> <!-- Subsonic features -->
<string name="feature_flags_category_title">Sinalização de Recursos</string> <string name="settings.features_title">Sinalização de Recursos</string>
<string name="feature_flags_five_star_rating_title">Usar Classif. 5 Estrelas para Músicas</string> <string name="settings.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 <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 em vez de simplesmente estrelar/não estrelar itens
</string> </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_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> <string name="api.subsonic.upgrade_server">Versões incompativeis. Atualize o servidor Ultrasonic.</string>
<!-- Subsonic feature flags --> <!-- Subsonic features -->
<string name="feature_flags_category_title">Bandeiras de recursos</string> <string name="settings.features_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="settings.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 <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. em vez de simplesmente estrelar / não estrelar itens.
</string> </string>

View File

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

View File

@ -432,10 +432,10 @@
<string name="api.subsonic.upgrade_client">版本不兼容,请升级 Ultrasonic 应用。</string> <string name="api.subsonic.upgrade_client">版本不兼容,请升级 Ultrasonic 应用。</string>
<string name="api.subsonic.upgrade_server">不兼容的版本。请升级Subsonic 服务。</string> <string name="api.subsonic.upgrade_server">不兼容的版本。请升级Subsonic 服务。</string>
<!-- Subsonic feature flags --> <!-- Subsonic features -->
<string name="feature_flags_category_title">特性标志</string> <string name="settings.features_title">特性标志</string>
<string name="feature_flags_five_star_rating_title">为歌曲使用五星评分</string> <string name="settings.five_star_rating_title">为歌曲使用五星评分</string>
<string name="feature_flags_five_star_rating_description">对歌曲使用五星级评级系统 <string name="settings.five_star_rating_description">对歌曲使用五星级评级系统
而不是简单地为项目加星标/取消星标。</string> 而不是简单地为项目加星标/取消星标。</string>
</resources> </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_client">Incompatible versions. Please upgrade Ultrasonic Android app.</string>
<string name="api.subsonic.upgrade_server">Incompatible versions. Please upgrade Subsonic server.</string> <string name="api.subsonic.upgrade_server">Incompatible versions. Please upgrade Subsonic server.</string>
<!-- Subsonic feature flags --> <!-- Subsonic features -->
<string name="feature_flags_category_title">Feature Flags</string> <string name="settings.features_title">Features</string>
<string name="feature_flags_five_star_rating_title">Use five star rating for songs</string> <string name="settings.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 <string name="settings.five_star_rating_description">Use five star rating system for songs
instead of simply starring/unstarring items. instead of simply starring/unstarring items.
</string> </string>

View File

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