mirror of
https://gitlab.com/ultrasonic/ultrasonic.git
synced 2025-04-25 13:12:16 +03:00
Merge branch 'ready/useFlow2' into 'develop'
Retrieve server features in parallel, retrieve Jukebox capabilities as server feature. Closes #829 See merge request ultrasonic/ultrasonic!886
This commit is contained in:
commit
ffdd5df82d
2
.idea/inspectionProfiles/Project_Default.xml
generated
2
.idea/inspectionProfiles/Project_Default.xml
generated
@ -1,7 +1,7 @@
|
|||||||
<component name="InspectionProjectProfileManager">
|
<component name="InspectionProjectProfileManager">
|
||||||
<profile version="1.0">
|
<profile version="1.0">
|
||||||
<option name="myName" value="Project Default" />
|
<option name="myName" value="Project Default" />
|
||||||
<inspection_tool class="Reformat" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
<inspection_tool class="Reformat" enabled="false" level="WEAK WARNING" enabled_by_default="false">
|
||||||
<option name="processChangedFilesOnly" value="true" />
|
<option name="processChangedFilesOnly" value="true" />
|
||||||
</inspection_tool>
|
</inspection_tool>
|
||||||
</profile>
|
</profile>
|
||||||
|
@ -54,6 +54,9 @@ interface SubsonicAPIDefinition {
|
|||||||
@GET("ping.view")
|
@GET("ping.view")
|
||||||
fun ping(): Call<SubsonicResponse>
|
fun ping(): Call<SubsonicResponse>
|
||||||
|
|
||||||
|
@GET("ping.view")
|
||||||
|
suspend fun pingSuspend(): SubsonicResponse
|
||||||
|
|
||||||
@GET("getLicense.view")
|
@GET("getLicense.view")
|
||||||
fun getLicense(): Call<LicenseResponse>
|
fun getLicense(): Call<LicenseResponse>
|
||||||
|
|
||||||
@ -164,6 +167,12 @@ interface SubsonicAPIDefinition {
|
|||||||
@Query("id") id: String? = null
|
@Query("id") id: String? = null
|
||||||
): Call<GetPodcastsResponse>
|
): Call<GetPodcastsResponse>
|
||||||
|
|
||||||
|
@GET("getPodcasts.view")
|
||||||
|
suspend fun getPodcastsSuspend(
|
||||||
|
@Query("includeEpisodes") includeEpisodes: Boolean? = null,
|
||||||
|
@Query("id") id: String? = null
|
||||||
|
): GetPodcastsResponse
|
||||||
|
|
||||||
@GET("getLyrics.view")
|
@GET("getLyrics.view")
|
||||||
fun getLyrics(
|
fun getLyrics(
|
||||||
@Query("artist") artist: String? = null,
|
@Query("artist") artist: String? = null,
|
||||||
@ -261,6 +270,9 @@ interface SubsonicAPIDefinition {
|
|||||||
@GET("getShares.view")
|
@GET("getShares.view")
|
||||||
fun getShares(): Call<SharesResponse>
|
fun getShares(): Call<SharesResponse>
|
||||||
|
|
||||||
|
@GET("getShares.view")
|
||||||
|
suspend fun getSharesSuspend(): SharesResponse
|
||||||
|
|
||||||
@GET("createShare.view")
|
@GET("createShare.view")
|
||||||
fun createShare(
|
fun createShare(
|
||||||
@Query("id") idsToShare: List<String>,
|
@Query("id") idsToShare: List<String>,
|
||||||
@ -292,15 +304,24 @@ interface SubsonicAPIDefinition {
|
|||||||
@GET("getUser.view")
|
@GET("getUser.view")
|
||||||
fun getUser(@Query("username") username: String): Call<GetUserResponse>
|
fun getUser(@Query("username") username: String): Call<GetUserResponse>
|
||||||
|
|
||||||
|
@GET("getUser.view")
|
||||||
|
suspend fun getUserSuspend(@Query("username") username: String): GetUserResponse
|
||||||
|
|
||||||
@GET("getChatMessages.view")
|
@GET("getChatMessages.view")
|
||||||
fun getChatMessages(@Query("since") since: Long? = null): Call<ChatMessagesResponse>
|
fun getChatMessages(@Query("since") since: Long? = null): Call<ChatMessagesResponse>
|
||||||
|
|
||||||
|
@GET("getChatMessages.view")
|
||||||
|
suspend fun getChatMessagesSuspend(@Query("since") since: Long? = null): ChatMessagesResponse
|
||||||
|
|
||||||
@GET("addChatMessage.view")
|
@GET("addChatMessage.view")
|
||||||
fun addChatMessage(@Query("message") message: String): Call<SubsonicResponse>
|
fun addChatMessage(@Query("message") message: String): Call<SubsonicResponse>
|
||||||
|
|
||||||
@GET("getBookmarks.view")
|
@GET("getBookmarks.view")
|
||||||
fun getBookmarks(): Call<BookmarksResponse>
|
fun getBookmarks(): Call<BookmarksResponse>
|
||||||
|
|
||||||
|
@GET("getBookmarks.view")
|
||||||
|
suspend fun getBookmarksSuspend(): BookmarksResponse
|
||||||
|
|
||||||
@GET("createBookmark.view")
|
@GET("createBookmark.view")
|
||||||
fun createBookmark(
|
fun createBookmark(
|
||||||
@Query("id") id: String,
|
@Query("id") id: String,
|
||||||
@ -314,6 +335,9 @@ interface SubsonicAPIDefinition {
|
|||||||
@GET("getVideos.view")
|
@GET("getVideos.view")
|
||||||
fun getVideos(): Call<VideosResponse>
|
fun getVideos(): Call<VideosResponse>
|
||||||
|
|
||||||
|
@GET("getVideos.view")
|
||||||
|
suspend fun getVideosSuspend(): VideosResponse
|
||||||
|
|
||||||
@GET("getAvatar.view")
|
@GET("getAvatar.view")
|
||||||
fun getAvatar(@Query("username") username: String): Call<ResponseBody>
|
fun getAvatar(@Query("username") username: String): Call<ResponseBody>
|
||||||
}
|
}
|
||||||
|
@ -66,6 +66,8 @@ style:
|
|||||||
active: false
|
active: false
|
||||||
ReturnCount:
|
ReturnCount:
|
||||||
max: 5
|
max: 5
|
||||||
|
ForbiddenImport:
|
||||||
|
imports: ['android.app.AlertDialog']
|
||||||
|
|
||||||
comments:
|
comments:
|
||||||
active: true
|
active: true
|
||||||
|
136
ultrasonic/schemas/org.moire.ultrasonic.data.AppDatabase/6.json
Normal file
136
ultrasonic/schemas/org.moire.ultrasonic.data.AppDatabase/6.json
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
{
|
||||||
|
"formatVersion": 1,
|
||||||
|
"database": {
|
||||||
|
"version": 6,
|
||||||
|
"identityHash": "9d28146ad3086d9c761f25ca007a96ce",
|
||||||
|
"entities": [
|
||||||
|
{
|
||||||
|
"tableName": "ServerSetting",
|
||||||
|
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `index` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `color` INTEGER, `userName` TEXT NOT NULL, `password` TEXT NOT NULL, `jukeboxByDefault` INTEGER NOT NULL, `allowSelfSignedCertificate` INTEGER NOT NULL, `forcePlainTextPassword` INTEGER NOT NULL, `musicFolderId` TEXT, `minimumApiVersion` TEXT, `chatSupport` INTEGER, `bookmarkSupport` INTEGER, `shareSupport` INTEGER, `podcastSupport` INTEGER, `jukeboxSupport` INTEGER, `videoSupport` INTEGER)",
|
||||||
|
"fields": [
|
||||||
|
{
|
||||||
|
"fieldPath": "id",
|
||||||
|
"columnName": "id",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "index",
|
||||||
|
"columnName": "index",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "name",
|
||||||
|
"columnName": "name",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "url",
|
||||||
|
"columnName": "url",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "color",
|
||||||
|
"columnName": "color",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "userName",
|
||||||
|
"columnName": "userName",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "password",
|
||||||
|
"columnName": "password",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "jukeboxByDefault",
|
||||||
|
"columnName": "jukeboxByDefault",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "allowSelfSignedCertificate",
|
||||||
|
"columnName": "allowSelfSignedCertificate",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "forcePlainTextPassword",
|
||||||
|
"columnName": "forcePlainTextPassword",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "musicFolderId",
|
||||||
|
"columnName": "musicFolderId",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "minimumApiVersion",
|
||||||
|
"columnName": "minimumApiVersion",
|
||||||
|
"affinity": "TEXT",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "chatSupport",
|
||||||
|
"columnName": "chatSupport",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "bookmarkSupport",
|
||||||
|
"columnName": "bookmarkSupport",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "shareSupport",
|
||||||
|
"columnName": "shareSupport",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "podcastSupport",
|
||||||
|
"columnName": "podcastSupport",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "jukeboxSupport",
|
||||||
|
"columnName": "jukeboxSupport",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"fieldPath": "videoSupport",
|
||||||
|
"columnName": "videoSupport",
|
||||||
|
"affinity": "INTEGER",
|
||||||
|
"notNull": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"primaryKey": {
|
||||||
|
"columnNames": [
|
||||||
|
"id"
|
||||||
|
],
|
||||||
|
"autoGenerate": true
|
||||||
|
},
|
||||||
|
"indices": [],
|
||||||
|
"foreignKeys": []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"views": [],
|
||||||
|
"setupQueries": [
|
||||||
|
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
|
||||||
|
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '9d28146ad3086d9c761f25ca007a96ce')"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
@ -14,7 +14,6 @@ import android.view.inputmethod.EditorInfo;
|
|||||||
import android.widget.EditText;
|
import android.widget.EditText;
|
||||||
import android.widget.ListAdapter;
|
import android.widget.ListAdapter;
|
||||||
import android.widget.ListView;
|
import android.widget.ListView;
|
||||||
import android.widget.TextView;
|
|
||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
@ -19,10 +19,8 @@
|
|||||||
package org.moire.ultrasonic.util;
|
package org.moire.ultrasonic.util;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
|
|
||||||
import org.moire.ultrasonic.R;
|
import org.moire.ultrasonic.R;
|
||||||
|
|
||||||
import timber.log.Timber;
|
import timber.log.Timber;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -47,9 +45,9 @@ public abstract class ModalBackgroundTask<T> extends BackgroundTask<T>
|
|||||||
this(activity, true);
|
this(activity, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private AlertDialog createProgressDialog()
|
private androidx.appcompat.app.AlertDialog createProgressDialog()
|
||||||
{
|
{
|
||||||
AlertDialog.Builder builder = new InfoDialog.Builder(getActivity());
|
InfoDialog.Builder builder = new InfoDialog.Builder(getActivity().getApplicationContext());
|
||||||
builder.setTitle(R.string.background_task_wait);
|
builder.setTitle(R.string.background_task_wait);
|
||||||
builder.setMessage(R.string.background_task_loading);
|
builder.setMessage(R.string.background_task_loading);
|
||||||
builder.setOnCancelListener(dialogInterface -> cancel());
|
builder.setOnCancelListener(dialogInterface -> cancel());
|
||||||
|
@ -172,9 +172,6 @@ class NavigationActivity : AppCompatActivity() {
|
|||||||
} else {
|
} else {
|
||||||
if (!nowPlayingHidden) showNowPlaying()
|
if (!nowPlayingHidden) showNowPlaying()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hides menu items for Offline mode
|
|
||||||
setMenuForServerCapabilities()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Determine if this is a first run
|
// Determine if this is a first run
|
||||||
@ -207,6 +204,7 @@ class NavigationActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
rxBusSubscription += RxBus.activeServerChangedObservable.subscribe {
|
rxBusSubscription += RxBus.activeServerChangedObservable.subscribe {
|
||||||
updateNavigationHeaderForServer()
|
updateNavigationHeaderForServer()
|
||||||
|
setMenuForServerCapabilities()
|
||||||
}
|
}
|
||||||
|
|
||||||
serverRepository.liveServerCount().observe(this) { count ->
|
serverRepository.liveServerCount().observe(this) { count ->
|
||||||
@ -506,6 +504,6 @@ class NavigationActivity : AppCompatActivity() {
|
|||||||
podcastsMenuItem?.isVisible = activeServer.podcastSupport != false
|
podcastsMenuItem?.isVisible = activeServer.podcastSupport != false
|
||||||
playlistsMenuItem?.isVisible = isOnline
|
playlistsMenuItem?.isVisible = isOnline
|
||||||
downloadsMenuItem?.isVisible = isOnline
|
downloadsMenuItem?.isVisible = isOnline
|
||||||
videoMenuItem?.isVisible = isOnline
|
videoMenuItem?.isVisible = activeServer.videoSupport != false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -214,7 +214,9 @@ class ActiveServerProvider(
|
|||||||
bookmarkSupport = false,
|
bookmarkSupport = false,
|
||||||
podcastSupport = false,
|
podcastSupport = false,
|
||||||
shareSupport = false,
|
shareSupport = false,
|
||||||
chatSupport = false
|
chatSupport = false,
|
||||||
|
videoSupport = false,
|
||||||
|
jukeboxSupport = false
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -11,7 +11,7 @@ import androidx.sqlite.db.SupportSQLiteDatabase
|
|||||||
*/
|
*/
|
||||||
@Database(
|
@Database(
|
||||||
entities = [ServerSetting::class],
|
entities = [ServerSetting::class],
|
||||||
version = 5,
|
version = 6,
|
||||||
exportSchema = true
|
exportSchema = true
|
||||||
)
|
)
|
||||||
abstract class AppDatabase : RoomDatabase() {
|
abstract class AppDatabase : RoomDatabase() {
|
||||||
@ -265,7 +265,6 @@ val MIGRATION_5_4: Migration = object : Migration(5, 4) {
|
|||||||
database.execSQL("ALTER TABLE `_new_ServerSetting` RENAME TO `ServerSetting`")
|
database.execSQL("ALTER TABLE `_new_ServerSetting` RENAME TO `ServerSetting`")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ktlint-disable max-line-length */
|
/* ktlint-disable max-line-length */
|
||||||
val MIGRATION_5_6: Migration = object : Migration(5, 6) {
|
val MIGRATION_5_6: Migration = object : Migration(5, 6) {
|
||||||
override fun migrate(database: SupportSQLiteDatabase) {
|
override fun migrate(database: SupportSQLiteDatabase) {
|
||||||
|
@ -17,8 +17,6 @@ import androidx.room.PrimaryKey
|
|||||||
* @param forcePlainTextPassword: True if the server authenticates the user using old Ldap-like way
|
* @param forcePlainTextPassword: True if the server authenticates the user using old Ldap-like way
|
||||||
* @param musicFolderId: The Id of the MusicFolder to be used with the server
|
* @param musicFolderId: The Id of the MusicFolder to be used with the server
|
||||||
*
|
*
|
||||||
* TODO: forcePlainTextPassword is still using the old column name.
|
|
||||||
* Could be updated on the next significant change to the DB scheme
|
|
||||||
*/
|
*/
|
||||||
@Entity
|
@Entity
|
||||||
data class ServerSetting(
|
data class ServerSetting(
|
||||||
@ -32,14 +30,17 @@ data class ServerSetting(
|
|||||||
@ColumnInfo(name = "password") var password: String,
|
@ColumnInfo(name = "password") var password: String,
|
||||||
@ColumnInfo(name = "jukeboxByDefault") var jukeboxByDefault: Boolean,
|
@ColumnInfo(name = "jukeboxByDefault") var jukeboxByDefault: Boolean,
|
||||||
@ColumnInfo(name = "allowSelfSignedCertificate") var allowSelfSignedCertificate: Boolean,
|
@ColumnInfo(name = "allowSelfSignedCertificate") var allowSelfSignedCertificate: Boolean,
|
||||||
@ColumnInfo(name = "ldapSupport") var forcePlainTextPassword: Boolean,
|
@ColumnInfo(name = "forcePlainTextPassword") var forcePlainTextPassword: Boolean,
|
||||||
@ColumnInfo(name = "musicFolderId") var musicFolderId: String?,
|
@ColumnInfo(name = "musicFolderId") var musicFolderId: String?,
|
||||||
@ColumnInfo(name = "minimumApiVersion") var minimumApiVersion: String?,
|
@ColumnInfo(name = "minimumApiVersion") var minimumApiVersion: String?,
|
||||||
@ColumnInfo(name = "chatSupport") var chatSupport: Boolean? = null,
|
@ColumnInfo(name = "chatSupport") var chatSupport: Boolean? = null,
|
||||||
@ColumnInfo(name = "bookmarkSupport") var bookmarkSupport: Boolean? = null,
|
@ColumnInfo(name = "bookmarkSupport") var bookmarkSupport: Boolean? = null,
|
||||||
@ColumnInfo(name = "shareSupport") var shareSupport: Boolean? = null,
|
@ColumnInfo(name = "shareSupport") var shareSupport: Boolean? = null,
|
||||||
@ColumnInfo(name = "podcastSupport") var podcastSupport: Boolean? = null
|
@ColumnInfo(name = "podcastSupport") var podcastSupport: Boolean? = null,
|
||||||
|
@ColumnInfo(name = "jukeboxSupport") var jukeboxSupport: Boolean? = null,
|
||||||
|
@ColumnInfo(name = "videoSupport") var videoSupport: Boolean? = null
|
||||||
) {
|
) {
|
||||||
|
|
||||||
constructor() : this (
|
constructor() : this (
|
||||||
0, 0, "", "", null, "", "", false, false, false, null, null
|
0, 0, "", "", null, "", "", false, false, false, null, null
|
||||||
)
|
)
|
||||||
|
@ -13,8 +13,11 @@ import android.view.View
|
|||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.Button
|
import android.widget.Button
|
||||||
import android.widget.ImageView
|
import android.widget.ImageView
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.fragment.app.viewModels
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
import androidx.navigation.fragment.navArgs
|
import androidx.navigation.fragment.navArgs
|
||||||
import com.google.android.material.switchmaterial.SwitchMaterial
|
import com.google.android.material.switchmaterial.SwitchMaterial
|
||||||
@ -23,32 +26,25 @@ import com.skydoves.colorpickerview.ColorPickerDialog
|
|||||||
import com.skydoves.colorpickerview.flag.BubbleFlag
|
import com.skydoves.colorpickerview.flag.BubbleFlag
|
||||||
import com.skydoves.colorpickerview.flag.FlagMode
|
import com.skydoves.colorpickerview.flag.FlagMode
|
||||||
import com.skydoves.colorpickerview.listeners.ColorEnvelopeListener
|
import com.skydoves.colorpickerview.listeners.ColorEnvelopeListener
|
||||||
import java.io.IOException
|
|
||||||
import java.net.MalformedURLException
|
import java.net.MalformedURLException
|
||||||
import java.net.URL
|
import java.net.URL
|
||||||
import java.util.Locale
|
import kotlinx.coroutines.CancellationException
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.flowOn
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import org.koin.android.ext.android.inject
|
import org.koin.android.ext.android.inject
|
||||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||||
import org.moire.ultrasonic.BuildConfig
|
|
||||||
import org.moire.ultrasonic.R
|
import org.moire.ultrasonic.R
|
||||||
import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient
|
|
||||||
import org.moire.ultrasonic.api.subsonic.SubsonicAPIVersions
|
|
||||||
import org.moire.ultrasonic.api.subsonic.SubsonicClientConfiguration
|
|
||||||
import org.moire.ultrasonic.api.subsonic.SubsonicRESTException
|
|
||||||
import org.moire.ultrasonic.api.subsonic.falseOnFailure
|
|
||||||
import org.moire.ultrasonic.api.subsonic.response.SubsonicResponse
|
|
||||||
import org.moire.ultrasonic.api.subsonic.throwOnFailure
|
|
||||||
import org.moire.ultrasonic.data.ActiveServerProvider
|
import org.moire.ultrasonic.data.ActiveServerProvider
|
||||||
import org.moire.ultrasonic.data.ServerSetting
|
import org.moire.ultrasonic.data.ServerSetting
|
||||||
|
import org.moire.ultrasonic.model.EditServerModel
|
||||||
import org.moire.ultrasonic.model.ServerSettingsModel
|
import org.moire.ultrasonic.model.ServerSettingsModel
|
||||||
import org.moire.ultrasonic.service.MusicServiceFactory
|
import org.moire.ultrasonic.service.MusicServiceFactory
|
||||||
import org.moire.ultrasonic.util.Constants
|
import org.moire.ultrasonic.util.CommunicationError.getErrorMessage
|
||||||
import org.moire.ultrasonic.util.ErrorDialog
|
import org.moire.ultrasonic.util.ErrorDialog
|
||||||
import org.moire.ultrasonic.util.InfoDialog
|
import org.moire.ultrasonic.util.InfoDialog
|
||||||
import org.moire.ultrasonic.util.ModalBackgroundTask
|
|
||||||
import org.moire.ultrasonic.util.ServerColor
|
import org.moire.ultrasonic.util.ServerColor
|
||||||
import org.moire.ultrasonic.util.Util
|
import org.moire.ultrasonic.util.Util
|
||||||
import retrofit2.Response
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
private const val DIALOG_PADDING = 12
|
private const val DIALOG_PADDING = 12
|
||||||
@ -78,6 +74,7 @@ class EditServerFragment : Fragment(), OnBackPressedHandler {
|
|||||||
private var selectedColor: Int? = null
|
private var selectedColor: Int? = null
|
||||||
|
|
||||||
private val navArgs by navArgs<EditServerFragmentArgs>()
|
private val navArgs by navArgs<EditServerFragmentArgs>()
|
||||||
|
val model: EditServerModel by viewModels()
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
@ -372,149 +369,82 @@ class EditServerFragment : Fragment(), OnBackPressedHandler {
|
|||||||
/**
|
/**
|
||||||
* Tests if the network connection to the entered Server Settings can be made
|
* Tests if the network connection to the entered Server Settings can be made
|
||||||
*/
|
*/
|
||||||
|
@Suppress("TooGenericExceptionCaught")
|
||||||
private fun testConnection() {
|
private fun testConnection() {
|
||||||
val task: ModalBackgroundTask<String> = object : ModalBackgroundTask<String>(
|
val testSetting = ServerSetting()
|
||||||
activity,
|
val builder = InfoDialog.Builder(requireContext())
|
||||||
false
|
builder.setTitle(R.string.supported_server_features)
|
||||||
) {
|
builder.setMessage(getProgress(testSetting))
|
||||||
fun boolToMark(value: Boolean?): String {
|
val dialog: AlertDialog = builder.create()
|
||||||
if (value == null)
|
dialog.show()
|
||||||
return "⌛"
|
|
||||||
return if (value) "✔️" else "❌"
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getProgress(): String {
|
val testJob = lifecycleScope.launch {
|
||||||
return String.format(
|
try {
|
||||||
"""
|
val flow = model.queryFeatureSupport(currentServerSetting!!).flowOn(Dispatchers.IO)
|
||||||
|
|
||||||
|
flow.collect {
|
||||||
|
model.storeFeatureSupport(testSetting, it)
|
||||||
|
dialog.setMessage(getProgress(testSetting))
|
||||||
|
Timber.w("${it.type} support: ${it.supported}")
|
||||||
|
}
|
||||||
|
|
||||||
|
currentServerSetting!!.chatSupport = testSetting.chatSupport
|
||||||
|
currentServerSetting!!.bookmarkSupport = testSetting.bookmarkSupport
|
||||||
|
currentServerSetting!!.shareSupport = testSetting.shareSupport
|
||||||
|
currentServerSetting!!.podcastSupport = testSetting.podcastSupport
|
||||||
|
currentServerSetting!!.videoSupport = testSetting.videoSupport
|
||||||
|
currentServerSetting!!.jukeboxSupport = testSetting.jukeboxSupport
|
||||||
|
} catch (cancellationException: CancellationException) {
|
||||||
|
Timber.i(cancellationException)
|
||||||
|
} catch (exception: Exception) {
|
||||||
|
dialog.dismiss()
|
||||||
|
Timber.w(exception)
|
||||||
|
ErrorDialog.Builder(requireContext())
|
||||||
|
.setTitle(R.string.error_label)
|
||||||
|
.setMessage(getErrorMessage(exception, context))
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dialog.setOnDismissListener { testJob.cancel() }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getProgress(serverSetting: ServerSetting): String {
|
||||||
|
val isAnyDisabled = arrayOf(
|
||||||
|
serverSetting.chatSupport,
|
||||||
|
serverSetting.bookmarkSupport,
|
||||||
|
serverSetting.shareSupport,
|
||||||
|
serverSetting.podcastSupport,
|
||||||
|
serverSetting.videoSupport,
|
||||||
|
serverSetting.jukeboxSupport,
|
||||||
|
).any { x -> x == false }
|
||||||
|
|
||||||
|
var progressString = String.format(
|
||||||
|
"""
|
||||||
|%s - ${resources.getString(R.string.button_bar_chat)}
|
|%s - ${resources.getString(R.string.button_bar_chat)}
|
||||||
|%s - ${resources.getString(R.string.button_bar_bookmarks)}
|
|%s - ${resources.getString(R.string.button_bar_bookmarks)}
|
||||||
|%s - ${resources.getString(R.string.button_bar_shares)}
|
|%s - ${resources.getString(R.string.button_bar_shares)}
|
||||||
|%s - ${resources.getString(R.string.button_bar_podcasts)}
|
|%s - ${resources.getString(R.string.button_bar_podcasts)}
|
||||||
|
|%s - ${resources.getString(R.string.main_videos)}
|
||||||
|
|%s - ${resources.getString(R.string.jukebox)}
|
||||||
""".trimMargin(),
|
""".trimMargin(),
|
||||||
boolToMark(currentServerSetting!!.chatSupport),
|
boolToMark(serverSetting.chatSupport),
|
||||||
boolToMark(currentServerSetting!!.bookmarkSupport),
|
boolToMark(serverSetting.bookmarkSupport),
|
||||||
boolToMark(currentServerSetting!!.shareSupport),
|
boolToMark(serverSetting.shareSupport),
|
||||||
boolToMark(currentServerSetting!!.podcastSupport)
|
boolToMark(serverSetting.podcastSupport),
|
||||||
)
|
boolToMark(serverSetting.videoSupport),
|
||||||
}
|
boolToMark(serverSetting.jukeboxSupport)
|
||||||
|
)
|
||||||
|
if (isAnyDisabled)
|
||||||
|
progressString += "\n\n" + resources.getString(R.string.server_editor_disabled_feature)
|
||||||
|
|
||||||
@Throws(Throwable::class)
|
return progressString
|
||||||
override fun doInBackground(): String {
|
|
||||||
|
|
||||||
currentServerSetting!!.chatSupport = null
|
|
||||||
currentServerSetting!!.bookmarkSupport = null
|
|
||||||
currentServerSetting!!.shareSupport = null
|
|
||||||
currentServerSetting!!.podcastSupport = null
|
|
||||||
|
|
||||||
updateProgress(getProgress())
|
|
||||||
|
|
||||||
val configuration = SubsonicClientConfiguration(
|
|
||||||
currentServerSetting!!.url,
|
|
||||||
currentServerSetting!!.userName,
|
|
||||||
currentServerSetting!!.password,
|
|
||||||
SubsonicAPIVersions.getClosestKnownClientApiVersion(
|
|
||||||
Constants.REST_PROTOCOL_VERSION
|
|
||||||
),
|
|
||||||
Constants.REST_CLIENT_ID,
|
|
||||||
currentServerSetting!!.allowSelfSignedCertificate,
|
|
||||||
currentServerSetting!!.forcePlainTextPassword,
|
|
||||||
BuildConfig.DEBUG
|
|
||||||
)
|
|
||||||
val subsonicApiClient = SubsonicAPIClient(configuration)
|
|
||||||
|
|
||||||
// Execute a ping to retrieve the API version.
|
|
||||||
// This is accepted to fail if the authentication is incorrect yet.
|
|
||||||
var pingResponse = subsonicApiClient.api.ping().execute()
|
|
||||||
if (pingResponse.body() != null) {
|
|
||||||
val restApiVersion = pingResponse.body()!!.version.restApiVersion
|
|
||||||
currentServerSetting!!.minimumApiVersion = restApiVersion
|
|
||||||
Timber.i("Server minimum API version set to %s", restApiVersion)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute a ping to check the authentication, now using the correct API version.
|
|
||||||
pingResponse = subsonicApiClient.api.ping().execute()
|
|
||||||
pingResponse.throwOnFailure()
|
|
||||||
|
|
||||||
currentServerSetting!!.chatSupport = isServerFunctionAvailable {
|
|
||||||
subsonicApiClient.api.getChatMessages().execute()
|
|
||||||
}
|
|
||||||
|
|
||||||
updateProgress(getProgress())
|
|
||||||
|
|
||||||
currentServerSetting!!.bookmarkSupport = isServerFunctionAvailable {
|
|
||||||
subsonicApiClient.api.getBookmarks().execute()
|
|
||||||
}
|
|
||||||
|
|
||||||
updateProgress(getProgress())
|
|
||||||
|
|
||||||
currentServerSetting!!.shareSupport = isServerFunctionAvailable {
|
|
||||||
subsonicApiClient.api.getShares().execute()
|
|
||||||
}
|
|
||||||
|
|
||||||
updateProgress(getProgress())
|
|
||||||
|
|
||||||
currentServerSetting!!.podcastSupport = isServerFunctionAvailable {
|
|
||||||
subsonicApiClient.api.getPodcasts().execute()
|
|
||||||
}
|
|
||||||
|
|
||||||
updateProgress(getProgress())
|
|
||||||
|
|
||||||
val licenseResponse = subsonicApiClient.api.getLicense().execute()
|
|
||||||
licenseResponse.throwOnFailure()
|
|
||||||
|
|
||||||
if (!licenseResponse.body()!!.license.valid) {
|
|
||||||
return getProgress() + "\n" +
|
|
||||||
resources.getString(R.string.settings_testing_unlicensed)
|
|
||||||
}
|
|
||||||
return getProgress()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun done(responseString: String) {
|
|
||||||
var dialogText = responseString
|
|
||||||
if (arrayOf(
|
|
||||||
currentServerSetting!!.chatSupport,
|
|
||||||
currentServerSetting!!.bookmarkSupport,
|
|
||||||
currentServerSetting!!.shareSupport,
|
|
||||||
currentServerSetting!!.podcastSupport
|
|
||||||
).any { x -> x == false }
|
|
||||||
) {
|
|
||||||
dialogText = String.format(
|
|
||||||
Locale.ROOT,
|
|
||||||
"%s\n\n%s",
|
|
||||||
responseString,
|
|
||||||
resources.getString(R.string.server_editor_disabled_feature)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
InfoDialog.Builder(requireActivity())
|
|
||||||
.setTitle(R.string.settings_testing_ok)
|
|
||||||
.setMessage(dialogText)
|
|
||||||
.show()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun error(error: Throwable) {
|
|
||||||
Timber.w(error)
|
|
||||||
ErrorDialog(
|
|
||||||
context = activity,
|
|
||||||
message = String.format(
|
|
||||||
"%s %s",
|
|
||||||
resources.getString(R.string.settings_connection_failure),
|
|
||||||
getErrorMessage(error)
|
|
||||||
)
|
|
||||||
).show()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
task.execute()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun isServerFunctionAvailable(function: () -> Response<out SubsonicResponse>): Boolean {
|
private fun boolToMark(value: Boolean?): String {
|
||||||
return try {
|
if (value == null)
|
||||||
function().falseOnFailure()
|
return "⌛"
|
||||||
} catch (_: IOException) {
|
return if (value) "✔️" else "❌"
|
||||||
false
|
|
||||||
} catch (_: SubsonicRESTException) {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -522,7 +452,7 @@ class EditServerFragment : Fragment(), OnBackPressedHandler {
|
|||||||
*/
|
*/
|
||||||
private fun finishActivity() {
|
private fun finishActivity() {
|
||||||
if (areFieldsChanged()) {
|
if (areFieldsChanged()) {
|
||||||
ErrorDialog.Builder(context)
|
ErrorDialog.Builder(requireContext())
|
||||||
.setTitle(R.string.common_confirm)
|
.setTitle(R.string.common_confirm)
|
||||||
.setMessage(R.string.server_editor_leave_confirmation)
|
.setMessage(R.string.server_editor_leave_confirmation)
|
||||||
.setPositiveButton(R.string.common_ok) { dialog, _ ->
|
.setPositiveButton(R.string.common_ok) { dialog, _ ->
|
||||||
|
@ -8,7 +8,6 @@
|
|||||||
package org.moire.ultrasonic.fragment
|
package org.moire.ultrasonic.fragment
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.AlertDialog
|
|
||||||
import android.graphics.Canvas
|
import android.graphics.Canvas
|
||||||
import android.graphics.Color.argb
|
import android.graphics.Color.argb
|
||||||
import android.graphics.Point
|
import android.graphics.Point
|
||||||
@ -90,6 +89,7 @@ import org.moire.ultrasonic.subsonic.NetworkAndStorageChecker
|
|||||||
import org.moire.ultrasonic.subsonic.ShareHandler
|
import org.moire.ultrasonic.subsonic.ShareHandler
|
||||||
import org.moire.ultrasonic.util.CancellationToken
|
import org.moire.ultrasonic.util.CancellationToken
|
||||||
import org.moire.ultrasonic.util.CommunicationError
|
import org.moire.ultrasonic.util.CommunicationError
|
||||||
|
import org.moire.ultrasonic.util.ConfirmationDialog
|
||||||
import org.moire.ultrasonic.util.Settings
|
import org.moire.ultrasonic.util.Settings
|
||||||
import org.moire.ultrasonic.util.Util
|
import org.moire.ultrasonic.util.Util
|
||||||
import org.moire.ultrasonic.util.toTrack
|
import org.moire.ultrasonic.util.toTrack
|
||||||
@ -1256,12 +1256,14 @@ class PlayerFragment :
|
|||||||
mediaPlayerController.setSongRating(rating)
|
mediaPlayerController.setSongRating(rating)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressLint("InflateParams")
|
||||||
private fun showSavePlaylistDialog() {
|
private fun showSavePlaylistDialog() {
|
||||||
val layout = LayoutInflater.from(this.context).inflate(R.layout.save_playlist, null)
|
val layout = LayoutInflater.from(this.context)
|
||||||
|
.inflate(R.layout.save_playlist, null)
|
||||||
|
|
||||||
playlistNameView = layout.findViewById(R.id.save_playlist_name)
|
playlistNameView = layout.findViewById(R.id.save_playlist_name)
|
||||||
|
|
||||||
val builder: AlertDialog.Builder = AlertDialog.Builder(context)
|
val builder = ConfirmationDialog.Builder(requireContext())
|
||||||
builder.setTitle(R.string.download_playlist_title)
|
builder.setTitle(R.string.download_playlist_title)
|
||||||
builder.setMessage(R.string.download_playlist_name)
|
builder.setMessage(R.string.download_playlist_name)
|
||||||
|
|
||||||
|
@ -88,7 +88,8 @@ class ServerSelectorFragment : Fragment() {
|
|||||||
* This Callback handles the deletion of a Server Setting
|
* This Callback handles the deletion of a Server Setting
|
||||||
*/
|
*/
|
||||||
private fun deleteServerById(id: Int) {
|
private fun deleteServerById(id: Int) {
|
||||||
ErrorDialog.Builder(context)
|
// FIXME
|
||||||
|
ErrorDialog.Builder(requireContext())
|
||||||
.setTitle(R.string.server_menu_delete)
|
.setTitle(R.string.server_menu_delete)
|
||||||
.setMessage(R.string.server_selector_delete_confirmation)
|
.setMessage(R.string.server_selector_delete_confirmation)
|
||||||
.setPositiveButton(R.string.common_delete) { dialog, _ ->
|
.setPositiveButton(R.string.common_delete) { dialog, _ ->
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package org.moire.ultrasonic.fragment
|
package org.moire.ultrasonic.fragment
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.app.AlertDialog
|
|
||||||
import android.content.DialogInterface
|
import android.content.DialogInterface
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
@ -34,6 +33,7 @@ import org.moire.ultrasonic.log.FileLoggerTree.Companion.uprootFromTimberForest
|
|||||||
import org.moire.ultrasonic.provider.SearchSuggestionProvider
|
import org.moire.ultrasonic.provider.SearchSuggestionProvider
|
||||||
import org.moire.ultrasonic.service.MediaPlayerController
|
import org.moire.ultrasonic.service.MediaPlayerController
|
||||||
import org.moire.ultrasonic.service.RxBus
|
import org.moire.ultrasonic.service.RxBus
|
||||||
|
import org.moire.ultrasonic.util.ConfirmationDialog
|
||||||
import org.moire.ultrasonic.util.Constants
|
import org.moire.ultrasonic.util.Constants
|
||||||
import org.moire.ultrasonic.util.ErrorDialog
|
import org.moire.ultrasonic.util.ErrorDialog
|
||||||
import org.moire.ultrasonic.util.FileUtil.ultrasonicDirectory
|
import org.moire.ultrasonic.util.FileUtil.ultrasonicDirectory
|
||||||
@ -136,7 +136,7 @@ class SettingsFragment :
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ErrorDialog.Builder(context)
|
ErrorDialog.Builder(requireContext())
|
||||||
.setMessage(R.string.settings_cache_location_error)
|
.setMessage(R.string.settings_cache_location_error)
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
@ -297,7 +297,7 @@ class SettingsFragment :
|
|||||||
onChosen: (Int) -> Unit
|
onChosen: (Int) -> Unit
|
||||||
) {
|
) {
|
||||||
val choice = intArrayOf(defaultChoice)
|
val choice = intArrayOf(defaultChoice)
|
||||||
AlertDialog.Builder(activity).setTitle(title)
|
ConfirmationDialog.Builder(requireContext()).setTitle(title)
|
||||||
.setSingleChoiceItems(
|
.setSingleChoiceItems(
|
||||||
R.array.bluetoothDeviceSettingNames, defaultChoice
|
R.array.bluetoothDeviceSettingNames, defaultChoice
|
||||||
) { _: DialogInterface?, i: Int -> choice[0] = i }
|
) { _: DialogInterface?, i: Int -> choice[0] = i }
|
||||||
@ -404,7 +404,7 @@ class SettingsFragment :
|
|||||||
)
|
)
|
||||||
val keep = R.string.settings_debug_log_keep
|
val keep = R.string.settings_debug_log_keep
|
||||||
val delete = R.string.settings_debug_log_delete
|
val delete = R.string.settings_debug_log_delete
|
||||||
InfoDialog.Builder(activity)
|
ConfirmationDialog.Builder(requireContext())
|
||||||
.setMessage(message)
|
.setMessage(message)
|
||||||
.setNegativeButton(keep) { dIf: DialogInterface, _: Int ->
|
.setNegativeButton(keep) { dIf: DialogInterface, _: Int ->
|
||||||
dIf.cancel()
|
dIf.cancel()
|
||||||
@ -413,7 +413,7 @@ class SettingsFragment :
|
|||||||
deleteLogFiles()
|
deleteLogFiles()
|
||||||
Timber.i("Deleted debug log files")
|
Timber.i("Deleted debug log files")
|
||||||
dIf.dismiss()
|
dIf.dismiss()
|
||||||
AlertDialog.Builder(activity)
|
InfoDialog.Builder(requireContext())
|
||||||
.setMessage(R.string.settings_debug_log_deleted)
|
.setMessage(R.string.settings_debug_log_deleted)
|
||||||
.setPositiveButton(R.string.common_ok) { dIf2: DialogInterface, _: Int ->
|
.setPositiveButton(R.string.common_ok) { dIf2: DialogInterface, _: Int ->
|
||||||
dIf2.dismiss()
|
dIf2.dismiss()
|
||||||
|
@ -229,7 +229,7 @@ open class TrackCollectionFragment(
|
|||||||
|
|
||||||
unpinButton?.setOnClickListener {
|
unpinButton?.setOnClickListener {
|
||||||
if (Settings.showConfirmationDialog) {
|
if (Settings.showConfirmationDialog) {
|
||||||
ConfirmationDialog.Builder(context)
|
ConfirmationDialog.Builder(requireContext())
|
||||||
.setMessage(R.string.common_unpin_selection_confirmation)
|
.setMessage(R.string.common_unpin_selection_confirmation)
|
||||||
.setPositiveButton(R.string.common_unpin) { _, _ ->
|
.setPositiveButton(R.string.common_unpin) { _, _ ->
|
||||||
unpin()
|
unpin()
|
||||||
@ -245,7 +245,7 @@ open class TrackCollectionFragment(
|
|||||||
|
|
||||||
deleteButton?.setOnClickListener {
|
deleteButton?.setOnClickListener {
|
||||||
if (Settings.showConfirmationDialog) {
|
if (Settings.showConfirmationDialog) {
|
||||||
ConfirmationDialog.Builder(context)
|
ConfirmationDialog.Builder(requireContext())
|
||||||
.setMessage(R.string.common_delete_selection_confirmation)
|
.setMessage(R.string.common_delete_selection_confirmation)
|
||||||
.setPositiveButton(R.string.common_delete) { _, _ ->
|
.setPositiveButton(R.string.common_delete) { _, _ ->
|
||||||
delete()
|
delete()
|
||||||
|
@ -8,7 +8,6 @@
|
|||||||
package org.moire.ultrasonic.fragment.legacy
|
package org.moire.ultrasonic.fragment.legacy
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.AlertDialog
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.Spannable
|
import android.text.Spannable
|
||||||
import android.text.SpannableString
|
import android.text.SpannableString
|
||||||
@ -43,7 +42,9 @@ import org.moire.ultrasonic.subsonic.DownloadHandler
|
|||||||
import org.moire.ultrasonic.util.BackgroundTask
|
import org.moire.ultrasonic.util.BackgroundTask
|
||||||
import org.moire.ultrasonic.util.CacheCleaner
|
import org.moire.ultrasonic.util.CacheCleaner
|
||||||
import org.moire.ultrasonic.util.CancellationToken
|
import org.moire.ultrasonic.util.CancellationToken
|
||||||
|
import org.moire.ultrasonic.util.ConfirmationDialog
|
||||||
import org.moire.ultrasonic.util.FragmentBackgroundTask
|
import org.moire.ultrasonic.util.FragmentBackgroundTask
|
||||||
|
import org.moire.ultrasonic.util.InfoDialog
|
||||||
import org.moire.ultrasonic.util.LoadingTask
|
import org.moire.ultrasonic.util.LoadingTask
|
||||||
import org.moire.ultrasonic.util.Util.applyTheme
|
import org.moire.ultrasonic.util.Util.applyTheme
|
||||||
import org.moire.ultrasonic.util.Util.toast
|
import org.moire.ultrasonic.util.Util.toast
|
||||||
@ -222,7 +223,7 @@ class PlaylistsFragment : Fragment() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun deletePlaylist(playlist: Playlist) {
|
private fun deletePlaylist(playlist: Playlist) {
|
||||||
AlertDialog.Builder(context).setIcon(R.drawable.ic_baseline_warning)
|
ConfirmationDialog.Builder(requireContext()).setIcon(R.drawable.ic_baseline_warning)
|
||||||
.setTitle(R.string.common_confirm).setMessage(
|
.setTitle(R.string.common_confirm).setMessage(
|
||||||
resources.getString(R.string.delete_playlist, playlist.name)
|
resources.getString(R.string.delete_playlist, playlist.name)
|
||||||
).setPositiveButton(R.string.common_ok) { _, _ ->
|
).setPositiveButton(R.string.common_ok) { _, _ ->
|
||||||
@ -283,8 +284,8 @@ class PlaylistsFragment : Fragment() {
|
|||||||
Linkify.addLinks(message, Linkify.WEB_URLS)
|
Linkify.addLinks(message, Linkify.WEB_URLS)
|
||||||
textView.text = message
|
textView.text = message
|
||||||
textView.movementMethod = LinkMovementMethod.getInstance()
|
textView.movementMethod = LinkMovementMethod.getInstance()
|
||||||
AlertDialog.Builder(context).setTitle(playlist.name).setCancelable(true)
|
InfoDialog.Builder(requireContext()).setTitle(playlist.name).setCancelable(true)
|
||||||
.setIcon(R.drawable.ic_baseline_info).setView(textView).show()
|
.setView(textView).show()
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressLint("InflateParams")
|
@SuppressLint("InflateParams")
|
||||||
@ -301,7 +302,7 @@ class PlaylistsFragment : Fragment() {
|
|||||||
} else {
|
} else {
|
||||||
publicBox.isChecked = pub
|
publicBox.isChecked = pub
|
||||||
}
|
}
|
||||||
val alertDialog = AlertDialog.Builder(context)
|
val alertDialog = ConfirmationDialog.Builder(requireContext())
|
||||||
alertDialog.setIcon(R.drawable.ic_baseline_warning)
|
alertDialog.setIcon(R.drawable.ic_baseline_warning)
|
||||||
alertDialog.setTitle(R.string.playlist_update_info)
|
alertDialog.setTitle(R.string.playlist_update_info)
|
||||||
alertDialog.setView(dialogView)
|
alertDialog.setView(dialogView)
|
||||||
|
@ -0,0 +1,155 @@
|
|||||||
|
/*
|
||||||
|
* EditServerModel.kt
|
||||||
|
* Copyright (C) 2009-2022 Ultrasonic developers
|
||||||
|
*
|
||||||
|
* Distributed under terms of the GNU GPLv3 license.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.moire.ultrasonic.model
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import androidx.lifecycle.AndroidViewModel
|
||||||
|
import java.io.IOException
|
||||||
|
import kotlinx.coroutines.FlowPreview
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.asFlow
|
||||||
|
import kotlinx.coroutines.flow.flatMapMerge
|
||||||
|
import kotlinx.coroutines.flow.flow
|
||||||
|
import org.koin.core.component.KoinComponent
|
||||||
|
import org.koin.core.component.inject
|
||||||
|
import org.moire.ultrasonic.BuildConfig
|
||||||
|
import org.moire.ultrasonic.api.subsonic.SubsonicAPIClient
|
||||||
|
import org.moire.ultrasonic.api.subsonic.SubsonicAPIDefinition
|
||||||
|
import org.moire.ultrasonic.api.subsonic.SubsonicAPIVersions
|
||||||
|
import org.moire.ultrasonic.api.subsonic.SubsonicClientConfiguration
|
||||||
|
import org.moire.ultrasonic.api.subsonic.SubsonicRESTException
|
||||||
|
import org.moire.ultrasonic.api.subsonic.response.SubsonicResponse
|
||||||
|
import org.moire.ultrasonic.data.ActiveServerProvider
|
||||||
|
import org.moire.ultrasonic.data.ServerSetting
|
||||||
|
import org.moire.ultrasonic.util.Constants
|
||||||
|
import retrofit2.HttpException
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
|
@Suppress("MagicNumber")
|
||||||
|
class EditServerModel(val app: Application) : AndroidViewModel(app), KoinComponent {
|
||||||
|
|
||||||
|
val activeServerProvider: ActiveServerProvider by inject()
|
||||||
|
|
||||||
|
private suspend fun serverFunctionAvailable(
|
||||||
|
type: ServerFeature,
|
||||||
|
function: suspend () -> SubsonicResponse
|
||||||
|
): FeatureSupport {
|
||||||
|
val result = try {
|
||||||
|
function().falseOnFailure()
|
||||||
|
} catch (_: IOException) {
|
||||||
|
false
|
||||||
|
} catch (_: SubsonicRESTException) {
|
||||||
|
false
|
||||||
|
} catch (_: HttpException) {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
return FeatureSupport(type, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This extension checks API call results for errors, API version, etc
|
||||||
|
* @return Boolean: True if everything was ok, false if an error was found
|
||||||
|
*/
|
||||||
|
private fun SubsonicResponse.falseOnFailure(): Boolean {
|
||||||
|
return (this.status === SubsonicResponse.Status.OK)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun requestFlow(
|
||||||
|
type: ServerFeature,
|
||||||
|
api: SubsonicAPIDefinition,
|
||||||
|
userName: String
|
||||||
|
) = flow {
|
||||||
|
when (type) {
|
||||||
|
ServerFeature.CHAT -> emit(
|
||||||
|
serverFunctionAvailable(type, api::getChatMessagesSuspend)
|
||||||
|
)
|
||||||
|
ServerFeature.BOOKMARK -> emit(
|
||||||
|
serverFunctionAvailable(type, api::getBookmarksSuspend)
|
||||||
|
)
|
||||||
|
ServerFeature.SHARE -> emit(
|
||||||
|
serverFunctionAvailable(type, api::getSharesSuspend)
|
||||||
|
)
|
||||||
|
ServerFeature.PODCAST -> emit(
|
||||||
|
serverFunctionAvailable(type, api::getPodcastsSuspend)
|
||||||
|
)
|
||||||
|
ServerFeature.JUKEBOX -> emit(
|
||||||
|
serverFunctionAvailable(type) {
|
||||||
|
val response = api.getUserSuspend(userName)
|
||||||
|
if (!response.user.jukeboxRole) throw IOException()
|
||||||
|
response
|
||||||
|
}
|
||||||
|
)
|
||||||
|
ServerFeature.VIDEO -> emit(
|
||||||
|
serverFunctionAvailable(type, api::getVideosSuspend)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(FlowPreview::class)
|
||||||
|
suspend fun queryFeatureSupport(currentServerSetting: ServerSetting): Flow<FeatureSupport> {
|
||||||
|
val client = buildTestClient(currentServerSetting)
|
||||||
|
// One line of magic:
|
||||||
|
// Get all possible feature values, turn them into a flow,
|
||||||
|
// and execute each request concurrently
|
||||||
|
return (ServerFeature.values()).asFlow().flatMapMerge {
|
||||||
|
requestFlow(it, client.api, currentServerSetting.userName)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun buildTestClient(serverSetting: ServerSetting): SubsonicAPIClient {
|
||||||
|
val configuration = SubsonicClientConfiguration(
|
||||||
|
serverSetting.url,
|
||||||
|
serverSetting.userName,
|
||||||
|
serverSetting.password,
|
||||||
|
SubsonicAPIVersions.getClosestKnownClientApiVersion(
|
||||||
|
Constants.REST_PROTOCOL_VERSION
|
||||||
|
),
|
||||||
|
Constants.REST_CLIENT_ID,
|
||||||
|
serverSetting.allowSelfSignedCertificate,
|
||||||
|
serverSetting.forcePlainTextPassword,
|
||||||
|
BuildConfig.DEBUG
|
||||||
|
)
|
||||||
|
|
||||||
|
val client = SubsonicAPIClient(configuration)
|
||||||
|
|
||||||
|
// Execute a ping to retrieve the API version.
|
||||||
|
// This is accepted to fail if the authentication is incorrect yet.
|
||||||
|
var pingResponse = client.api.pingSuspend()
|
||||||
|
val restApiVersion = pingResponse.version.restApiVersion
|
||||||
|
serverSetting.minimumApiVersion = restApiVersion
|
||||||
|
Timber.i("Server minimum API version set to %s", restApiVersion)
|
||||||
|
|
||||||
|
// Execute a ping to check the authentication, now using the correct API version.
|
||||||
|
pingResponse = client.api.pingSuspend()
|
||||||
|
return client
|
||||||
|
}
|
||||||
|
|
||||||
|
fun storeFeatureSupport(settings: ServerSetting, it: FeatureSupport) {
|
||||||
|
when (it.type) {
|
||||||
|
ServerFeature.CHAT -> settings.chatSupport = it.supported
|
||||||
|
ServerFeature.BOOKMARK -> settings.bookmarkSupport = it.supported
|
||||||
|
ServerFeature.SHARE -> settings.shareSupport = it.supported
|
||||||
|
ServerFeature.PODCAST -> settings.podcastSupport = it.supported
|
||||||
|
ServerFeature.JUKEBOX -> settings.jukeboxSupport = it.supported
|
||||||
|
ServerFeature.VIDEO -> settings.videoSupport = it.supported
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
enum class ServerFeature(val named: String) {
|
||||||
|
CHAT("chat"),
|
||||||
|
BOOKMARK("bookmark"),
|
||||||
|
SHARE("share"),
|
||||||
|
PODCAST("podcast"),
|
||||||
|
JUKEBOX("jukebox"),
|
||||||
|
VIDEO("video")
|
||||||
|
}
|
||||||
|
|
||||||
|
data class FeatureSupport(val type: ServerFeature, val supported: Boolean)
|
||||||
|
}
|
||||||
|
}
|
@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
package org.moire.ultrasonic.subsonic
|
package org.moire.ultrasonic.subsonic
|
||||||
|
|
||||||
import android.app.AlertDialog
|
import android.annotation.SuppressLint
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
@ -27,6 +27,7 @@ import org.moire.ultrasonic.domain.Track
|
|||||||
import org.moire.ultrasonic.service.MusicServiceFactory.getMusicService
|
import org.moire.ultrasonic.service.MusicServiceFactory.getMusicService
|
||||||
import org.moire.ultrasonic.util.BackgroundTask
|
import org.moire.ultrasonic.util.BackgroundTask
|
||||||
import org.moire.ultrasonic.util.CancellationToken
|
import org.moire.ultrasonic.util.CancellationToken
|
||||||
|
import org.moire.ultrasonic.util.ConfirmationDialog
|
||||||
import org.moire.ultrasonic.util.FragmentBackgroundTask
|
import org.moire.ultrasonic.util.FragmentBackgroundTask
|
||||||
import org.moire.ultrasonic.util.Settings
|
import org.moire.ultrasonic.util.Settings
|
||||||
import org.moire.ultrasonic.util.ShareDetails
|
import org.moire.ultrasonic.util.ShareDetails
|
||||||
@ -150,6 +151,8 @@ class ShareHandler(val context: Context) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("LongMethod")
|
||||||
|
@SuppressLint("InflateParams")
|
||||||
private fun showDialog(
|
private fun showDialog(
|
||||||
fragment: Fragment,
|
fragment: Fragment,
|
||||||
shareDetails: ShareDetails,
|
shareDetails: ShareDetails,
|
||||||
@ -184,7 +187,7 @@ class ShareHandler(val context: Context) {
|
|||||||
}
|
}
|
||||||
updateVisibility()
|
updateVisibility()
|
||||||
|
|
||||||
val builder = AlertDialog.Builder(fragment.context)
|
val builder = ConfirmationDialog.Builder(fragment.requireContext())
|
||||||
builder.setTitle(R.string.share_set_share_options)
|
builder.setTitle(R.string.share_set_share_options)
|
||||||
|
|
||||||
builder.setPositiveButton(R.string.menu_share) { _, _ ->
|
builder.setPositiveButton(R.string.menu_share) { _, _ ->
|
||||||
|
@ -8,10 +8,14 @@
|
|||||||
package org.moire.ultrasonic.util
|
package org.moire.ultrasonic.util
|
||||||
|
|
||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.app.AlertDialog
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
import org.moire.ultrasonic.R
|
import org.moire.ultrasonic.R
|
||||||
|
|
||||||
|
/*
|
||||||
|
* InfoDialog can be used to show some information to the user. Typically it cannot be cancelled,
|
||||||
|
* only dismissed via OK.
|
||||||
|
*/
|
||||||
open class InfoDialog(
|
open class InfoDialog(
|
||||||
context: Context,
|
context: Context,
|
||||||
message: CharSequence?,
|
message: CharSequence?,
|
||||||
@ -19,7 +23,7 @@ open class InfoDialog(
|
|||||||
private val finishActivityOnClose: Boolean = false
|
private val finishActivityOnClose: Boolean = false
|
||||||
) {
|
) {
|
||||||
|
|
||||||
open var builder: AlertDialog.Builder = Builder(activity ?: context, message)
|
open var builder: MaterialAlertDialogBuilder = Builder(activity ?: context, message)
|
||||||
|
|
||||||
fun show() {
|
fun show() {
|
||||||
builder.setOnCancelListener {
|
builder.setOnCancelListener {
|
||||||
@ -35,7 +39,7 @@ open class InfoDialog(
|
|||||||
builder.create().show()
|
builder.create().show()
|
||||||
}
|
}
|
||||||
|
|
||||||
class Builder(context: Context?) : AlertDialog.Builder(context) {
|
class Builder(context: Context) : MaterialAlertDialogBuilder(context) {
|
||||||
|
|
||||||
constructor(context: Context, message: CharSequence?) : this(context) {
|
constructor(context: Context, message: CharSequence?) : this(context) {
|
||||||
setMessage(message)
|
setMessage(message)
|
||||||
@ -44,7 +48,6 @@ open class InfoDialog(
|
|||||||
init {
|
init {
|
||||||
setIcon(R.drawable.ic_baseline_info)
|
setIcon(R.drawable.ic_baseline_info)
|
||||||
setTitle(R.string.common_confirm)
|
setTitle(R.string.common_confirm)
|
||||||
setCancelable(true)
|
|
||||||
setPositiveButton(R.string.common_ok) { _, _ ->
|
setPositiveButton(R.string.common_ok) { _, _ ->
|
||||||
// Just close it
|
// Just close it
|
||||||
}
|
}
|
||||||
@ -52,6 +55,10 @@ open class InfoDialog(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ErrorDialog can be used to show some an error to the user.
|
||||||
|
* Typically it cannot be cancelled, only dismissed via OK.
|
||||||
|
*/
|
||||||
class ErrorDialog(
|
class ErrorDialog(
|
||||||
context: Context,
|
context: Context,
|
||||||
message: CharSequence?,
|
message: CharSequence?,
|
||||||
@ -59,9 +66,9 @@ class ErrorDialog(
|
|||||||
finishActivityOnClose: Boolean = false
|
finishActivityOnClose: Boolean = false
|
||||||
) : InfoDialog(context, message, activity, finishActivityOnClose) {
|
) : InfoDialog(context, message, activity, finishActivityOnClose) {
|
||||||
|
|
||||||
override var builder: AlertDialog.Builder = Builder(activity ?: context, message)
|
override var builder: MaterialAlertDialogBuilder = Builder(activity ?: context, message)
|
||||||
|
|
||||||
class Builder(context: Context?) : AlertDialog.Builder(context) {
|
class Builder(context: Context) : MaterialAlertDialogBuilder(context) {
|
||||||
constructor(context: Context, message: CharSequence?) : this(context) {
|
constructor(context: Context, message: CharSequence?) : this(context) {
|
||||||
setMessage(message)
|
setMessage(message)
|
||||||
}
|
}
|
||||||
@ -69,7 +76,6 @@ class ErrorDialog(
|
|||||||
init {
|
init {
|
||||||
setIcon(R.drawable.ic_baseline_warning)
|
setIcon(R.drawable.ic_baseline_warning)
|
||||||
setTitle(R.string.error_label)
|
setTitle(R.string.error_label)
|
||||||
setCancelable(true)
|
|
||||||
setPositiveButton(R.string.common_ok) { _, _ ->
|
setPositiveButton(R.string.common_ok) { _, _ ->
|
||||||
// Just close it
|
// Just close it
|
||||||
}
|
}
|
||||||
@ -77,6 +83,10 @@ class ErrorDialog(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ConfirmationDialog can be used to present a choice to the user.
|
||||||
|
* Typically it will be cancelable..
|
||||||
|
*/
|
||||||
class ConfirmationDialog(
|
class ConfirmationDialog(
|
||||||
context: Context,
|
context: Context,
|
||||||
message: CharSequence?,
|
message: CharSequence?,
|
||||||
@ -84,9 +94,9 @@ class ConfirmationDialog(
|
|||||||
finishActivityOnClose: Boolean = false
|
finishActivityOnClose: Boolean = false
|
||||||
) : InfoDialog(context, message, activity, finishActivityOnClose) {
|
) : InfoDialog(context, message, activity, finishActivityOnClose) {
|
||||||
|
|
||||||
override var builder: AlertDialog.Builder = Builder(activity ?: context, message)
|
override var builder: MaterialAlertDialogBuilder = Builder(activity ?: context, message)
|
||||||
|
|
||||||
class Builder(context: Context?) : AlertDialog.Builder(context) {
|
class Builder(context: Context) : MaterialAlertDialogBuilder(context) {
|
||||||
constructor(context: Context, message: CharSequence?) : this(context) {
|
constructor(context: Context, message: CharSequence?) : this(context) {
|
||||||
setMessage(message)
|
setMessage(message)
|
||||||
}
|
}
|
||||||
|
@ -157,7 +157,6 @@
|
|||||||
<string name="settings.clear_bookmark">Zahodit záložku</string>
|
<string name="settings.clear_bookmark">Zahodit záložku</string>
|
||||||
<string name="settings.clear_bookmark_summary">Zahodit záložku po dokončení přehrávání skladby</string>
|
<string name="settings.clear_bookmark_summary">Zahodit záložku po dokončení přehrávání skladby</string>
|
||||||
<string name="settings.clear_search_history">Vyčistit historii vyhledávání</string>
|
<string name="settings.clear_search_history">Vyčistit historii vyhledávání</string>
|
||||||
<string name="settings.connection_failure">Chyba připojení.</string>
|
|
||||||
<string name="settings.default_albums">Výchozí alba</string>
|
<string name="settings.default_albums">Výchozí alba</string>
|
||||||
<string name="settings.default_artists">Výchozí umělci</string>
|
<string name="settings.default_artists">Výchozí umělci</string>
|
||||||
<string name="settings.default_songs">Výchozí skladby</string>
|
<string name="settings.default_songs">Výchozí skladby</string>
|
||||||
@ -248,8 +247,6 @@
|
|||||||
<string name="settings.show_track_number">Zobrazovat číslo skladby</string>
|
<string name="settings.show_track_number">Zobrazovat číslo skladby</string>
|
||||||
<string name="settings.show_track_number_summary">Připojovat číslo skladby při zobrazování skladby</string>
|
<string name="settings.show_track_number_summary">Připojovat číslo skladby při zobrazování skladby</string>
|
||||||
<string name="settings.test_connection_title">Test připojení</string>
|
<string name="settings.test_connection_title">Test připojení</string>
|
||||||
<string name="settings.testing_ok">Připojení je v pořádku</string>
|
|
||||||
<string name="settings.testing_unlicensed">Připojení je v pořádku. Server bez licence.</string>
|
|
||||||
<string name="settings.theme_light">Světlý</string>
|
<string name="settings.theme_light">Světlý</string>
|
||||||
<string name="settings.theme_dark">Tmavý</string>
|
<string name="settings.theme_dark">Tmavý</string>
|
||||||
<string name="settings.theme_black">Černý</string>
|
<string name="settings.theme_black">Černý</string>
|
||||||
|
@ -196,7 +196,6 @@
|
|||||||
<string name="settings.clear_bookmark">Lesezeichen löschen</string>
|
<string name="settings.clear_bookmark">Lesezeichen löschen</string>
|
||||||
<string name="settings.clear_bookmark_summary">Lesezeichen nach Wiedergabe löschen</string>
|
<string name="settings.clear_bookmark_summary">Lesezeichen nach Wiedergabe löschen</string>
|
||||||
<string name="settings.clear_search_history">Suchverlauf löschen</string>
|
<string name="settings.clear_search_history">Suchverlauf löschen</string>
|
||||||
<string name="settings.connection_failure">Verbindungsfehler</string>
|
|
||||||
<string name="settings.default_albums">Anzahl der Alben</string>
|
<string name="settings.default_albums">Anzahl der Alben</string>
|
||||||
<string name="settings.default_artists">Anzahl der Künstler*innen</string>
|
<string name="settings.default_artists">Anzahl der Künstler*innen</string>
|
||||||
<string name="settings.default_songs">Anzahl der Titel</string>
|
<string name="settings.default_songs">Anzahl der Titel</string>
|
||||||
@ -297,8 +296,6 @@
|
|||||||
<string name="settings.show_track_number">Titelnummer anzeigen</string>
|
<string name="settings.show_track_number">Titelnummer anzeigen</string>
|
||||||
<string name="settings.show_track_number_summary">Titel mit Nummer anzeigen</string>
|
<string name="settings.show_track_number_summary">Titel mit Nummer anzeigen</string>
|
||||||
<string name="settings.test_connection_title">Verbindung testen</string>
|
<string name="settings.test_connection_title">Verbindung testen</string>
|
||||||
<string name="settings.testing_ok">Verbindung OK</string>
|
|
||||||
<string name="settings.testing_unlicensed">Verbindung OK, Server nicht lizenziert.</string>
|
|
||||||
<string name="settings.theme_light">Hell</string>
|
<string name="settings.theme_light">Hell</string>
|
||||||
<string name="settings.theme_dark">Dunkel</string>
|
<string name="settings.theme_dark">Dunkel</string>
|
||||||
<string name="settings.theme_black">Schwarz</string>
|
<string name="settings.theme_black">Schwarz</string>
|
||||||
|
@ -198,7 +198,6 @@
|
|||||||
<string name="settings.clear_bookmark">Limpiar marcador</string>
|
<string name="settings.clear_bookmark">Limpiar marcador</string>
|
||||||
<string name="settings.clear_bookmark_summary">Limpiar marcador tras la finalización de la reproducción de una canción</string>
|
<string name="settings.clear_bookmark_summary">Limpiar marcador tras la finalización de la reproducción de una canción</string>
|
||||||
<string name="settings.clear_search_history">Limpiar el historial de búsqueda</string>
|
<string name="settings.clear_search_history">Limpiar el historial de búsqueda</string>
|
||||||
<string name="settings.connection_failure">Fallo de conexión.</string>
|
|
||||||
<string name="settings.default_albums">Álbumes predeterminados</string>
|
<string name="settings.default_albums">Álbumes predeterminados</string>
|
||||||
<string name="settings.default_artists">Artistas predeterminados</string>
|
<string name="settings.default_artists">Artistas predeterminados</string>
|
||||||
<string name="settings.default_songs">Canciones predeterminadas</string>
|
<string name="settings.default_songs">Canciones predeterminadas</string>
|
||||||
@ -299,8 +298,6 @@
|
|||||||
<string name="settings.show_track_number">Mostrar número de pista</string>
|
<string name="settings.show_track_number">Mostrar número de pista</string>
|
||||||
<string name="settings.show_track_number_summary">Incluir el número de pista cuando se muestre una canción</string>
|
<string name="settings.show_track_number_summary">Incluir el número de pista cuando se muestre una canción</string>
|
||||||
<string name="settings.test_connection_title">Comprobar conexión</string>
|
<string name="settings.test_connection_title">Comprobar conexión</string>
|
||||||
<string name="settings.testing_ok">La conexión es correcta</string>
|
|
||||||
<string name="settings.testing_unlicensed">La conexión es correcta. Servidor sin licencia.</string>
|
|
||||||
<string name="settings.theme_day_night">Día y noche</string>
|
<string name="settings.theme_day_night">Día y noche</string>
|
||||||
<string name="settings.theme_light">Claro</string>
|
<string name="settings.theme_light">Claro</string>
|
||||||
<string name="settings.theme_dark">Oscuro</string>
|
<string name="settings.theme_dark">Oscuro</string>
|
||||||
|
@ -194,7 +194,6 @@
|
|||||||
<string name="settings.clear_bookmark">Effacer le signet</string>
|
<string name="settings.clear_bookmark">Effacer le signet</string>
|
||||||
<string name="settings.clear_bookmark_summary">Effacer le signet à la fin de la lecture d\'un titre</string>
|
<string name="settings.clear_bookmark_summary">Effacer le signet à la fin de la lecture d\'un titre</string>
|
||||||
<string name="settings.clear_search_history">Effacer l\'historique des recherches</string>
|
<string name="settings.clear_search_history">Effacer l\'historique des recherches</string>
|
||||||
<string name="settings.connection_failure">Échec de la connexion</string>
|
|
||||||
<string name="settings.default_albums">Albums par défaut</string>
|
<string name="settings.default_albums">Albums par défaut</string>
|
||||||
<string name="settings.default_artists">Artistes par défaut</string>
|
<string name="settings.default_artists">Artistes par défaut</string>
|
||||||
<string name="settings.default_songs">Musiques par défaut</string>
|
<string name="settings.default_songs">Musiques par défaut</string>
|
||||||
@ -291,8 +290,6 @@
|
|||||||
<string name="settings.show_track_number">Afficher le numéro du titre</string>
|
<string name="settings.show_track_number">Afficher le numéro du titre</string>
|
||||||
<string name="settings.show_track_number_summary">Inclure son numéro lors de l\'affichage d\'un titre</string>
|
<string name="settings.show_track_number_summary">Inclure son numéro lors de l\'affichage d\'un titre</string>
|
||||||
<string name="settings.test_connection_title">Tester la connexion</string>
|
<string name="settings.test_connection_title">Tester la connexion</string>
|
||||||
<string name="settings.testing_ok">Connexion correcte</string>
|
|
||||||
<string name="settings.testing_unlicensed">Connexion correcte. Serveur sans licence.</string>
|
|
||||||
<string name="settings.theme_light">Clair</string>
|
<string name="settings.theme_light">Clair</string>
|
||||||
<string name="settings.theme_dark">Sombre</string>
|
<string name="settings.theme_dark">Sombre</string>
|
||||||
<string name="settings.theme_black">Noir</string>
|
<string name="settings.theme_black">Noir</string>
|
||||||
|
@ -163,7 +163,6 @@
|
|||||||
<string name="settings.clear_bookmark">Könyvjelző törlése</string>
|
<string name="settings.clear_bookmark">Könyvjelző törlése</string>
|
||||||
<string name="settings.clear_bookmark_summary">Könyvjelző törlése a dal lejátszása után.</string>
|
<string name="settings.clear_bookmark_summary">Könyvjelző törlése a dal lejátszása után.</string>
|
||||||
<string name="settings.clear_search_history">Keresési előzmények törlése</string>
|
<string name="settings.clear_search_history">Keresési előzmények törlése</string>
|
||||||
<string name="settings.connection_failure">Csatlakozási hiba!</string>
|
|
||||||
<string name="settings.default_albums">Albumok találati száma</string>
|
<string name="settings.default_albums">Albumok találati száma</string>
|
||||||
<string name="settings.default_artists">Előadók találati száma</string>
|
<string name="settings.default_artists">Előadók találati száma</string>
|
||||||
<string name="settings.default_songs">Dalok találati száma</string>
|
<string name="settings.default_songs">Dalok találati száma</string>
|
||||||
@ -256,8 +255,6 @@
|
|||||||
<string name="settings.show_track_number">Sorszám megjelenítése</string>
|
<string name="settings.show_track_number">Sorszám megjelenítése</string>
|
||||||
<string name="settings.show_track_number_summary">Dalok sorszámának megjelenítése.</string>
|
<string name="settings.show_track_number_summary">Dalok sorszámának megjelenítése.</string>
|
||||||
<string name="settings.test_connection_title">Kapcsolat tesztelése</string>
|
<string name="settings.test_connection_title">Kapcsolat tesztelése</string>
|
||||||
<string name="settings.testing_ok">Kapcsolat OK!</string>
|
|
||||||
<string name="settings.testing_unlicensed">Kapcsolat OK! A kiszolgálónak nincs licence!</string>
|
|
||||||
<string name="settings.theme_light">Világos</string>
|
<string name="settings.theme_light">Világos</string>
|
||||||
<string name="settings.theme_dark">Sötét</string>
|
<string name="settings.theme_dark">Sötét</string>
|
||||||
<string name="settings.theme_black">Fekete</string>
|
<string name="settings.theme_black">Fekete</string>
|
||||||
|
@ -154,7 +154,6 @@
|
|||||||
<string name="settings.clear_bookmark">Pulisci Segnalibro</string>
|
<string name="settings.clear_bookmark">Pulisci Segnalibro</string>
|
||||||
<string name="settings.clear_bookmark_summary">Pulisci segnalibro al completamento della riproduzione di una canzone</string>
|
<string name="settings.clear_bookmark_summary">Pulisci segnalibro al completamento della riproduzione di una canzone</string>
|
||||||
<string name="settings.clear_search_history">Pulisci Storico Ricerca</string>
|
<string name="settings.clear_search_history">Pulisci Storico Ricerca</string>
|
||||||
<string name="settings.connection_failure">Errore connessione.</string>
|
|
||||||
<string name="settings.default_albums">Album predefiniti</string>
|
<string name="settings.default_albums">Album predefiniti</string>
|
||||||
<string name="settings.default_artists">Artisti predefiniti</string>
|
<string name="settings.default_artists">Artisti predefiniti</string>
|
||||||
<string name="settings.default_songs">Canzoni predefinte</string>
|
<string name="settings.default_songs">Canzoni predefinte</string>
|
||||||
@ -240,8 +239,6 @@
|
|||||||
<string name="settings.show_track_number">Visualizza numero traccia</string>
|
<string name="settings.show_track_number">Visualizza numero traccia</string>
|
||||||
<string name="settings.show_track_number_summary">Includi numero traccia quando visualizzi una canzone</string>
|
<string name="settings.show_track_number_summary">Includi numero traccia quando visualizzi una canzone</string>
|
||||||
<string name="settings.test_connection_title">Prova Connessione</string>
|
<string name="settings.test_connection_title">Prova Connessione</string>
|
||||||
<string name="settings.testing_ok">Connessione OK</string>
|
|
||||||
<string name="settings.testing_unlicensed">Connessione OK. Server senza licenza.</string>
|
|
||||||
<string name="settings.theme_light">Chiaro</string>
|
<string name="settings.theme_light">Chiaro</string>
|
||||||
<string name="settings.theme_dark">Scuro</string>
|
<string name="settings.theme_dark">Scuro</string>
|
||||||
<string name="settings.theme_title">Tema</string>
|
<string name="settings.theme_title">Tema</string>
|
||||||
|
@ -198,7 +198,6 @@
|
|||||||
<string name="settings.clear_bookmark">Bladwijzer verwijderen</string>
|
<string name="settings.clear_bookmark">Bladwijzer verwijderen</string>
|
||||||
<string name="settings.clear_bookmark_summary">Bladwijzer verwijderen nadat nummer is afgespeeld</string>
|
<string name="settings.clear_bookmark_summary">Bladwijzer verwijderen nadat nummer is afgespeeld</string>
|
||||||
<string name="settings.clear_search_history">Zoekgeschiedenis wissen</string>
|
<string name="settings.clear_search_history">Zoekgeschiedenis wissen</string>
|
||||||
<string name="settings.connection_failure">Verbindingsfout.</string>
|
|
||||||
<string name="settings.default_albums">Standaardalbums</string>
|
<string name="settings.default_albums">Standaardalbums</string>
|
||||||
<string name="settings.default_artists">Standaardartiesten</string>
|
<string name="settings.default_artists">Standaardartiesten</string>
|
||||||
<string name="settings.default_songs">Standaardnummers</string>
|
<string name="settings.default_songs">Standaardnummers</string>
|
||||||
@ -299,8 +298,6 @@
|
|||||||
<string name="settings.show_track_number">Itemnummer tonen</string>
|
<string name="settings.show_track_number">Itemnummer tonen</string>
|
||||||
<string name="settings.show_track_number_summary">Itemnummer tonen tijdens tonen van nummers</string>
|
<string name="settings.show_track_number_summary">Itemnummer tonen tijdens tonen van nummers</string>
|
||||||
<string name="settings.test_connection_title">Verbinding testen</string>
|
<string name="settings.test_connection_title">Verbinding testen</string>
|
||||||
<string name="settings.testing_ok">Verbinding is goed</string>
|
|
||||||
<string name="settings.testing_unlicensed">Verbinding is goed; geen serverlicentie.</string>
|
|
||||||
<string name="settings.theme_day_night">Dag en nacht</string>
|
<string name="settings.theme_day_night">Dag en nacht</string>
|
||||||
<string name="settings.theme_light">Licht</string>
|
<string name="settings.theme_light">Licht</string>
|
||||||
<string name="settings.theme_dark">Donker</string>
|
<string name="settings.theme_dark">Donker</string>
|
||||||
|
@ -157,7 +157,6 @@
|
|||||||
<string name="settings.clear_bookmark">Czyszczenie zakładek</string>
|
<string name="settings.clear_bookmark">Czyszczenie zakładek</string>
|
||||||
<string name="settings.clear_bookmark_summary">Czyść zakładkę po zakończeniu odtwarzania utworu</string>
|
<string name="settings.clear_bookmark_summary">Czyść zakładkę po zakończeniu odtwarzania utworu</string>
|
||||||
<string name="settings.clear_search_history">Wyczyść historię wyszukiwania</string>
|
<string name="settings.clear_search_history">Wyczyść historię wyszukiwania</string>
|
||||||
<string name="settings.connection_failure">Błąd połączenia.</string>
|
|
||||||
<string name="settings.default_albums">Domyślna ilość wyników - albumy</string>
|
<string name="settings.default_albums">Domyślna ilość wyników - albumy</string>
|
||||||
<string name="settings.default_artists">Domyślna ilość wyników - artyści</string>
|
<string name="settings.default_artists">Domyślna ilość wyników - artyści</string>
|
||||||
<string name="settings.default_songs">Domyślna ilość wyników - utwory</string>
|
<string name="settings.default_songs">Domyślna ilość wyników - utwory</string>
|
||||||
@ -248,8 +247,6 @@
|
|||||||
<string name="settings.show_track_number">Wyświetlaj numer utworu</string>
|
<string name="settings.show_track_number">Wyświetlaj numer utworu</string>
|
||||||
<string name="settings.show_track_number_summary">Dołącza numer utworu podczas wyświetlania utworu</string>
|
<string name="settings.show_track_number_summary">Dołącza numer utworu podczas wyświetlania utworu</string>
|
||||||
<string name="settings.test_connection_title">Testuj połączenie</string>
|
<string name="settings.test_connection_title">Testuj połączenie</string>
|
||||||
<string name="settings.testing_ok">Połączenie jest OK</string>
|
|
||||||
<string name="settings.testing_unlicensed">Połączenie jest OK. Brak licencji na serwerze.</string>
|
|
||||||
<string name="settings.theme_light">Jasny</string>
|
<string name="settings.theme_light">Jasny</string>
|
||||||
<string name="settings.theme_dark">Ciemny</string>
|
<string name="settings.theme_dark">Ciemny</string>
|
||||||
<string name="settings.theme_title">Motyw</string>
|
<string name="settings.theme_title">Motyw</string>
|
||||||
|
@ -196,7 +196,6 @@
|
|||||||
<string name="settings.clear_bookmark">Limpar Favoritos</string>
|
<string name="settings.clear_bookmark">Limpar Favoritos</string>
|
||||||
<string name="settings.clear_bookmark_summary">Limpar favoritos após terminar de tocar a música</string>
|
<string name="settings.clear_bookmark_summary">Limpar favoritos após terminar de tocar a música</string>
|
||||||
<string name="settings.clear_search_history">Limpar Histórico de Pesquisas</string>
|
<string name="settings.clear_search_history">Limpar Histórico de Pesquisas</string>
|
||||||
<string name="settings.connection_failure">Falha na conexão.</string>
|
|
||||||
<string name="settings.default_albums">Álbuns Padrões</string>
|
<string name="settings.default_albums">Álbuns Padrões</string>
|
||||||
<string name="settings.default_artists">Artistas Padrões</string>
|
<string name="settings.default_artists">Artistas Padrões</string>
|
||||||
<string name="settings.default_songs">Músicas Padrões</string>
|
<string name="settings.default_songs">Músicas Padrões</string>
|
||||||
@ -297,8 +296,6 @@
|
|||||||
<string name="settings.show_track_number">Mostrar o Número da Faixa</string>
|
<string name="settings.show_track_number">Mostrar o Número da Faixa</string>
|
||||||
<string name="settings.show_track_number_summary">Incluir o número da faixa ao mostrar uma música</string>
|
<string name="settings.show_track_number_summary">Incluir o número da faixa ao mostrar uma música</string>
|
||||||
<string name="settings.test_connection_title">Teste de Conexão</string>
|
<string name="settings.test_connection_title">Teste de Conexão</string>
|
||||||
<string name="settings.testing_ok">Conexão OK</string>
|
|
||||||
<string name="settings.testing_unlicensed">Conexão OK. Servidor não licenciado.</string>
|
|
||||||
<string name="settings.theme_day_night">Dia & Noite</string>
|
<string name="settings.theme_day_night">Dia & Noite</string>
|
||||||
<string name="settings.theme_light">Claro</string>
|
<string name="settings.theme_light">Claro</string>
|
||||||
<string name="settings.theme_dark">Escuro</string>
|
<string name="settings.theme_dark">Escuro</string>
|
||||||
|
@ -157,7 +157,6 @@
|
|||||||
<string name="settings.clear_bookmark">Limpar Favoritos</string>
|
<string name="settings.clear_bookmark">Limpar Favoritos</string>
|
||||||
<string name="settings.clear_bookmark_summary">Limpar favoritos após terminar de tocar a música</string>
|
<string name="settings.clear_bookmark_summary">Limpar favoritos após terminar de tocar a música</string>
|
||||||
<string name="settings.clear_search_history">Limpar Histórico de Pesquisas</string>
|
<string name="settings.clear_search_history">Limpar Histórico de Pesquisas</string>
|
||||||
<string name="settings.connection_failure">Falha na conexão.</string>
|
|
||||||
<string name="settings.default_albums">Álbuns Padrões</string>
|
<string name="settings.default_albums">Álbuns Padrões</string>
|
||||||
<string name="settings.default_artists">Artistas Padrões</string>
|
<string name="settings.default_artists">Artistas Padrões</string>
|
||||||
<string name="settings.default_songs">Músicas Padrões</string>
|
<string name="settings.default_songs">Músicas Padrões</string>
|
||||||
@ -248,8 +247,6 @@
|
|||||||
<string name="settings.show_track_number">Mostrar o Número da Faixa</string>
|
<string name="settings.show_track_number">Mostrar o Número da Faixa</string>
|
||||||
<string name="settings.show_track_number_summary">Incluir o número da faixa quando mostrando uma música</string>
|
<string name="settings.show_track_number_summary">Incluir o número da faixa quando mostrando uma música</string>
|
||||||
<string name="settings.test_connection_title">Teste de Conexão</string>
|
<string name="settings.test_connection_title">Teste de Conexão</string>
|
||||||
<string name="settings.testing_ok">Conexão OK</string>
|
|
||||||
<string name="settings.testing_unlicensed">Conexão OK. Servidor não licenciado.</string>
|
|
||||||
<string name="settings.theme_light">Claro</string>
|
<string name="settings.theme_light">Claro</string>
|
||||||
<string name="settings.theme_dark">Escuro</string>
|
<string name="settings.theme_dark">Escuro</string>
|
||||||
<string name="settings.theme_title">Tema</string>
|
<string name="settings.theme_title">Tema</string>
|
||||||
|
@ -181,7 +181,6 @@
|
|||||||
<string name="settings.clear_bookmark">Очистить закладку</string>
|
<string name="settings.clear_bookmark">Очистить закладку</string>
|
||||||
<string name="settings.clear_bookmark_summary">Очистить закладку после завершения воспроизведения песни</string>
|
<string name="settings.clear_bookmark_summary">Очистить закладку после завершения воспроизведения песни</string>
|
||||||
<string name="settings.clear_search_history">Очистить историю поиска</string>
|
<string name="settings.clear_search_history">Очистить историю поиска</string>
|
||||||
<string name="settings.connection_failure">Ошибка подключения.</string>
|
|
||||||
<string name="settings.default_albums">Альбомы по умолчанию</string>
|
<string name="settings.default_albums">Альбомы по умолчанию</string>
|
||||||
<string name="settings.default_artists">Исполнители по умолчанию</string>
|
<string name="settings.default_artists">Исполнители по умолчанию</string>
|
||||||
<string name="settings.default_songs">Треки по умолчанию</string>
|
<string name="settings.default_songs">Треки по умолчанию</string>
|
||||||
@ -274,8 +273,6 @@
|
|||||||
<string name="settings.show_track_number">Показать номер трека</string>
|
<string name="settings.show_track_number">Показать номер трека</string>
|
||||||
<string name="settings.show_track_number_summary">Включить номер дорожки при отображении песни</string>
|
<string name="settings.show_track_number_summary">Включить номер дорожки при отображении песни</string>
|
||||||
<string name="settings.test_connection_title">Тестовое соединение</string>
|
<string name="settings.test_connection_title">Тестовое соединение</string>
|
||||||
<string name="settings.testing_ok">Успешное соединение</string>
|
|
||||||
<string name="settings.testing_unlicensed">Успешное соединение. Сервер нелицензионный.</string>
|
|
||||||
<string name="settings.theme_light">Светлая</string>
|
<string name="settings.theme_light">Светлая</string>
|
||||||
<string name="settings.theme_dark">Темная</string>
|
<string name="settings.theme_dark">Темная</string>
|
||||||
<string name="settings.theme_black">Черная</string>
|
<string name="settings.theme_black">Черная</string>
|
||||||
|
@ -181,7 +181,6 @@
|
|||||||
<string name="settings.clear_bookmark">清空书签</string>
|
<string name="settings.clear_bookmark">清空书签</string>
|
||||||
<string name="settings.clear_bookmark_summary">歌曲播放完毕后清除书签</string>
|
<string name="settings.clear_bookmark_summary">歌曲播放完毕后清除书签</string>
|
||||||
<string name="settings.clear_search_history">清空搜索历史</string>
|
<string name="settings.clear_search_history">清空搜索历史</string>
|
||||||
<string name="settings.connection_failure">连接失败</string>
|
|
||||||
<string name="settings.default_albums">默认专辑</string>
|
<string name="settings.default_albums">默认专辑</string>
|
||||||
<string name="settings.default_artists">默认艺术家</string>
|
<string name="settings.default_artists">默认艺术家</string>
|
||||||
<string name="settings.default_songs">默认音乐</string>
|
<string name="settings.default_songs">默认音乐</string>
|
||||||
@ -277,8 +276,6 @@
|
|||||||
<string name="settings.show_track_number">显示曲目编号</string>
|
<string name="settings.show_track_number">显示曲目编号</string>
|
||||||
<string name="settings.show_track_number_summary">显示歌曲时包括曲目编号</string>
|
<string name="settings.show_track_number_summary">显示歌曲时包括曲目编号</string>
|
||||||
<string name="settings.test_connection_title">测试连接</string>
|
<string name="settings.test_connection_title">测试连接</string>
|
||||||
<string name="settings.testing_ok">连接正常</string>
|
|
||||||
<string name="settings.testing_unlicensed">连接正常, 服务器未授权。</string>
|
|
||||||
<string name="settings.theme_light">Light</string>
|
<string name="settings.theme_light">Light</string>
|
||||||
<string name="settings.theme_dark">Dark</string>
|
<string name="settings.theme_dark">Dark</string>
|
||||||
<string name="settings.theme_black">Black</string>
|
<string name="settings.theme_black">Black</string>
|
||||||
|
@ -198,7 +198,6 @@
|
|||||||
<string name="settings.clear_bookmark">Clear Bookmark</string>
|
<string name="settings.clear_bookmark">Clear Bookmark</string>
|
||||||
<string name="settings.clear_bookmark_summary">Clear bookmark upon completion of playback of a song</string>
|
<string name="settings.clear_bookmark_summary">Clear bookmark upon completion of playback of a song</string>
|
||||||
<string name="settings.clear_search_history">Clear Search History</string>
|
<string name="settings.clear_search_history">Clear Search History</string>
|
||||||
<string name="settings.connection_failure">Connection failure.</string>
|
|
||||||
<string name="settings.default_albums">Default Albums</string>
|
<string name="settings.default_albums">Default Albums</string>
|
||||||
<string name="settings.default_artists">Default Artists</string>
|
<string name="settings.default_artists">Default Artists</string>
|
||||||
<string name="settings.default_songs">Default Songs</string>
|
<string name="settings.default_songs">Default Songs</string>
|
||||||
@ -299,8 +298,6 @@
|
|||||||
<string name="settings.show_track_number">Show Track Number</string>
|
<string name="settings.show_track_number">Show Track Number</string>
|
||||||
<string name="settings.show_track_number_summary">Include track number when displaying a song</string>
|
<string name="settings.show_track_number_summary">Include track number when displaying a song</string>
|
||||||
<string name="settings.test_connection_title">Test Connection</string>
|
<string name="settings.test_connection_title">Test Connection</string>
|
||||||
<string name="settings.testing_ok">Connection is OK</string>
|
|
||||||
<string name="settings.testing_unlicensed">Connection is OK. Server unlicensed.</string>
|
|
||||||
<string name="settings.theme_day_night">Day & Night</string>
|
<string name="settings.theme_day_night">Day & Night</string>
|
||||||
<string name="settings.theme_light">Light</string>
|
<string name="settings.theme_light">Light</string>
|
||||||
<string name="settings.theme_dark">Dark</string>
|
<string name="settings.theme_dark">Dark</string>
|
||||||
@ -446,11 +443,12 @@
|
|||||||
<!-- Subsonic features -->
|
<!-- Subsonic features -->
|
||||||
<string name="settings.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="settings.five_star_rating_description">Use five star rating system for songs instead of simply starring/unstarring items.</string>
|
<string name="settings.five_star_rating_description">Use five star rating system for songs instead of simply starring/unstarring items.</string>
|
||||||
|
|
||||||
<string name="settings.use_hw_offload_title">Use hardware playback (experimental)</string>
|
<string name="settings.use_hw_offload_title">Use hardware playback (experimental)</string>
|
||||||
<string name="settings.use_hw_offload_description">Try to play the media using the media decoder chip on your phone. This can improve battery usage.</string>
|
<string name="settings.use_hw_offload_description">Try to play the media using the media decoder chip on your phone. This can improve battery usage.</string>
|
||||||
<string name="list_view">List</string>
|
<string name="list_view">List</string>
|
||||||
<string name="grid_view">Cover</string>
|
<string name="grid_view">Cover</string>
|
||||||
|
<string name="supported_server_features">Supported features</string>
|
||||||
|
<string name="jukebox">Jukebox</string>
|
||||||
|
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user