diff --git a/app/build.gradle b/app/build.gradle index ac0c11a05..ad870864a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -10,7 +10,7 @@ android { defaultConfig { applicationId "org.oxycblt.auxio" - minSdkVersion 24 + minSdkVersion 21 targetSdkVersion 30 versionCode 1 versionName "1.0" @@ -56,7 +56,7 @@ dependencies { implementation 'androidx.fragment:fragment-ktx:1.3.0-beta01' // Layout - implementation 'androidx.constraintlayout:constraintlayout:2.0.2' + implementation 'androidx.constraintlayout:constraintlayout:2.0.4' // Lifecycle implementation "androidx.lifecycle:lifecycle-extensions:2.2.0" diff --git a/app/src/main/java/org/oxycblt/auxio/MainActivity.kt b/app/src/main/java/org/oxycblt/auxio/MainActivity.kt index 5d0ec4d90..b0b15450c 100644 --- a/app/src/main/java/org/oxycblt/auxio/MainActivity.kt +++ b/app/src/main/java/org/oxycblt/auxio/MainActivity.kt @@ -5,14 +5,22 @@ import android.content.Intent import android.util.AttributeSet import android.view.View import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.app.AppCompatDelegate import org.oxycblt.auxio.playback.PlaybackService import org.oxycblt.auxio.theme.accent // FIXME: Fix bug where fast navigation will break the animations and // lead to nothing being displayed [Possibly Un-fixable] +// TODO: Test for compatibility +// API 30 - No Issues +// API 29 - No Issues [Primary Testing Version] +// API 28-23 - Not tested yet +// API 22 - ProgressBar/SeekBar look wonky, RecyclerView dividers don't show +// API 21 - Not tested yet class MainActivity : AppCompatActivity(R.layout.activity_main) { override fun onCreateView(name: String, context: Context, attrs: AttributeSet): View? { + AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES) // Apply the theme setTheme(accent.second) diff --git a/app/src/main/java/org/oxycblt/auxio/playback/NotificationUtils.kt b/app/src/main/java/org/oxycblt/auxio/playback/NotificationUtils.kt index d10c3f949..cf1d925b7 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/NotificationUtils.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/NotificationUtils.kt @@ -15,6 +15,7 @@ import org.oxycblt.auxio.R import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.coil.getBitmap import org.oxycblt.auxio.playback.state.LoopMode +import org.oxycblt.auxio.playback.state.PlaybackMode import org.oxycblt.auxio.playback.state.PlaybackStateManager object NotificationUtils { @@ -52,8 +53,7 @@ fun NotificationManager.createMediaNotification( ) // TODO: Things that probably aren't possible but would be nice - // - Swipe to close instead of a button to press - // - Playing intent takes you to now playing screen instead of elsewhere + // - Playing intent takes you to PlaybackFragment instead of MainFragment return NotificationCompat.Builder(context, NotificationUtils.CHANNEL_ID) .setSmallIcon(R.drawable.ic_song) .setStyle( @@ -64,12 +64,12 @@ fun NotificationManager.createMediaNotification( .setCategory(NotificationCompat.CATEGORY_SERVICE) .setChannelId(NotificationUtils.CHANNEL_ID) .setShowWhen(false) - .setTicker(context.getString(R.string.title_playback)) .addAction(newAction(NotificationUtils.ACTION_LOOP, context)) .addAction(newAction(NotificationUtils.ACTION_SKIP_PREV, context)) .addAction(newAction(NotificationUtils.ACTION_PLAY_PAUSE, context)) .addAction(newAction(NotificationUtils.ACTION_SKIP_NEXT, context)) .addAction(newAction(NotificationUtils.ACTION_EXIT, context)) + .setNotificationSilent() .setSubText(context.getString(R.string.title_playback)) .setContentIntent(mainIntent) .setVisibility(NotificationCompat.VISIBILITY_PUBLIC) @@ -91,8 +91,6 @@ fun NotificationCompat.Builder.setMetadata(song: Song, context: Context, onDone: @SuppressLint("RestrictedApi") fun NotificationCompat.Builder.updatePlaying(context: Context) { mActions[2] = newAction(NotificationUtils.ACTION_PLAY_PAUSE, context) - - setOngoing(PlaybackStateManager.getInstance().isPlaying) } @SuppressLint("RestrictedApi") @@ -100,6 +98,18 @@ fun NotificationCompat.Builder.updateLoop(context: Context) { mActions[0] = newAction(NotificationUtils.ACTION_LOOP, context) } +@SuppressLint("RestrictedApi") +fun NotificationCompat.Builder.updateMode(context: Context) { + val playbackManager = PlaybackStateManager.getInstance() + + // If the mode is ALL_SONGS, then just put a string, otherwise put the parent model's name. + if (playbackManager.mode == PlaybackMode.ALL_SONGS) { + setSubText(context.getString(R.string.title_all_songs)) + } else { + setSubText(playbackManager.parent!!.name) + } +} + private fun newAction(action: String, context: Context): NotificationCompat.Action { val playbackManager = PlaybackStateManager.getInstance() diff --git a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackService.kt b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackService.kt index 5a0054e21..873c94ffa 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/PlaybackService.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/PlaybackService.kt @@ -38,6 +38,7 @@ import org.oxycblt.auxio.music.Song import org.oxycblt.auxio.music.coil.getBitmap import org.oxycblt.auxio.music.toURI import org.oxycblt.auxio.playback.state.LoopMode +import org.oxycblt.auxio.playback.state.PlaybackMode import org.oxycblt.auxio.playback.state.PlaybackStateCallback import org.oxycblt.auxio.playback.state.PlaybackStateManager @@ -57,7 +58,6 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateCallback { private var changeIsFromAudioFocus = true private var isForeground = false - private var isDetachedFromUI = false private val serviceJob = Job() private val serviceScope = CoroutineScope( @@ -220,6 +220,12 @@ class PlaybackService : Service(), Player.EventListener, PlaybackStateCallback { stopForegroundAndNotification() } + override fun onModeUpdate(mode: PlaybackMode) { + notification.updateMode(this) + + startForegroundOrNotify() + } + override fun onPlayingUpdate(isPlaying: Boolean) { changeIsFromAudioFocus = false diff --git a/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateCallback.kt b/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateCallback.kt index 353396140..e545be3ee 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateCallback.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateCallback.kt @@ -6,6 +6,7 @@ interface PlaybackStateCallback { fun onSongUpdate(song: Song?) {} fun onPositionUpdate(position: Long) {} fun onQueueUpdate(queue: MutableList) {} + fun onModeUpdate(mode: PlaybackMode) {} fun onIndexUpdate(index: Int) {} fun onPlayingUpdate(isPlaying: Boolean) {} fun onShuffleUpdate(isShuffling: Boolean) {} diff --git a/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt b/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt index e6d37eed4..cd281d7f5 100644 --- a/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt +++ b/app/src/main/java/org/oxycblt/auxio/playback/state/PlaybackStateManager.kt @@ -11,7 +11,7 @@ import org.oxycblt.auxio.music.Song import kotlin.random.Random // The manager of the current playback state [Current Song, Queue, Shuffling] -// This class is for sole use by the classes in /playback/. +// This class is for sole use by the code in /playback/. // If you want to add system-side things, add to PlaybackService. // If you want to add ui-side things, add to PlaybackViewModel. // [Yes, I know MediaSessionCompat exists, but I like having full control over the @@ -42,6 +42,10 @@ class PlaybackStateManager private constructor() { callbacks.forEach { it.onIndexUpdate(value) } } private var mMode = PlaybackMode.ALL_SONGS + set(value) { + field = value + callbacks.forEach { it.onModeUpdate(value) } + } private var mIsPlaying = false set(value) { @@ -62,9 +66,11 @@ class PlaybackStateManager private constructor() { } val song: Song? get() = mSong + val parent: BaseModel? get() = mParent val position: Long get() = mPosition val queue: MutableList get() = mQueue val index: Int get() = mIndex + val mode: PlaybackMode get() = mMode val isPlaying: Boolean get() = mIsPlaying val isShuffling: Boolean get() = mIsShuffling val loopMode: LoopMode get() = mLoopMode @@ -136,6 +142,10 @@ class PlaybackStateManager private constructor() { Log.d(this::class.simpleName, "Playing ${baseModel.name}") + mParent = baseModel + mIndex = 0 + mIsShuffling = shuffled + when (baseModel) { is Album -> { mQueue = orderSongsInAlbum(baseModel) @@ -157,10 +167,6 @@ class PlaybackStateManager private constructor() { updatePlayback(mQueue[0]) - mIndex = 0 - mParent = baseModel - mIsShuffling = shuffled - if (mIsShuffling) { genShuffle(false) } else { @@ -178,8 +184,7 @@ class PlaybackStateManager private constructor() { } fun setPosition(position: Long) { - // Due to the hacky way I poll ExoPlayer positions, don't accept any bugged positions - // that are over the duration of the song. + // Don't accept any bugged positions that are over the duration of the song. mSong?.let { if (position <= it.duration) { mPosition = position