Merge changes
This commit is contained in:
commit
3f570636dd
|
@ -7,7 +7,7 @@ ext.versions = [
|
||||||
|
|
||||||
navigation : "2.3.5",
|
navigation : "2.3.5",
|
||||||
gradlePlugin : "4.2.2",
|
gradlePlugin : "4.2.2",
|
||||||
androidxcore : "1.5.0",
|
androidxcore : "1.6.0",
|
||||||
ktlint : "0.37.1",
|
ktlint : "0.37.1",
|
||||||
ktlintGradle : "10.2.0",
|
ktlintGradle : "10.2.0",
|
||||||
detekt : "1.18.1",
|
detekt : "1.18.1",
|
||||||
|
@ -23,7 +23,7 @@ ext.versions = [
|
||||||
room : "2.3.0",
|
room : "2.3.0",
|
||||||
kotlin : "1.5.31",
|
kotlin : "1.5.31",
|
||||||
kotlinxCoroutines : "1.5.2-native-mt",
|
kotlinxCoroutines : "1.5.2-native-mt",
|
||||||
viewModelKtx : "2.2.0",
|
viewModelKtx : "2.3.0",
|
||||||
|
|
||||||
retrofit : "2.6.4",
|
retrofit : "2.6.4",
|
||||||
jackson : "2.9.5",
|
jackson : "2.9.5",
|
||||||
|
@ -35,7 +35,7 @@ ext.versions = [
|
||||||
junit4 : "4.13.2",
|
junit4 : "4.13.2",
|
||||||
junit5 : "5.8.1",
|
junit5 : "5.8.1",
|
||||||
mockito : "4.0.0",
|
mockito : "4.0.0",
|
||||||
mockitoKotlin : "3.2.0",
|
mockitoKotlin : "4.0.0",
|
||||||
kluent : "1.68",
|
kluent : "1.68",
|
||||||
apacheCodecs : "1.15",
|
apacheCodecs : "1.15",
|
||||||
robolectric : "4.6.1",
|
robolectric : "4.6.1",
|
||||||
|
|
|
@ -1,6 +1,20 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<issues format="5" by="lint 4.2.2" client="gradle" variant="release" version="4.2.2">
|
<issues format="5" by="lint 4.2.2" client="gradle" variant="release" version="4.2.2">
|
||||||
|
|
||||||
|
<issue
|
||||||
|
id="ObsoleteLintCustomCheck"
|
||||||
|
message="Lint found an issue registry (`androidx.appcompat.AppCompatIssueRegistry`) which is older than the current API level; these checks may not work correctly.

Recompile the checks against the latest version. Custom check API version is 7 (4.0), current lint API level is 8 (4.1)">
|
||||||
|
<location
|
||||||
|
file="../../../../.gradle/caches/transforms-3/2f10f1fe0ff7ab74304d702879de0789/transformed/appcompat-1.2.0/jars/lint.jar"/>
|
||||||
|
</issue>
|
||||||
|
|
||||||
|
<issue
|
||||||
|
id="ObsoleteLintCustomCheck"
|
||||||
|
message="Lint found an issue registry (`timber.lint.TimberIssueRegistry`) which is older than the current API level; these checks may not work correctly.

Recompile the checks against the latest version. Custom check API version is 1 (3.1), current lint API level is 8 (4.1)">
|
||||||
|
<location
|
||||||
|
file="../../../../.gradle/caches/transforms-3/e9d816753daf5450613abd98ccf3b80c/transformed/jetified-timber-4.7.1/jars/lint.jar"/>
|
||||||
|
</issue>
|
||||||
|
|
||||||
<issue
|
<issue
|
||||||
id="IncludeLayoutParam"
|
id="IncludeLayoutParam"
|
||||||
message="Layout parameter `layout_gravity` ignored unless both `layout_width` and `layout_height` are also specified on `<include>` tag"
|
message="Layout parameter `layout_gravity` ignored unless both `layout_width` and `layout_height` are also specified on `<include>` tag"
|
||||||
|
@ -188,39 +202,6 @@
|
||||||
column="5"/>
|
column="5"/>
|
||||||
</issue>
|
</issue>
|
||||||
|
|
||||||
<issue
|
|
||||||
id="UnusedResources"
|
|
||||||
message="The resource `R.drawable.btn_bg` appears to be unused"
|
|
||||||
errorLine1=" <item android:drawable="@color/ics_opaque" android:state_pressed="true"/>"
|
|
||||||
errorLine2="^">
|
|
||||||
<location
|
|
||||||
file="src/main/res/drawable/btn_bg.xml"
|
|
||||||
line="14"
|
|
||||||
column="1"/>
|
|
||||||
</issue>
|
|
||||||
|
|
||||||
<issue
|
|
||||||
id="UnusedResources"
|
|
||||||
message="The resource `R.color.ics_opaque` appears to be unused"
|
|
||||||
errorLine1=" <color name="ics_opaque">#8033b5e5</color>"
|
|
||||||
errorLine2=" ~~~~~~~~~~~~~~~~~">
|
|
||||||
<location
|
|
||||||
file="src/main/res/values/colors.xml"
|
|
||||||
line="9"
|
|
||||||
column="12"/>
|
|
||||||
</issue>
|
|
||||||
|
|
||||||
<issue
|
|
||||||
id="UnusedResources"
|
|
||||||
message="The resource `R.color.md__transparent` appears to be unused"
|
|
||||||
errorLine1=" <color name="md__transparent">#00000000</color>"
|
|
||||||
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~">
|
|
||||||
<location
|
|
||||||
file="src/main/res/values/colors.xml"
|
|
||||||
line="10"
|
|
||||||
column="12"/>
|
|
||||||
</issue>
|
|
||||||
|
|
||||||
<issue
|
<issue
|
||||||
id="UnusedResources"
|
id="UnusedResources"
|
||||||
message="The resource `R.drawable.ic_menu_arrow` appears to be unused"
|
message="The resource `R.drawable.ic_menu_arrow` appears to be unused"
|
||||||
|
@ -232,39 +213,6 @@
|
||||||
column="1"/>
|
column="1"/>
|
||||||
</issue>
|
</issue>
|
||||||
|
|
||||||
<issue
|
|
||||||
id="UnusedResources"
|
|
||||||
message="The resource `R.drawable.line` appears to be unused"
|
|
||||||
errorLine1="<shape xmlns:android="http://schemas.android.com/apk/res/android""
|
|
||||||
errorLine2="^">
|
|
||||||
<location
|
|
||||||
file="src/main/res/drawable/line.xml"
|
|
||||||
line="2"
|
|
||||||
column="1"/>
|
|
||||||
</issue>
|
|
||||||
|
|
||||||
<issue
|
|
||||||
id="UnusedResources"
|
|
||||||
message="The resource `R.drawable.line_drawable` appears to be unused"
|
|
||||||
errorLine1="<selector xmlns:android="http://schemas.android.com/apk/res/android">"
|
|
||||||
errorLine2="^">
|
|
||||||
<location
|
|
||||||
file="src/main/res/drawable/line_drawable.xml"
|
|
||||||
line="2"
|
|
||||||
column="1"/>
|
|
||||||
</issue>
|
|
||||||
|
|
||||||
<issue
|
|
||||||
id="UnusedResources"
|
|
||||||
message="The resource `R.menu.main` appears to be unused"
|
|
||||||
errorLine1="<menu"
|
|
||||||
errorLine2="^">
|
|
||||||
<location
|
|
||||||
file="src/main/res/menu/main.xml"
|
|
||||||
line="2"
|
|
||||||
column="1"/>
|
|
||||||
</issue>
|
|
||||||
|
|
||||||
<issue
|
<issue
|
||||||
id="UnusedResources"
|
id="UnusedResources"
|
||||||
message="The resource `R.drawable.menu_arrow` appears to be unused"
|
message="The resource `R.drawable.menu_arrow` appears to be unused"
|
||||||
|
@ -1730,28 +1678,6 @@
|
||||||
column="10"/>
|
column="10"/>
|
||||||
</issue>
|
</issue>
|
||||||
|
|
||||||
<issue
|
|
||||||
id="ContentDescription"
|
|
||||||
message="Missing `contentDescription` attribute on image"
|
|
||||||
errorLine1=" <ImageView"
|
|
||||||
errorLine2=" ~~~~~~~~~">
|
|
||||||
<location
|
|
||||||
file="src/main/res/layout/now_playing.xml"
|
|
||||||
line="18"
|
|
||||||
column="10"/>
|
|
||||||
</issue>
|
|
||||||
|
|
||||||
<issue
|
|
||||||
id="ContentDescription"
|
|
||||||
message="Missing `contentDescription` attribute on image"
|
|
||||||
errorLine1=" <ImageView"
|
|
||||||
errorLine2=" ~~~~~~~~~">
|
|
||||||
<location
|
|
||||||
file="src/main/res/layout/now_playing.xml"
|
|
||||||
line="55"
|
|
||||||
column="10"/>
|
|
||||||
</issue>
|
|
||||||
|
|
||||||
<issue
|
<issue
|
||||||
id="ContentDescription"
|
id="ContentDescription"
|
||||||
message="Missing `contentDescription` attribute on image"
|
message="Missing `contentDescription` attribute on image"
|
||||||
|
|
|
@ -2,8 +2,8 @@ package org.moire.ultrasonic.data
|
||||||
|
|
||||||
import androidx.lifecycle.MutableLiveData
|
import androidx.lifecycle.MutableLiveData
|
||||||
import androidx.room.Room
|
import androidx.room.Room
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.GlobalScope
|
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
|
@ -24,7 +24,7 @@ import timber.log.Timber
|
||||||
*/
|
*/
|
||||||
class ActiveServerProvider(
|
class ActiveServerProvider(
|
||||||
private val repository: ServerSettingDao
|
private val repository: ServerSettingDao
|
||||||
) {
|
) : CoroutineScope by CoroutineScope(Dispatchers.IO) {
|
||||||
private var cachedServer: ServerSetting? = null
|
private var cachedServer: ServerSetting? = null
|
||||||
private var cachedDatabase: MetaDatabase? = null
|
private var cachedDatabase: MetaDatabase? = null
|
||||||
private var cachedServerId: Int? = null
|
private var cachedServerId: Int? = null
|
||||||
|
@ -83,7 +83,7 @@ class ActiveServerProvider(
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
GlobalScope.launch(Dispatchers.IO) {
|
launch {
|
||||||
val serverId = repository.findByIndex(index)?.id ?: 0
|
val serverId = repository.findByIndex(index)?.id ?: 0
|
||||||
setActiveServerId(serverId)
|
setActiveServerId(serverId)
|
||||||
}
|
}
|
||||||
|
@ -133,7 +133,7 @@ class ActiveServerProvider(
|
||||||
* Sets the minimum Subsonic API version of the current server.
|
* Sets the minimum Subsonic API version of the current server.
|
||||||
*/
|
*/
|
||||||
fun setMinimumApiVersion(apiVersion: String) {
|
fun setMinimumApiVersion(apiVersion: String) {
|
||||||
GlobalScope.launch(Dispatchers.IO) {
|
launch {
|
||||||
if (cachedServer != null) {
|
if (cachedServer != null) {
|
||||||
cachedServer!!.minimumApiVersion = apiVersion
|
cachedServer!!.minimumApiVersion = apiVersion
|
||||||
repository.update(cachedServer!!)
|
repository.update(cachedServer!!)
|
||||||
|
|
|
@ -55,6 +55,7 @@ import java.io.File
|
||||||
/**
|
/**
|
||||||
* Shows main app settings.
|
* Shows main app settings.
|
||||||
*/
|
*/
|
||||||
|
@Suppress("TooManyFunctions")
|
||||||
class SettingsFragment :
|
class SettingsFragment :
|
||||||
PreferenceFragmentCompat(),
|
PreferenceFragmentCompat(),
|
||||||
OnSharedPreferenceChangeListener,
|
OnSharedPreferenceChangeListener,
|
||||||
|
|
|
@ -22,6 +22,7 @@ import org.moire.ultrasonic.domain.MusicDirectory
|
||||||
import org.moire.ultrasonic.util.FileUtil
|
import org.moire.ultrasonic.util.FileUtil
|
||||||
import org.moire.ultrasonic.util.StorageFile
|
import org.moire.ultrasonic.util.StorageFile
|
||||||
import org.moire.ultrasonic.util.Util
|
import org.moire.ultrasonic.util.Util
|
||||||
|
import org.moire.ultrasonic.util.Util.safeClose
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
|
@ -187,10 +188,10 @@ class ImageLoader(
|
||||||
outputStream = FileOutputStream(file)
|
outputStream = FileOutputStream(file)
|
||||||
outputStream.write(bytes)
|
outputStream.write(bytes)
|
||||||
} finally {
|
} finally {
|
||||||
Util.close(outputStream)
|
outputStream.safeClose()
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
Util.close(inputStream)
|
inputStream.safeClose()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,7 @@ import java.text.SimpleDateFormat
|
||||||
import java.util.Date
|
import java.util.Date
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
import org.moire.ultrasonic.util.FileUtil
|
import org.moire.ultrasonic.util.FileUtil
|
||||||
import org.moire.ultrasonic.util.Util
|
import org.moire.ultrasonic.util.Util.safeClose
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ class FileLoggerTree : Timber.DebugTree() {
|
||||||
// Using base class DebugTree here, we don't want to try to log this into file
|
// Using base class DebugTree here, we don't want to try to log this into file
|
||||||
super.log(6, TAG, String.format("Failed to write log to %s", file), x)
|
super.log(6, TAG, String.format("Failed to write log to %s", file), x)
|
||||||
} finally {
|
} finally {
|
||||||
if (writer != null) Util.close(writer)
|
writer.safeClose()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,7 @@ import org.moire.ultrasonic.util.StorageFile
|
||||||
import org.moire.ultrasonic.util.FileUtil
|
import org.moire.ultrasonic.util.FileUtil
|
||||||
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.Util.safeClose
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -133,9 +134,9 @@ class DownloadFile(
|
||||||
|
|
||||||
fun delete() {
|
fun delete() {
|
||||||
cancelDownload()
|
cancelDownload()
|
||||||
Util.delete(partialFile)
|
FileUtil.delete(partialFile)
|
||||||
Util.delete(completeFile)
|
FileUtil.delete(completeFile)
|
||||||
Util.delete(saveFile)
|
FileUtil.delete(saveFile)
|
||||||
|
|
||||||
Util.scanMedia(saveFile)
|
Util.scanMedia(saveFile)
|
||||||
}
|
}
|
||||||
|
@ -149,11 +150,11 @@ class DownloadFile(
|
||||||
fun cleanup(): Boolean {
|
fun cleanup(): Boolean {
|
||||||
var ok = true
|
var ok = true
|
||||||
if (StorageFile.isPathExists(completeFile) || StorageFile.isPathExists(saveFile)) {
|
if (StorageFile.isPathExists(completeFile) || StorageFile.isPathExists(saveFile)) {
|
||||||
ok = Util.delete(partialFile)
|
ok = FileUtil.delete(partialFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (StorageFile.isPathExists(saveFile)) {
|
if (StorageFile.isPathExists(saveFile)) {
|
||||||
ok = ok and Util.delete(completeFile)
|
ok = ok and FileUtil.delete(completeFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
return ok
|
return ok
|
||||||
|
@ -168,14 +169,14 @@ class DownloadFile(
|
||||||
private fun doPendingRename() {
|
private fun doPendingRename() {
|
||||||
try {
|
try {
|
||||||
if (saveWhenDone) {
|
if (saveWhenDone) {
|
||||||
Util.renameFile(completeFile, saveFile)
|
FileUtil.renameFile(completeFile, saveFile)
|
||||||
saveWhenDone = false
|
saveWhenDone = false
|
||||||
} else if (completeWhenDone) {
|
} else if (completeWhenDone) {
|
||||||
if (save) {
|
if (save) {
|
||||||
Util.renameFile(partialFile, saveFile)
|
FileUtil.renameFile(partialFile, saveFile)
|
||||||
Util.scanMedia(saveFile)
|
Util.scanMedia(saveFile)
|
||||||
} else {
|
} else {
|
||||||
Util.renameFile(partialFile, completeFile)
|
FileUtil.renameFile(partialFile, completeFile)
|
||||||
}
|
}
|
||||||
completeWhenDone = false
|
completeWhenDone = false
|
||||||
}
|
}
|
||||||
|
@ -207,7 +208,7 @@ class DownloadFile(
|
||||||
if (isPlaying) {
|
if (isPlaying) {
|
||||||
saveWhenDone = true
|
saveWhenDone = true
|
||||||
} else {
|
} else {
|
||||||
Util.renameFile(completeFile, saveFile)
|
FileUtil.renameFile(completeFile, saveFile)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Timber.i("%s already exists. Skipping.", completeFile)
|
Timber.i("%s already exists. Skipping.", completeFile)
|
||||||
|
@ -276,16 +277,16 @@ class DownloadFile(
|
||||||
completeWhenDone = true
|
completeWhenDone = true
|
||||||
} else {
|
} else {
|
||||||
if (save) {
|
if (save) {
|
||||||
Util.renameFile(partialFile, saveFile)
|
FileUtil.renameFile(partialFile, saveFile)
|
||||||
Util.scanMedia(saveFile)
|
Util.scanMedia(saveFile)
|
||||||
} else {
|
} else {
|
||||||
Util.renameFile(partialFile, completeFile)
|
FileUtil.renameFile(partialFile, completeFile)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (all: Exception) {
|
} catch (all: Exception) {
|
||||||
Util.close(outputStream)
|
outputStream.safeClose()
|
||||||
Util.delete(completeFile)
|
FileUtil.delete(completeFile)
|
||||||
Util.delete(saveFile)
|
FileUtil.delete(saveFile)
|
||||||
if (!isCancelled) {
|
if (!isCancelled) {
|
||||||
isFailed = true
|
isFailed = true
|
||||||
if (retryCount > 1) {
|
if (retryCount > 1) {
|
||||||
|
@ -298,8 +299,8 @@ class DownloadFile(
|
||||||
Timber.w(all, "Failed to download '%s'.", song)
|
Timber.w(all, "Failed to download '%s'.", song)
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
Util.close(inputStream)
|
inputStream.safeClose()
|
||||||
Util.close(outputStream)
|
outputStream.safeClose()
|
||||||
CacheCleaner().cleanSpace()
|
CacheCleaner().cleanSpace()
|
||||||
downloader.checkDownloads()
|
downloader.checkDownloads()
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,7 +39,7 @@ import org.moire.ultrasonic.domain.Share
|
||||||
import org.moire.ultrasonic.domain.UserInfo
|
import org.moire.ultrasonic.domain.UserInfo
|
||||||
import org.moire.ultrasonic.util.Constants
|
import org.moire.ultrasonic.util.Constants
|
||||||
import org.moire.ultrasonic.util.FileUtil
|
import org.moire.ultrasonic.util.FileUtil
|
||||||
import org.moire.ultrasonic.util.Util
|
import org.moire.ultrasonic.util.Util.safeClose
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.io.FileReader
|
import java.io.FileReader
|
||||||
import java.io.FileWriter
|
import java.io.FileWriter
|
||||||
|
@ -215,8 +215,8 @@ class OfflineMusicService : MusicService, KoinComponent {
|
||||||
}
|
}
|
||||||
playlist
|
playlist
|
||||||
} finally {
|
} finally {
|
||||||
Util.close(buffer)
|
buffer.safeClose()
|
||||||
Util.close(reader)
|
reader.safeClose()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
package org.moire.ultrasonic.util
|
package org.moire.ultrasonic.util
|
||||||
|
|
||||||
import android.net.Uri
|
|
||||||
import android.os.AsyncTask
|
import android.os.AsyncTask
|
||||||
import android.os.StatFs
|
import android.os.StatFs
|
||||||
import android.system.Os
|
import android.system.Os
|
||||||
|
@ -16,7 +15,7 @@ import org.moire.ultrasonic.util.FileUtil.getPlaylistFile
|
||||||
import org.moire.ultrasonic.util.FileUtil.listFiles
|
import org.moire.ultrasonic.util.FileUtil.listFiles
|
||||||
import org.moire.ultrasonic.util.FileUtil.musicDirectory
|
import org.moire.ultrasonic.util.FileUtil.musicDirectory
|
||||||
import org.moire.ultrasonic.util.Settings.cacheSizeMB
|
import org.moire.ultrasonic.util.Settings.cacheSizeMB
|
||||||
import org.moire.ultrasonic.util.Util.delete
|
import org.moire.ultrasonic.util.FileUtil.delete
|
||||||
import org.moire.ultrasonic.util.Util.formatBytes
|
import org.moire.ultrasonic.util.Util.formatBytes
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
|
@ -60,9 +59,12 @@ class CacheCleaner {
|
||||||
Thread.currentThread().name = "BackgroundCleanup"
|
Thread.currentThread().name = "BackgroundCleanup"
|
||||||
val files: MutableList<StorageFile> = ArrayList()
|
val files: MutableList<StorageFile> = ArrayList()
|
||||||
val dirs: MutableList<StorageFile> = ArrayList()
|
val dirs: MutableList<StorageFile> = ArrayList()
|
||||||
|
|
||||||
findCandidatesForDeletion(musicDirectory, files, dirs)
|
findCandidatesForDeletion(musicDirectory, files, dirs)
|
||||||
|
|
||||||
sortByAscendingModificationTime(files)
|
sortByAscendingModificationTime(files)
|
||||||
val filesToNotDelete = findFilesToNotDelete()
|
val filesToNotDelete = findFilesToNotDelete()
|
||||||
|
|
||||||
deleteFiles(files, filesToNotDelete, getMinimumDelete(files), true)
|
deleteFiles(files, filesToNotDelete, getMinimumDelete(files), true)
|
||||||
deleteEmptyDirs(dirs, filesToNotDelete)
|
deleteEmptyDirs(dirs, filesToNotDelete)
|
||||||
} catch (all: RuntimeException) {
|
} catch (all: RuntimeException) {
|
||||||
|
@ -76,10 +78,14 @@ class CacheCleaner {
|
||||||
override fun doInBackground(vararg params: Void?): Void? {
|
override fun doInBackground(vararg params: Void?): Void? {
|
||||||
try {
|
try {
|
||||||
Thread.currentThread().name = "BackgroundSpaceCleanup"
|
Thread.currentThread().name = "BackgroundSpaceCleanup"
|
||||||
|
|
||||||
val files: MutableList<StorageFile> = ArrayList()
|
val files: MutableList<StorageFile> = ArrayList()
|
||||||
val dirs: MutableList<StorageFile> = ArrayList()
|
val dirs: MutableList<StorageFile> = ArrayList()
|
||||||
|
|
||||||
findCandidatesForDeletion(musicDirectory, files, dirs)
|
findCandidatesForDeletion(musicDirectory, files, dirs)
|
||||||
|
|
||||||
val bytesToDelete = getMinimumDelete(files)
|
val bytesToDelete = getMinimumDelete(files)
|
||||||
|
|
||||||
if (bytesToDelete > 0L) {
|
if (bytesToDelete > 0L) {
|
||||||
sortByAscendingModificationTime(files)
|
sortByAscendingModificationTime(files)
|
||||||
val filesToNotDelete = findFilesToNotDelete()
|
val filesToNotDelete = findFilesToNotDelete()
|
||||||
|
@ -99,12 +105,15 @@ class CacheCleaner {
|
||||||
ActiveServerProvider::class.java
|
ActiveServerProvider::class.java
|
||||||
)
|
)
|
||||||
Thread.currentThread().name = "BackgroundPlaylistsCleanup"
|
Thread.currentThread().name = "BackgroundPlaylistsCleanup"
|
||||||
|
|
||||||
val server = activeServerProvider.value.getActiveServer().name
|
val server = activeServerProvider.value.getActiveServer().name
|
||||||
val playlistFiles = listFiles(getPlaylistDirectory(server))
|
val playlistFiles = listFiles(getPlaylistDirectory(server))
|
||||||
val playlists = params[0]
|
val playlists = params[0]
|
||||||
|
|
||||||
for ((_, name) in playlists) {
|
for ((_, name) in playlists) {
|
||||||
playlistFiles.remove(getPlaylistFile(server, name))
|
playlistFiles.remove(getPlaylistFile(server, name))
|
||||||
}
|
}
|
||||||
|
|
||||||
for (playlist in playlistFiles) {
|
for (playlist in playlistFiles) {
|
||||||
playlist.delete()
|
playlist.delete()
|
||||||
}
|
}
|
||||||
|
@ -119,9 +128,8 @@ class CacheCleaner {
|
||||||
private const val MIN_FREE_SPACE = 500 * 1024L * 1024L
|
private const val MIN_FREE_SPACE = 500 * 1024L * 1024L
|
||||||
private fun deleteEmptyDirs(dirs: Iterable<StorageFile>, doNotDelete: Collection<String>) {
|
private fun deleteEmptyDirs(dirs: Iterable<StorageFile>, doNotDelete: Collection<String>) {
|
||||||
for (dir in dirs) {
|
for (dir in dirs) {
|
||||||
if (doNotDelete.contains(dir.getPath())) {
|
if (doNotDelete.contains(dir.getPath())) continue
|
||||||
continue
|
|
||||||
}
|
|
||||||
var children = dir.listFiles()
|
var children = dir.listFiles()
|
||||||
if (children != null) {
|
if (children != null) {
|
||||||
// No songs left in the folder
|
// No songs left in the folder
|
||||||
|
@ -140,11 +148,11 @@ class CacheCleaner {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getMinimumDelete(files: List<StorageFile>): Long {
|
private fun getMinimumDelete(files: List<StorageFile>): Long {
|
||||||
if (files.isEmpty()) {
|
if (files.isEmpty()) return 0L
|
||||||
return 0L
|
|
||||||
}
|
|
||||||
val cacheSizeBytes = cacheSizeMB * 1024L * 1024L
|
val cacheSizeBytes = cacheSizeMB * 1024L * 1024L
|
||||||
var bytesUsedBySubsonic = 0L
|
var bytesUsedBySubsonic = 0L
|
||||||
|
|
||||||
for (file in files) {
|
for (file in files) {
|
||||||
bytesUsedBySubsonic += file.length()
|
bytesUsedBySubsonic += file.length()
|
||||||
}
|
}
|
||||||
|
@ -154,6 +162,7 @@ class CacheCleaner {
|
||||||
val minFsAvailability: Long
|
val minFsAvailability: Long
|
||||||
val bytesTotalFs: Long
|
val bytesTotalFs: Long
|
||||||
val bytesAvailableFs: Long
|
val bytesAvailableFs: Long
|
||||||
|
|
||||||
if (files[0].isRawFile()) {
|
if (files[0].isRawFile()) {
|
||||||
val stat = StatFs(files[0].getRawFilePath())
|
val stat = StatFs(files[0].getRawFilePath())
|
||||||
bytesTotalFs = stat.blockCountLong * stat.blockSizeLong
|
bytesTotalFs = stat.blockCountLong * stat.blockSizeLong
|
||||||
|
@ -169,6 +178,7 @@ class CacheCleaner {
|
||||||
minFsAvailability = bytesTotalFs - MIN_FREE_SPACE
|
minFsAvailability = bytesTotalFs - MIN_FREE_SPACE
|
||||||
descriptor.close()
|
descriptor.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
val bytesToDeleteCacheLimit = (bytesUsedBySubsonic - cacheSizeBytes).coerceAtLeast(0L)
|
val bytesToDeleteCacheLimit = (bytesUsedBySubsonic - cacheSizeBytes).coerceAtLeast(0L)
|
||||||
val bytesToDeleteFsLimit = (bytesUsedFs - minFsAvailability).coerceAtLeast(0L)
|
val bytesToDeleteFsLimit = (bytesUsedFs - minFsAvailability).coerceAtLeast(0L)
|
||||||
val bytesToDelete = bytesToDeleteCacheLimit.coerceAtLeast(bytesToDeleteFsLimit)
|
val bytesToDelete = bytesToDeleteCacheLimit.coerceAtLeast(bytesToDeleteFsLimit)
|
||||||
|
@ -181,6 +191,7 @@ class CacheCleaner {
|
||||||
Timber.i("Cache limit : %s", formatBytes(cacheSizeBytes))
|
Timber.i("Cache limit : %s", formatBytes(cacheSizeBytes))
|
||||||
Timber.i("Cache size before : %s", formatBytes(bytesUsedBySubsonic))
|
Timber.i("Cache size before : %s", formatBytes(bytesUsedBySubsonic))
|
||||||
Timber.i("Minimum to delete : %s", formatBytes(bytesToDelete))
|
Timber.i("Minimum to delete : %s", formatBytes(bytesToDelete))
|
||||||
|
|
||||||
return bytesToDelete
|
return bytesToDelete
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -203,6 +214,7 @@ class CacheCleaner {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var bytesDeleted = 0L
|
var bytesDeleted = 0L
|
||||||
|
|
||||||
for (file in files) {
|
for (file in files) {
|
||||||
if (!deletePartials && bytesDeleted > bytesToDelete) break
|
if (!deletePartials && bytesDeleted > bytesToDelete) break
|
||||||
if (bytesToDelete > bytesDeleted || deletePartials && isPartial(file)) {
|
if (bytesToDelete > bytesDeleted || deletePartials && isPartial(file)) {
|
||||||
|
@ -244,10 +256,12 @@ class CacheCleaner {
|
||||||
val downloader = inject<Downloader>(
|
val downloader = inject<Downloader>(
|
||||||
Downloader::class.java
|
Downloader::class.java
|
||||||
)
|
)
|
||||||
|
|
||||||
for (downloadFile in downloader.value.all) {
|
for (downloadFile in downloader.value.all) {
|
||||||
filesToNotDelete.add(downloadFile.partialFile)
|
filesToNotDelete.add(downloadFile.partialFile)
|
||||||
filesToNotDelete.add(downloadFile.completeOrSaveFile)
|
filesToNotDelete.add(downloadFile.completeOrSaveFile)
|
||||||
}
|
}
|
||||||
|
|
||||||
filesToNotDelete.add(musicDirectory.getPath())
|
filesToNotDelete.add(musicDirectory.getPath())
|
||||||
return filesToNotDelete
|
return filesToNotDelete
|
||||||
}
|
}
|
||||||
|
|
|
@ -26,9 +26,11 @@ import java.util.TreeSet
|
||||||
import java.util.regex.Pattern
|
import java.util.regex.Pattern
|
||||||
import org.moire.ultrasonic.app.UApp
|
import org.moire.ultrasonic.app.UApp
|
||||||
import org.moire.ultrasonic.domain.MusicDirectory
|
import org.moire.ultrasonic.domain.MusicDirectory
|
||||||
|
import org.moire.ultrasonic.util.Util.safeClose
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
|
@Suppress("TooManyFunctions")
|
||||||
object FileUtil {
|
object FileUtil {
|
||||||
|
|
||||||
private val FILE_SYSTEM_UNSAFE = arrayOf("/", "\\", "..", ":", "\"", "?", "*", "<", ">", "|")
|
private val FILE_SYSTEM_UNSAFE = arrayOf("/", "\\", "..", ":", "\"", "?", "*", "<", ">", "|")
|
||||||
|
@ -423,7 +425,7 @@ object FileUtil {
|
||||||
Timber.w("Failed to serialize object to %s", file)
|
Timber.w("Failed to serialize object to %s", file)
|
||||||
false
|
false
|
||||||
} finally {
|
} finally {
|
||||||
Util.close(out)
|
out.safeClose()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -445,7 +447,7 @@ object FileUtil {
|
||||||
Timber.w(all, "Failed to deserialize object from %s", file)
|
Timber.w(all, "Failed to deserialize object from %s", file)
|
||||||
null
|
null
|
||||||
} finally {
|
} finally {
|
||||||
Util.close(inStream)
|
inStream.safeClose()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -473,8 +475,39 @@ object FileUtil {
|
||||||
Timber.w("Failed to save playlist: %s", name)
|
Timber.w("Failed to save playlist: %s", name)
|
||||||
throw e
|
throw e
|
||||||
} finally {
|
} finally {
|
||||||
bw.close()
|
bw.safeClose()
|
||||||
fw.close()
|
fw.safeClose()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
@Throws(IOException::class)
|
||||||
|
fun renameFile(from: String, to: String) {
|
||||||
|
StorageFile.rename(from, to)
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun delete(file: File?): Boolean {
|
||||||
|
if (file != null && file.exists()) {
|
||||||
|
if (!file.delete()) {
|
||||||
|
Timber.w("Failed to delete file %s", file)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
Timber.i("Deleted file %s", file)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@JvmStatic
|
||||||
|
fun delete(file: String?): Boolean {
|
||||||
|
if (file != null && StorageFile.isPathExists(file)) {
|
||||||
|
if (!StorageFile.getFromPath(file).delete()) {
|
||||||
|
Timber.w("Failed to delete file %s", file)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
Timber.i("Deleted file %s", file)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,6 @@ package org.moire.ultrasonic.util
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.net.ConnectivityManager
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import java.util.regex.Pattern
|
import java.util.regex.Pattern
|
||||||
|
@ -70,16 +69,20 @@ object Settings {
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
val maxBitRate: Int
|
val maxBitRate: Int
|
||||||
get() {
|
get() {
|
||||||
val manager = Util.getConnectivityManager()
|
val network = Util.networkInfo()
|
||||||
val networkInfo = manager.activeNetworkInfo ?: return 0
|
|
||||||
val wifi = networkInfo.type == ConnectivityManager.TYPE_WIFI
|
if (!network.connected) return 0
|
||||||
val preferences = preferences
|
|
||||||
return preferences.getString(
|
if (network.unmetered) {
|
||||||
if (wifi) Constants.PREFERENCES_KEY_MAX_BITRATE_WIFI
|
return maxWifiBitRate
|
||||||
else Constants.PREFERENCES_KEY_MAX_BITRATE_MOBILE,
|
} else {
|
||||||
"0"
|
return maxMobileBitRate
|
||||||
)!!.toInt()
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var maxWifiBitRate by StringIntSetting(Constants.PREFERENCES_KEY_MAX_BITRATE_WIFI)
|
||||||
|
|
||||||
|
private var maxMobileBitRate by StringIntSetting(Constants.PREFERENCES_KEY_MAX_BITRATE_MOBILE)
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
val preloadCount: Int
|
val preloadCount: Int
|
||||||
|
|
|
@ -3,6 +3,7 @@ package org.moire.ultrasonic.util
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import java.io.PrintWriter
|
import java.io.PrintWriter
|
||||||
|
import org.moire.ultrasonic.util.Util.safeClose
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
|
@ -34,7 +35,7 @@ class SubsonicUncaughtExceptionHandler(
|
||||||
} catch (x: Throwable) {
|
} catch (x: Throwable) {
|
||||||
Timber.e(x, "Failed to write stack trace to %s", file)
|
Timber.e(x, "Failed to write stack trace to %s", file)
|
||||||
} finally {
|
} finally {
|
||||||
Util.close(printWriter)
|
printWriter.safeClose()
|
||||||
defaultHandler?.uncaughtException(thread, throwable)
|
defaultHandler?.uncaughtException(thread, throwable)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,9 +23,13 @@ import android.graphics.drawable.BitmapDrawable
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.media.MediaScannerConnection
|
import android.media.MediaScannerConnection
|
||||||
import android.net.ConnectivityManager
|
import android.net.ConnectivityManager
|
||||||
|
import android.net.Network
|
||||||
|
import android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET
|
||||||
|
import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_METERED
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.net.wifi.WifiManager
|
import android.net.wifi.WifiManager
|
||||||
import android.net.wifi.WifiManager.WifiLock
|
import android.net.wifi.WifiManager.WifiLock
|
||||||
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.os.Environment
|
import android.os.Environment
|
||||||
import android.os.Parcelable
|
import android.os.Parcelable
|
||||||
|
@ -40,6 +44,7 @@ import androidx.annotation.AnyRes
|
||||||
import androidx.media.utils.MediaConstants
|
import androidx.media.utils.MediaConstants
|
||||||
import java.io.Closeable
|
import java.io.Closeable
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
|
import java.io.File
|
||||||
import java.io.UnsupportedEncodingException
|
import java.io.UnsupportedEncodingException
|
||||||
import java.security.MessageDigest
|
import java.security.MessageDigest
|
||||||
import java.text.DecimalFormat
|
import java.text.DecimalFormat
|
||||||
|
@ -57,7 +62,6 @@ import org.moire.ultrasonic.domain.PlayerState
|
||||||
import org.moire.ultrasonic.domain.SearchResult
|
import org.moire.ultrasonic.domain.SearchResult
|
||||||
import org.moire.ultrasonic.service.DownloadFile
|
import org.moire.ultrasonic.service.DownloadFile
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
private const val LINE_LENGTH = 60
|
private const val LINE_LENGTH = 60
|
||||||
private const val DEGRADE_PRECISION_AFTER = 10
|
private const val DEGRADE_PRECISION_AFTER = 10
|
||||||
|
@ -98,7 +102,7 @@ object Util {
|
||||||
when (Settings.theme.lowercase()) {
|
when (Settings.theme.lowercase()) {
|
||||||
Constants.PREFERENCES_KEY_THEME_DARK,
|
Constants.PREFERENCES_KEY_THEME_DARK,
|
||||||
"fullscreen" -> {
|
"fullscreen" -> {
|
||||||
context!!.setTheme(R.style.UltrasonicTheme)
|
context!!.setTheme(R.style.UltrasonicTheme_Dark)
|
||||||
}
|
}
|
||||||
Constants.PREFERENCES_KEY_THEME_BLACK -> {
|
Constants.PREFERENCES_KEY_THEME_BLACK -> {
|
||||||
context!!.setTheme(R.style.UltrasonicTheme_Black)
|
context!!.setTheme(R.style.UltrasonicTheme_Black)
|
||||||
|
@ -110,44 +114,6 @@ object Util {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
@Throws(IOException::class)
|
|
||||||
fun renameFile(from: String, to: String) {
|
|
||||||
StorageFile.rename(from, to)
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun close(closeable: Closeable?) {
|
|
||||||
try {
|
|
||||||
closeable?.close()
|
|
||||||
} catch (_: Throwable) {
|
|
||||||
// Ignored
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmStatic
|
|
||||||
fun delete(file: String?): Boolean {
|
|
||||||
if (file != null && StorageFile.isPathExists(file)) {
|
|
||||||
if (!StorageFile.getFromPath(file).delete()) {
|
|
||||||
Timber.w("Failed to delete file %s", file)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
Timber.i("Deleted file %s", file)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
fun delete(file: File?): Boolean {
|
|
||||||
if (file != null && file.exists()) {
|
|
||||||
if (!file.delete()) {
|
|
||||||
Timber.w("Failed to delete file %s", file)
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
Timber.i("Deleted file %s", file)
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@JvmOverloads
|
@JvmOverloads
|
||||||
fun toast(context: Context?, messageId: Int, shortDuration: Boolean = true) {
|
fun toast(context: Context?, messageId: Int, shortDuration: Boolean = true) {
|
||||||
|
@ -356,14 +322,45 @@ object Util {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a usable network for downloading media is available
|
||||||
|
*
|
||||||
|
* @return Boolean
|
||||||
|
*/
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun isNetworkConnected(): Boolean {
|
fun isNetworkConnected(): Boolean {
|
||||||
val manager = getConnectivityManager()
|
val info = networkInfo()
|
||||||
val networkInfo = manager.activeNetworkInfo
|
val isUnmetered = info.unmetered
|
||||||
val connected = networkInfo != null && networkInfo.isConnected
|
|
||||||
val wifiConnected = connected && networkInfo!!.type == ConnectivityManager.TYPE_WIFI
|
|
||||||
val wifiRequired = Settings.isWifiRequiredForDownload
|
val wifiRequired = Settings.isWifiRequiredForDownload
|
||||||
return connected && (!wifiRequired || wifiConnected)
|
return info.connected && (!wifiRequired || isUnmetered)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Query connectivity status
|
||||||
|
*
|
||||||
|
* @return NetworkInfo object
|
||||||
|
*/
|
||||||
|
@Suppress("DEPRECATION")
|
||||||
|
fun networkInfo(): NetworkInfo {
|
||||||
|
val manager = getConnectivityManager()
|
||||||
|
val info = NetworkInfo()
|
||||||
|
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
val network: Network? = manager.activeNetwork
|
||||||
|
val capabilities = manager.getNetworkCapabilities(network)
|
||||||
|
|
||||||
|
if (capabilities != null) {
|
||||||
|
info.unmetered = capabilities.hasCapability(NET_CAPABILITY_NOT_METERED)
|
||||||
|
info.connected = capabilities.hasCapability(NET_CAPABILITY_INTERNET)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
val networkInfo = manager.activeNetworkInfo
|
||||||
|
if (networkInfo != null) {
|
||||||
|
info.unmetered = networkInfo.type == ConnectivityManager.TYPE_WIFI
|
||||||
|
info.connected = networkInfo.isConnected
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return info
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
|
@ -904,4 +901,23 @@ object Util {
|
||||||
val context = appContext()
|
val context = appContext()
|
||||||
return context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
return context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Small data class to store information about the current network
|
||||||
|
**/
|
||||||
|
data class NetworkInfo(
|
||||||
|
var connected: Boolean = false,
|
||||||
|
var unmetered: Boolean = false
|
||||||
|
)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes a Closeable while ignoring any errors.
|
||||||
|
**/
|
||||||
|
fun Closeable?.safeClose() {
|
||||||
|
try {
|
||||||
|
this?.close()
|
||||||
|
} catch (_: Exception) {
|
||||||
|
// Ignored
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!--
|
|
||||||
Copyright (C) 2010 The Android Open Source Project 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.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<selector xmlns:android="http://schemas.android.com/apk/res/android" android:exitFadeDuration="@android:integer/config_mediumAnimTime">
|
|
||||||
|
|
||||||
<item android:drawable="@color/ics_opaque" android:state_pressed="true"/>
|
|
||||||
<item android:drawable="@color/ics_opaque" android:state_enabled="true" android:state_focused="true"/>
|
|
||||||
|
|
||||||
</selector>
|
|
|
@ -4,8 +4,8 @@
|
||||||
|
|
||||||
<gradient
|
<gradient
|
||||||
android:angle="90"
|
android:angle="90"
|
||||||
android:endColor="#00000000"
|
android:endColor="@android:color/transparent"
|
||||||
android:startColor="#80000000"
|
android:startColor="#73111111"
|
||||||
android:type="linear" />
|
android:type="linear" />
|
||||||
|
|
||||||
</shape>
|
</shape>
|
|
@ -1,12 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:shape="rectangle">
|
|
||||||
|
|
||||||
<solid android:color="@android:color/darker_gray" />
|
|
||||||
|
|
||||||
<padding
|
|
||||||
android:top="10dp"
|
|
||||||
android:left="10dp"
|
|
||||||
android:right="10dp"
|
|
||||||
android:bottom="10dp"/>
|
|
||||||
</shape>
|
|
|
@ -1,9 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<item
|
|
||||||
android:state_pressed="true"
|
|
||||||
android:drawable="@drawable/line"/>
|
|
||||||
|
|
||||||
<item
|
|
||||||
android:drawable="@drawable/line"/>
|
|
||||||
</selector>
|
|
|
@ -21,7 +21,8 @@
|
||||||
android:layout_height="64.0dip"
|
android:layout_height="64.0dip"
|
||||||
android:focusable="true"
|
android:focusable="true"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:layout_marginStart="6dp" />
|
android:layout_marginStart="6dp"
|
||||||
|
android:importantForAccessibility="no"/>
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="0.0dp"
|
android:layout_width="0.0dp"
|
||||||
|
@ -62,7 +63,8 @@
|
||||||
android:layout_weight="0.0"
|
android:layout_weight="0.0"
|
||||||
android:focusable="false"
|
android:focusable="false"
|
||||||
android:scaleType="fitCenter"
|
android:scaleType="fitCenter"
|
||||||
android:src="?attr/media_pause" />
|
android:src="?attr/media_pause"
|
||||||
|
android:contentDescription="@string/buttons.play"/>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
a:id="@+id/select_album_art"
|
a:id="@+id/select_album_art"
|
||||||
a:layout_width="160dip"
|
a:layout_width="160dip"
|
||||||
a:layout_height="160dip"
|
a:layout_height="160dip"
|
||||||
a:layout_alignParentLeft="true"
|
a:layout_alignParentStart="true"
|
||||||
a:layout_alignParentTop="true"
|
a:layout_alignParentTop="true"
|
||||||
a:layout_marginEnd="10dip"
|
a:layout_marginEnd="10dip"
|
||||||
a:contentDescription="@null"
|
a:contentDescription="@null"
|
||||||
|
|
|
@ -26,10 +26,10 @@
|
||||||
a:layout_height="wrap_content"
|
a:layout_height="wrap_content"
|
||||||
a:layout_gravity="left|center_vertical"
|
a:layout_gravity="left|center_vertical"
|
||||||
a:layout_weight="1"
|
a:layout_weight="1"
|
||||||
a:drawablePadding="6dip"
|
a:drawablePadding="4dip"
|
||||||
a:ellipsize="end"
|
a:ellipsize="end"
|
||||||
a:paddingStart="4dip"
|
a:paddingStart="6dip"
|
||||||
a:paddingEnd="2dip"
|
a:paddingEnd="4dip"
|
||||||
a:singleLine="true"
|
a:singleLine="true"
|
||||||
a:textAppearance="?android:attr/textAppearanceMedium"/>
|
a:textAppearance="?android:attr/textAppearanceMedium"/>
|
||||||
|
|
||||||
|
@ -55,7 +55,8 @@
|
||||||
a:layout_gravity="left|center_vertical"
|
a:layout_gravity="left|center_vertical"
|
||||||
a:layout_weight="1"
|
a:layout_weight="1"
|
||||||
a:ellipsize="middle"
|
a:ellipsize="middle"
|
||||||
a:paddingStart="4dip"
|
a:paddingStart="1dip"
|
||||||
|
a:paddingEnd="4dip"
|
||||||
a:singleLine="true"
|
a:singleLine="true"
|
||||||
a:textAppearance="?android:attr/textAppearanceSmall"/>
|
a:textAppearance="?android:attr/textAppearanceSmall"/>
|
||||||
|
|
||||||
|
|
|
@ -1,13 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<menu
|
|
||||||
xmlns:a="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
>
|
|
||||||
|
|
||||||
<item
|
|
||||||
a:id="@+id/main_shuffle"
|
|
||||||
a:icon="?attr/media_shuffle"
|
|
||||||
app:showAsAction="ifRoom|withText"
|
|
||||||
a:title="@string/main.shuffle"/>
|
|
||||||
|
|
||||||
</menu>
|
|
|
@ -6,8 +6,6 @@
|
||||||
<color name="cyan">#0099cc</color>
|
<color name="cyan">#0099cc</color>
|
||||||
<color name="navigation_header_light">#6200EE</color>
|
<color name="navigation_header_light">#6200EE</color>
|
||||||
<color name="navigation_header_dark">#BB86FC</color>
|
<color name="navigation_header_dark">#BB86FC</color>
|
||||||
<color name="ics_opaque">#8033b5e5</color>
|
|
||||||
<color name="md__transparent">#00000000</color>
|
|
||||||
<color name="translucent">#80000000</color>
|
<color name="translucent">#80000000</color>
|
||||||
<color name="background_color_dark">#000000</color>
|
<color name="background_color_dark">#000000</color>
|
||||||
<color name="background_color_grey">#333333</color>
|
<color name="background_color_grey">#333333</color>
|
||||||
|
|
|
@ -339,8 +339,8 @@
|
||||||
<string name="settings.view_refresh_4000">4 seconds</string>
|
<string name="settings.view_refresh_4000">4 seconds</string>
|
||||||
<string name="settings.view_refresh_4500">4.5 seconds</string>
|
<string name="settings.view_refresh_4500">4.5 seconds</string>
|
||||||
<string name="settings.view_refresh_5000">5 seconds</string>
|
<string name="settings.view_refresh_5000">5 seconds</string>
|
||||||
<string name="settings.wifi_required_summary">Only stream media if connected to Wi-Fi</string>
|
<string name="settings.wifi_required_summary">Only download media on unmetered connections</string>
|
||||||
<string name="settings.wifi_required_title">Wi-Fi Streaming Only</string>
|
<string name="settings.wifi_required_title">Download on Wi-Fi only</string>
|
||||||
<string name="song_details.all">%1$s%2$s</string>
|
<string name="song_details.all">%1$s%2$s</string>
|
||||||
<string name="song_details.kbps">%d kbps</string>
|
<string name="song_details.kbps">%d kbps</string>
|
||||||
<string name="util.bytes_format.byte">0 B</string>
|
<string name="util.bytes_format.byte">0 B</string>
|
||||||
|
|
|
@ -64,7 +64,7 @@
|
||||||
<item name="list_selector_holo_selected">@drawable/list_selector_holo_dark_selected</item>
|
<item name="list_selector_holo_selected">@drawable/list_selector_holo_dark_selected</item>
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<style name="UltrasonicTheme" parent="Theme.MaterialComponents">
|
<style name="UltrasonicTheme.Dark" parent="Theme.MaterialComponents">
|
||||||
<item name="windowActionBar">false</item>
|
<item name="windowActionBar">false</item>
|
||||||
<item name="windowNoTitle">true</item>
|
<item name="windowNoTitle">true</item>
|
||||||
<item name="color_background">@color/background_color_grey</item>
|
<item name="color_background">@color/background_color_grey</item>
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
a:title="@string/settings.server_scaling_title"
|
a:title="@string/settings.server_scaling_title"
|
||||||
app:iconSpaceReserved="false"/>
|
app:iconSpaceReserved="false"/>
|
||||||
<CheckBoxPreference
|
<CheckBoxPreference
|
||||||
a:defaultValue="true"
|
a:defaultValue="false"
|
||||||
a:key="displayBitrateWithArtist"
|
a:key="displayBitrateWithArtist"
|
||||||
a:summary="@string/settings.display_bitrate_summary"
|
a:summary="@string/settings.display_bitrate_summary"
|
||||||
a:title="@string/settings.display_bitrate"
|
a:title="@string/settings.display_bitrate"
|
||||||
|
|
Loading…
Reference in New Issue