Compare commits

...

2 Commits

Author SHA1 Message Date
birdbird
3a39902c4c Merge branch '434-master' into 'master'
Release 4.3.4

See merge request ultrasonic/ultrasonic!995
2023-05-07 15:23:38 +00:00
birdbird
88364b15d6 Release 4.3.4 2023-05-07 15:23:38 +00:00
11 changed files with 68 additions and 27 deletions

View 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.

View File

@ -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"

View File

@ -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

View File

@ -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);

View File

@ -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()

View File

@ -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()

View File

@ -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()
} }

View File

@ -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)

View File

@ -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) {

View File

@ -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

View File

@ -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()
} }
/** /**