mirror of
https://gitlab.com/ultrasonic/ultrasonic.git
synced 2025-07-23 20:01:56 +03:00
Compare commits
2 Commits
404c7c05d5
...
3a39902c4c
Author | SHA1 | Date | |
---|---|---|---|
|
3a39902c4c | ||
|
88364b15d6 |
8
fastlane/metadata/android/en-US/changelogs/117.txt
Normal file
8
fastlane/metadata/android/en-US/changelogs/117.txt
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
Bug fixes
|
||||||
|
- Fix more exceptions
|
||||||
|
|
||||||
|
Changes since 4.2.0
|
||||||
|
- #827: Make app full compliant Android Auto to publish in Play Store.
|
||||||
|
- #878: "Play shuffled" option for playlists always begins with the first track.
|
||||||
|
- #891: Dump config to log file when logging is enabled.
|
||||||
|
- #854: Remove Videos menu option for servers which don't support it.
|
@ -9,7 +9,7 @@ ktlint = "0.43.2"
|
|||||||
ktlintGradle = "11.3.1"
|
ktlintGradle = "11.3.1"
|
||||||
detekt = "1.22.0"
|
detekt = "1.22.0"
|
||||||
preferences = "1.2.0"
|
preferences = "1.2.0"
|
||||||
media3 = "1.0.0"
|
media3 = "1.0.1"
|
||||||
|
|
||||||
androidSupport = "1.6.0"
|
androidSupport = "1.6.0"
|
||||||
materialDesign = "1.8.0"
|
materialDesign = "1.8.0"
|
||||||
@ -23,7 +23,8 @@ viewModelKtx = "2.6.1"
|
|||||||
swipeRefresh = "1.1.0"
|
swipeRefresh = "1.1.0"
|
||||||
|
|
||||||
retrofit = "2.9.0"
|
retrofit = "2.9.0"
|
||||||
jackson = "2.14.2"
|
## KEEP ON 2.13 branch (https://github.com/FasterXML/jackson-databind/issues/3658#issuecomment-1312633064) for compatibility with API 24
|
||||||
|
jackson = "2.13.5"
|
||||||
okhttp = "4.10.0"
|
okhttp = "4.10.0"
|
||||||
koin = "3.3.2"
|
koin = "3.3.2"
|
||||||
picasso = "2.8"
|
picasso = "2.8"
|
||||||
|
@ -9,8 +9,8 @@ android {
|
|||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "org.moire.ultrasonic"
|
applicationId "org.moire.ultrasonic"
|
||||||
versionCode 116
|
versionCode 117
|
||||||
versionName "4.3.3"
|
versionName "4.3.4"
|
||||||
|
|
||||||
minSdkVersion versions.minSdk
|
minSdkVersion versions.minSdk
|
||||||
targetSdkVersion versions.targetSdk
|
targetSdkVersion versions.targetSdk
|
||||||
|
@ -46,7 +46,7 @@ public class GenreAdapter extends ArrayAdapter<Genre> implements SectionIndexer
|
|||||||
private final Object[] sections;
|
private final Object[] sections;
|
||||||
private final Integer[] positions;
|
private final Integer[] positions;
|
||||||
|
|
||||||
public GenreAdapter(Context context, List<Genre> genres)
|
public GenreAdapter(@NonNull Context context, List<Genre> genres)
|
||||||
{
|
{
|
||||||
super(context, R.layout.list_item_generic, genres);
|
super(context, R.layout.list_item_generic, genres);
|
||||||
|
|
||||||
|
@ -401,6 +401,8 @@ open class TrackCollectionFragment(
|
|||||||
) {
|
) {
|
||||||
// We are coming back from unknown context
|
// We are coming back from unknown context
|
||||||
// and need to ensure Main Thread in order to manipulate the UI
|
// and need to ensure Main Thread in order to manipulate the UI
|
||||||
|
// If view is null, our view was disposed in the meantime
|
||||||
|
if (view == null) return
|
||||||
viewLifecycleOwner.lifecycleScope.launch(Dispatchers.Main) {
|
viewLifecycleOwner.lifecycleScope.launch(Dispatchers.Main) {
|
||||||
val multipleSelection = viewAdapter.hasMultipleSelection()
|
val multipleSelection = viewAdapter.hasMultipleSelection()
|
||||||
|
|
||||||
|
@ -102,7 +102,9 @@ class SelectGenreFragment : Fragment() {
|
|||||||
|
|
||||||
override fun done(result: List<Genre>) {
|
override fun done(result: List<Genre>) {
|
||||||
emptyView!!.isVisible = result.isEmpty()
|
emptyView!!.isVisible = result.isEmpty()
|
||||||
genreListView!!.adapter = GenreAdapter(context, result)
|
if (context != null) {
|
||||||
|
genreListView!!.adapter = GenreAdapter(context!!, result)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
task.execute()
|
task.execute()
|
||||||
|
@ -240,6 +240,8 @@ class ImageLoader(
|
|||||||
} finally {
|
} finally {
|
||||||
inputStream.safeClose()
|
inputStream.safeClose()
|
||||||
}
|
}
|
||||||
|
} catch (all: Exception) {
|
||||||
|
Timber.w(all)
|
||||||
} finally {
|
} finally {
|
||||||
cacheInProgress.remove(file)?.countDown()
|
cacheInProgress.remove(file)?.countDown()
|
||||||
}
|
}
|
||||||
|
@ -7,11 +7,14 @@
|
|||||||
|
|
||||||
package org.moire.ultrasonic.subsonic
|
package org.moire.ultrasonic.subsonic
|
||||||
|
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.navigation.fragment.findNavController
|
import androidx.navigation.fragment.findNavController
|
||||||
import java.util.Collections
|
import java.util.Collections
|
||||||
import java.util.LinkedList
|
import java.util.LinkedList
|
||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
|
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@ -28,6 +31,7 @@ import org.moire.ultrasonic.util.EntryByDiscAndTrackComparator
|
|||||||
import org.moire.ultrasonic.util.InfoDialog
|
import org.moire.ultrasonic.util.InfoDialog
|
||||||
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 timber.log.Timber
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves a list of songs and adds them to the now playing list
|
* Retrieves a list of songs and adds them to the now playing list
|
||||||
@ -39,6 +43,16 @@ class DownloadHandler(
|
|||||||
) : CoroutineScope by CoroutineScope(Dispatchers.IO) {
|
) : CoroutineScope by CoroutineScope(Dispatchers.IO) {
|
||||||
private val maxSongs = 500
|
private val maxSongs = 500
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exception Handler for Coroutines
|
||||||
|
*/
|
||||||
|
val exceptionHandler = CoroutineExceptionHandler { _, exception ->
|
||||||
|
Handler(Looper.getMainLooper()).post {
|
||||||
|
Timber.w(exception)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Use coroutine here (with proper exception handler)
|
||||||
fun download(
|
fun download(
|
||||||
fragment: Fragment,
|
fragment: Fragment,
|
||||||
append: Boolean,
|
append: Boolean,
|
||||||
@ -210,7 +224,7 @@ class DownloadHandler(
|
|||||||
isArtist: Boolean
|
isArtist: Boolean
|
||||||
) {
|
) {
|
||||||
// Launch the Job
|
// Launch the Job
|
||||||
val job = launch {
|
val job = launch(exceptionHandler) {
|
||||||
val songs: MutableList<Track> =
|
val songs: MutableList<Track> =
|
||||||
getTracksFromServer(isArtist, id, isDirectory, name, isShare)
|
getTracksFromServer(isArtist, id, isDirectory, name, isShare)
|
||||||
|
|
||||||
|
@ -10,7 +10,9 @@ package org.moire.ultrasonic.util
|
|||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
import java.lang.ref.WeakReference
|
||||||
import org.moire.ultrasonic.R
|
import org.moire.ultrasonic.R
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* InfoDialog can be used to show some information to the user. Typically it cannot be cancelled,
|
* InfoDialog can be used to show some information to the user. Typically it cannot be cancelled,
|
||||||
@ -19,24 +21,30 @@ import org.moire.ultrasonic.R
|
|||||||
open class InfoDialog(
|
open class InfoDialog(
|
||||||
context: Context,
|
context: Context,
|
||||||
message: CharSequence?,
|
message: CharSequence?,
|
||||||
private val activity: Activity? = null,
|
activity: Activity? = null,
|
||||||
private val finishActivityOnClose: Boolean = false
|
private val finishActivityOnClose: Boolean = false
|
||||||
) {
|
) {
|
||||||
|
private val activityRef: WeakReference<Activity?> = WeakReference(activity)
|
||||||
open var builder: MaterialAlertDialogBuilder = Builder(activity ?: context, message)
|
open var builder: MaterialAlertDialogBuilder = Builder(activityRef.get() ?: context, message)
|
||||||
|
|
||||||
fun show() {
|
fun show() {
|
||||||
builder.setOnCancelListener {
|
builder.setOnCancelListener {
|
||||||
if (finishActivityOnClose) {
|
if (finishActivityOnClose) {
|
||||||
activity!!.finish()
|
activityRef.get()?.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
builder.setPositiveButton(R.string.common_ok) { _, _ ->
|
builder.setPositiveButton(R.string.common_ok) { _, _ ->
|
||||||
if (finishActivityOnClose) {
|
if (finishActivityOnClose) {
|
||||||
activity!!.finish()
|
activityRef.get()?.finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
builder.create().show()
|
|
||||||
|
// If the app was put into the background in the meantime this would fail
|
||||||
|
try {
|
||||||
|
builder.create().show()
|
||||||
|
} catch (all: Exception) {
|
||||||
|
Timber.w(all, "Failed to create dialog")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Builder(context: Context) : MaterialAlertDialogBuilder(context) {
|
class Builder(context: Context) : MaterialAlertDialogBuilder(context) {
|
||||||
@ -93,7 +101,6 @@ class ConfirmationDialog(
|
|||||||
activity: Activity? = null,
|
activity: Activity? = null,
|
||||||
finishActivityOnClose: Boolean = false
|
finishActivityOnClose: Boolean = false
|
||||||
) : InfoDialog(context, message, activity, finishActivityOnClose) {
|
) : InfoDialog(context, message, activity, finishActivityOnClose) {
|
||||||
|
|
||||||
override var builder: MaterialAlertDialogBuilder = Builder(activity ?: context, message)
|
override var builder: MaterialAlertDialogBuilder = Builder(activity ?: context, message)
|
||||||
|
|
||||||
class Builder(context: Context) : MaterialAlertDialogBuilder(context) {
|
class Builder(context: Context) : MaterialAlertDialogBuilder(context) {
|
||||||
|
@ -273,7 +273,7 @@ class StorageFile(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun getStorageFileForParentDirectory(path: String): StorageFile? {
|
private fun getStorageFileForParentDirectory(path: String): StorageFile? {
|
||||||
val parentPath = FileUtil.getParentPath(path)!!
|
val parentPath = FileUtil.getParentPath(path) ?: return null
|
||||||
if (storageFilePathDictionary.containsKey(parentPath))
|
if (storageFilePathDictionary.containsKey(parentPath))
|
||||||
return storageFilePathDictionary[parentPath]!!
|
return storageFilePathDictionary[parentPath]!!
|
||||||
if (notExistingPathDictionary.contains(parentPath)) return null
|
if (notExistingPathDictionary.contains(parentPath)) return null
|
||||||
|
@ -133,19 +133,24 @@ object Util {
|
|||||||
@JvmStatic
|
@JvmStatic
|
||||||
@SuppressLint("ShowToast") // Invalid warning
|
@SuppressLint("ShowToast") // Invalid warning
|
||||||
fun toast(context: Context?, message: CharSequence?, shortDuration: Boolean) {
|
fun toast(context: Context?, message: CharSequence?, shortDuration: Boolean) {
|
||||||
if (toast == null) {
|
// If called after doing some background processing, our context might have expired!
|
||||||
toast = Toast.makeText(
|
try {
|
||||||
context,
|
if (toast == null) {
|
||||||
message,
|
toast = Toast.makeText(
|
||||||
if (shortDuration) Toast.LENGTH_SHORT else Toast.LENGTH_LONG
|
context,
|
||||||
)
|
message,
|
||||||
toast!!.setGravity(Gravity.CENTER, 0, 0)
|
if (shortDuration) Toast.LENGTH_SHORT else Toast.LENGTH_LONG
|
||||||
} else {
|
)
|
||||||
toast!!.setText(message)
|
toast!!.setGravity(Gravity.CENTER, 0, 0)
|
||||||
toast!!.duration =
|
} else {
|
||||||
if (shortDuration) Toast.LENGTH_SHORT else Toast.LENGTH_LONG
|
toast!!.setText(message)
|
||||||
|
toast!!.duration =
|
||||||
|
if (shortDuration) Toast.LENGTH_SHORT else Toast.LENGTH_LONG
|
||||||
|
}
|
||||||
|
toast!!.show()
|
||||||
|
} catch (_: Exception) {
|
||||||
|
// Ignore
|
||||||
}
|
}
|
||||||
toast!!.show()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
x
Reference in New Issue
Block a user