
Fix two major highlighting bugs based around the janky and stupid way I would handle highlighting previously. Previously, I would index the views of a RecyclerView in order to highlight viewholders. In retrospect this was a pretty bad idea, as viewholders could be in a weird limbo state where they are bound, but not accessible. I mean, it's in the name. It's a Recycling View. Fortunately, google actually knew what they were doing and provided a way to mutate viewholders at runtime using notifyItemChanged. And the API actually makes sense! Wow! Migrate all detail adapters to a system that uses notifyItemChanged instead of the terrible pre-existing system.
178 lines
6.5 KiB
Kotlin
178 lines
6.5 KiB
Kotlin
/*
|
|
* Copyright (c) 2021 Auxio Project
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
package org.oxycblt.auxio
|
|
|
|
import android.content.Intent
|
|
import android.net.Uri
|
|
import android.os.Build
|
|
import android.os.Bundle
|
|
import android.view.View
|
|
import android.view.WindowInsets
|
|
import androidx.activity.viewModels
|
|
import androidx.appcompat.app.AppCompatActivity
|
|
import androidx.appcompat.app.AppCompatDelegate
|
|
import androidx.core.view.updatePadding
|
|
import org.oxycblt.auxio.databinding.ActivityMainBinding
|
|
import org.oxycblt.auxio.playback.PlaybackViewModel
|
|
import org.oxycblt.auxio.playback.system.PlaybackService
|
|
import org.oxycblt.auxio.settings.SettingsManager
|
|
import org.oxycblt.auxio.util.isNight
|
|
import org.oxycblt.auxio.util.logD
|
|
import org.oxycblt.auxio.util.replaceSystemBarInsetsCompat
|
|
import org.oxycblt.auxio.util.systemBarInsetsCompat
|
|
|
|
/**
|
|
* The single [AppCompatActivity] for Auxio.
|
|
*
|
|
* TODO: Add crash reporting and error screens. This likely has to be an external activity, so it is
|
|
* blocked by eliminating exitProcess from the app.
|
|
*
|
|
* TODO: Custom language support
|
|
*
|
|
* TODO: Rework padding ethos
|
|
*
|
|
* @author OxygenCobalt
|
|
*/
|
|
class MainActivity : AppCompatActivity() {
|
|
private val playbackModel: PlaybackViewModel by viewModels()
|
|
|
|
override fun onCreate(savedInstanceState: Bundle?) {
|
|
super.onCreate(savedInstanceState)
|
|
|
|
setupTheme()
|
|
|
|
val binding = ActivityMainBinding.inflate(layoutInflater)
|
|
setContentView(binding.root)
|
|
applyEdgeToEdgeWindow(binding.root)
|
|
|
|
logD("Activity created")
|
|
}
|
|
|
|
override fun onStart() {
|
|
super.onStart()
|
|
|
|
startService(Intent(this, PlaybackService::class.java))
|
|
|
|
// If we have a file URI already, open it. Otherwise, restore the playback state.
|
|
val action =
|
|
retrieveViewUri(intent)?.let { PlaybackViewModel.DelayedAction.Open(it) }
|
|
?: PlaybackViewModel.DelayedAction.RestoreState
|
|
|
|
playbackModel.performAction(this, action)
|
|
}
|
|
|
|
override fun onNewIntent(intent: Intent?) {
|
|
super.onNewIntent(intent)
|
|
|
|
// See if the new intent is a file intent. If so, open it.
|
|
val uri = retrieveViewUri(intent)
|
|
if (uri != null) {
|
|
playbackModel.performAction(this, PlaybackViewModel.DelayedAction.Open(uri))
|
|
}
|
|
}
|
|
|
|
private fun retrieveViewUri(intent: Intent?): Uri? {
|
|
// If this intent is a valid view intent that has not been used already, give it
|
|
// to PlaybackViewModel to be used later.
|
|
if (intent != null) {
|
|
val action = intent.action
|
|
val isConsumed = intent.getBooleanExtra(KEY_INTENT_USED, false)
|
|
|
|
if (action == Intent.ACTION_VIEW && !isConsumed) {
|
|
// Mark the intent as used so this does not fire again
|
|
intent.putExtra(KEY_INTENT_USED, true)
|
|
return intent.data
|
|
}
|
|
}
|
|
|
|
return null
|
|
}
|
|
|
|
private fun setupTheme() {
|
|
val settingsManager = SettingsManager.getInstance()
|
|
|
|
// Disable theme customization above Android 12, as it's far enough in as a version to
|
|
// the point where most phones should have an automatic option for light/dark theming.
|
|
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S) {
|
|
AppCompatDelegate.setDefaultNightMode(settingsManager.theme)
|
|
} else {
|
|
AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM)
|
|
}
|
|
|
|
val accent = settingsManager.accent
|
|
|
|
// The black theme has a completely separate set of styles since style attributes cannot
|
|
// be modified at runtime.
|
|
if (isNight && settingsManager.useBlackTheme) {
|
|
logD("Applying black theme [accent $accent]")
|
|
setTheme(accent.blackTheme)
|
|
} else {
|
|
logD("Applying normal theme [accent $accent]")
|
|
setTheme(accent.theme)
|
|
}
|
|
}
|
|
|
|
private fun applyEdgeToEdgeWindow(contentView: View) {
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
|
logD("Doing R+ edge-to-edge")
|
|
|
|
window?.setDecorFitsSystemWindows(false)
|
|
|
|
// Instead of automatically fetching these insets and exposing them,
|
|
// the R+ SDK decides to make you specify the insets yourself with a barely
|
|
// documented API that isn't even mentioned in any of the edge-to-edge
|
|
// tutorials. Thanks android, very cool!
|
|
contentView.setOnApplyWindowInsetsListener { view, insets ->
|
|
WindowInsets.Builder()
|
|
.setInsets(
|
|
WindowInsets.Type.systemBars(),
|
|
insets.getInsetsIgnoringVisibility(WindowInsets.Type.systemBars()))
|
|
.setInsets(
|
|
WindowInsets.Type.systemGestures(),
|
|
insets.getInsetsIgnoringVisibility(WindowInsets.Type.systemGestures()))
|
|
.build()
|
|
.applyLeftRightInsets(view)
|
|
}
|
|
} else {
|
|
// Do old edge-to-edge otherwise.
|
|
logD("Doing legacy edge-to-edge")
|
|
|
|
@Suppress("DEPRECATION")
|
|
contentView.apply {
|
|
systemUiVisibility =
|
|
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
|
|
|
setOnApplyWindowInsetsListener { view, insets -> insets.applyLeftRightInsets(view) }
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Apply blind padding to accommodate left/right window insets. This is done because
|
|
* implementing insets on *phone* landscape mode is pretty impractical.
|
|
*/
|
|
private fun WindowInsets.applyLeftRightInsets(contentView: View): WindowInsets {
|
|
val bars = systemBarInsetsCompat
|
|
contentView.updatePadding(left = bars.left, right = bars.right)
|
|
return replaceSystemBarInsetsCompat(0, bars.top, 0, bars.bottom)
|
|
}
|
|
|
|
companion object {
|
|
private const val KEY_INTENT_USED = BuildConfig.APPLICATION_ID + ".key.FILE_INTENT_USED"
|
|
}
|
|
}
|