Skip to content

Commit 01eeb38

Browse files
authored
Merge pull request #212 from timusus/fixes
Fixes
2 parents 4a740e4 + f27150c commit 01eeb38

18 files changed

Lines changed: 159 additions & 43 deletions

File tree

android/app/build.gradle.kts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ plugins {
44
id("com.android.application")
55
id("kotlin-android")
66
id("androidx.navigation.safeargs.kotlin")
7-
id("com.mikepenz.aboutlibraries.plugin")
7+
id("com.mikepenz.aboutlibraries.plugin.android")
88
id("kotlin-parcelize")
99
id("dagger.hilt.android.plugin")
1010
id("com.google.firebase.crashlytics")
@@ -239,7 +239,7 @@ android {
239239
}
240240

241241
// About Libraries
242-
implementation(libs.mikepenz.aboutlibrariesCore)
242+
implementation(libs.mikepenz.aboutlibraries)
243243

244244
// Billing
245245
implementation(libs.billingclient.billingKtx)

android/app/src/main/assets/changelog.json

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,21 @@
11
[
2+
{
3+
"versionName": "1.0.9",
4+
"releaseDate": "15/01/2026",
5+
"features": [
6+
7+
],
8+
"fixes": [
9+
"Fixed Chromecast reliability issues",
10+
"Fixed a long-standing issue where the app could crash when playing from the background",
11+
"Various stability improvements"
12+
],
13+
"improvements": [
14+
],
15+
"notes": [
16+
"What's up? I want to make Shuttle great again. I just need more hours in the day, and more days in the week!"
17+
]
18+
},
219
{
320
"versionName": "1.0.8",
421
"releaseDate": "10/01/2026",

android/app/src/main/java/com/simplecityapps/shuttle/appinitializers/PlaybackInitializer.kt

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@ package com.simplecityapps.shuttle.appinitializers
22

33
import android.annotation.SuppressLint
44
import android.app.Application
5+
import android.app.ForegroundServiceStartNotAllowedException
56
import android.content.Context
67
import android.content.Intent
8+
import android.os.Build
79
import androidx.core.content.ContextCompat
810
import com.simplecityapps.mediaprovider.repository.songs.SongRepository
911
import com.simplecityapps.playback.NoiseManager
@@ -158,7 +160,15 @@ constructor(
158160
override fun onPlaybackStateChanged(playbackState: PlaybackState) {
159161
when (playbackState) {
160162
is PlaybackState.Playing -> {
161-
ContextCompat.startForegroundService(context, Intent(context, PlaybackService::class.java))
163+
try {
164+
ContextCompat.startForegroundService(context, Intent(context, PlaybackService::class.java))
165+
} catch (e: IllegalStateException) {
166+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && e is ForegroundServiceStartNotAllowedException) {
167+
Timber.w(e, "Cannot start foreground service from background - likely audio focus regained while app in background")
168+
} else {
169+
throw e
170+
}
171+
}
162172
}
163173
is PlaybackState.Paused -> {
164174
playbackPreferenceManager.playbackPosition = playbackManager.getProgress()

android/app/src/main/java/com/simplecityapps/shuttle/ui/MainActivity.kt

Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.simplecityapps.shuttle.ui
22

33
import android.Manifest
4+
import android.app.ForegroundServiceStartNotAllowedException
45
import android.content.Intent
56
import android.content.pm.PackageManager
67
import android.os.Build
@@ -22,6 +23,7 @@ import kotlinx.coroutines.CoroutineScope
2223
import kotlinx.coroutines.launch
2324
import kotlinx.coroutines.tasks.await
2425
import kotlinx.coroutines.withTimeout
26+
import timber.log.Timber
2527

2628
@AndroidEntryPoint
2729
class MainActivity : AppCompatActivity() {
@@ -41,7 +43,7 @@ class MainActivity : AppCompatActivity() {
4143
@AppCoroutineScope
4244
lateinit var scope: CoroutineScope
4345

44-
lateinit var snowfallView: SnowfallView
46+
var snowfallView: SnowfallView? = null
4547

4648
// Lifecycle
4749

@@ -76,7 +78,7 @@ class MainActivity : AppCompatActivity() {
7678
withTimeout(5000) {
7779
remoteConfig.fetchAndActivate().await()
7880
}
79-
snowfallView.setForecast(remoteConfig.getDouble("snow_forecast"))
81+
snowfallView?.setForecast(remoteConfig.getDouble("snow_forecast"))
8082
}
8183
}
8284

@@ -104,15 +106,23 @@ class MainActivity : AppCompatActivity() {
104106

105107
private fun handleSearchQuery(intent: Intent?) {
106108
if (intent?.action == MediaStore.INTENT_ACTION_MEDIA_PLAY_FROM_SEARCH) {
107-
ContextCompat.startForegroundService(
108-
this,
109-
Intent(this, PlaybackService::class.java).apply {
110-
action = PlaybackService.ACTION_SEARCH
111-
intent.extras?.let { extras ->
112-
putExtras(extras)
109+
try {
110+
ContextCompat.startForegroundService(
111+
this,
112+
Intent(this, PlaybackService::class.java).apply {
113+
action = PlaybackService.ACTION_SEARCH
114+
intent.extras?.let { extras ->
115+
putExtras(extras)
116+
}
113117
}
118+
)
119+
} catch (e: IllegalStateException) {
120+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && e is ForegroundServiceStartNotAllowedException) {
121+
Timber.w(e, "Cannot start foreground service from search query - app may be in restricted state")
122+
} else {
123+
throw e
114124
}
115-
)
125+
}
116126
}
117127
}
118128
}

android/app/src/main/java/com/simplecityapps/shuttle/ui/ShortcutHandlerActivity.kt

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
package com.simplecityapps.shuttle.ui
22

3+
import android.app.ForegroundServiceStartNotAllowedException
34
import android.content.Intent
45
import android.os.Build
56
import android.os.Bundle
67
import androidx.appcompat.app.AppCompatActivity
78
import com.simplecityapps.playback.PlaybackService
89
import dagger.hilt.android.AndroidEntryPoint
10+
import timber.log.Timber
911

1012
@AndroidEntryPoint
1113
class ShortcutHandlerActivity : AppCompatActivity() {
@@ -19,10 +21,18 @@ class ShortcutHandlerActivity : AppCompatActivity() {
1921
action = PlaybackService.ACTION_TOGGLE_PLAYBACK
2022
}
2123

22-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
23-
startForegroundService(serviceIntent)
24-
} else {
25-
startService(serviceIntent)
24+
try {
25+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
26+
startForegroundService(serviceIntent)
27+
} else {
28+
startService(serviceIntent)
29+
}
30+
} catch (e: IllegalStateException) {
31+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && e is ForegroundServiceStartNotAllowedException) {
32+
Timber.w(e, "Cannot start foreground service from shortcut - app may be in restricted state")
33+
} else {
34+
throw e
35+
}
2636
}
2737
}
2838
}

android/app/src/main/java/com/simplecityapps/shuttle/ui/common/dialog/TagEditorAlertDialog.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,9 @@ class TagEditorAlertDialog :
167167
}
168168

169169
fun show(manager: FragmentManager) {
170-
super.show(manager, TAG)
170+
if (!manager.isStateSaved) {
171+
super.show(manager, TAG)
172+
}
171173
}
172174

173175
// Private

android/app/src/main/java/com/simplecityapps/shuttle/ui/screens/library/albumartists/AlbumArtistListFragment.kt

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ import com.simplecityapps.shuttle.ui.screens.playlistmenu.PlaylistMenuView
4646
import com.squareup.phrase.Phrase
4747
import dagger.hilt.android.AndroidEntryPoint
4848
import javax.inject.Inject
49+
import timber.log.Timber
4950

5051
@AndroidEntryPoint
5152
class AlbumArtistListFragment :
@@ -336,13 +337,18 @@ class AlbumArtistListFragment :
336337
viewHolder: AlbumArtistBinder.ViewHolder
337338
) {
338339
if (!contextualToolbarHelper.handleClick(albumArtist)) {
339-
if (findNavController().currentDestination?.id != R.id.albumArtistDetailFragment) {
340-
findNavController().navigate(
341-
R.id.action_libraryFragment_to_albumArtistDetailFragment,
342-
AlbumArtistDetailFragmentArgs(albumArtist).toBundle(),
343-
null,
344-
FragmentNavigatorExtras(viewHolder.imageView to viewHolder.imageView.transitionName)
345-
)
340+
// Verify we're on libraryFragment (where the action is defined) before navigating
341+
if (findNavController().currentDestination?.id == R.id.libraryFragment) {
342+
try {
343+
findNavController().navigate(
344+
R.id.action_libraryFragment_to_albumArtistDetailFragment,
345+
AlbumArtistDetailFragmentArgs(albumArtist).toBundle(),
346+
null,
347+
FragmentNavigatorExtras(viewHolder.imageView to viewHolder.imageView.transitionName)
348+
)
349+
} catch (e: IllegalArgumentException) {
350+
Timber.e(e, "Failed to navigate to album artist detail")
351+
}
346352
}
347353
}
348354
}

android/app/src/main/java/com/simplecityapps/shuttle/ui/screens/main/MainFragment.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,8 @@ class MainFragment :
165165
if (task.isSuccessful) {
166166
// We got the ReviewInfo object
167167
val reviewInfo = task.result
168-
reviewManager.launchReviewFlow(requireActivity(), reviewInfo)
168+
// Use nullable activity since fragment may be detached when async callback fires
169+
activity?.let { reviewManager.launchReviewFlow(it, reviewInfo) }
169170
} else {
170171
// There was some problem, log or handle the error code.
171172
Timber.e(task.exception ?: Exception("Unknown"), "Failed to launch review flow")

android/app/src/main/java/com/simplecityapps/shuttle/ui/screens/onboarding/OnboardingParentFragment.kt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,10 @@ class OnboardingParentFragment :
196196
generalPreferenceManager.hasOnboarded = true
197197

198198
if (args.isOnboarding) {
199-
findNavController().navigate(R.id.action_onboardingFragment_to_mainFragment)
199+
// Guard against double-tap or race conditions where navigation already occurred
200+
if (findNavController().currentDestination?.id == R.id.onboardingFragment) {
201+
findNavController().navigate(R.id.action_onboardingFragment_to_mainFragment)
202+
}
200203
} else {
201204
findNavController().popBackStack()
202205
}

android/app/src/main/java/com/simplecityapps/shuttle/ui/screens/playback/PlaybackFragment.kt

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import com.google.android.gms.cast.framework.CastButtonFactory
2020
import com.simplecityapps.adapter.RecyclerAdapter
2121
import com.simplecityapps.adapter.RecyclerListener
2222
import com.simplecityapps.playback.PlaybackState
23+
import com.simplecityapps.playback.chromecast.CastSessionManager
2324
import com.simplecityapps.playback.queue.QueueItem
2425
import com.simplecityapps.playback.queue.QueueManager
2526
import com.simplecityapps.shuttle.R
@@ -64,6 +65,9 @@ class PlaybackFragment :
6465
@Inject
6566
lateinit var queueManager: QueueManager
6667

68+
@Inject
69+
lateinit var castSessionManager: CastSessionManager
70+
6771
private var recyclerView: RecyclerView by autoCleared()
6872

6973
private var adapter: RecyclerAdapter by autoCleared()
@@ -224,7 +228,11 @@ class PlaybackFragment :
224228
presenter.setFavorite(favoriteButton.isChecked)
225229
}
226230

227-
CastButtonFactory.setUpMediaRouteButton(requireContext(), toolbar.menu, R.id.media_route_menu_item)
231+
if (castSessionManager.isAvailable) {
232+
CastButtonFactory.setUpMediaRouteButton(requireContext(), toolbar.menu, R.id.media_route_menu_item)
233+
} else {
234+
toolbar.menu.findItem(R.id.media_route_menu_item)?.isVisible = false
235+
}
228236

229237
savedInstanceState?.getParcelable<Parcelable>(QueueFragment.ARG_RECYCLER_STATE)?.let { recyclerViewState = it }
230238

0 commit comments

Comments
 (0)