Skip to content

Commit

Permalink
refactor: clean up widgets and restore functionality
Browse files Browse the repository at this point in the history
Picks refactoring work over the rewrite branch
  • Loading branch information
kelsos committed Jan 2, 2025
1 parent 09373fd commit 4e426b7
Show file tree
Hide file tree
Showing 18 changed files with 492 additions and 525 deletions.
19 changes: 13 additions & 6 deletions app/src/main/java/com/kelsos/mbrc/App.kt
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ import android.net.wifi.WifiManager
import androidx.annotation.CallSuper
import androidx.core.content.getSystemService
import androidx.preference.PreferenceManager
import coil3.ImageLoader
import coil3.SingletonImageLoader
import coil3.request.crossfade
import coil3.util.DebugLogger
import com.kelsos.mbrc.common.utilities.CustomLoggingTree
import com.raizlabs.android.dbflow.config.FlowConfig
import com.raizlabs.android.dbflow.config.FlowManager
import org.koin.android.ext.koin.androidContext
Expand All @@ -23,6 +28,13 @@ open class App : Application() {
@CallSuper
override fun onCreate() {
super.onCreate()
SingletonImageLoader.setSafe { context ->
ImageLoader
.Builder(context)
.crossfade(true)
.logger(DebugLogger())
.build()
}
initialize()
}

Expand Down Expand Up @@ -60,12 +72,7 @@ open class App : Application() {

private fun initializeTimber() {
if (BuildConfig.DEBUG) {
Timber.plant(
object : Timber.DebugTree() {
override fun createStackElementTag(element: StackTraceElement): String =
"${super.createStackElementTag(element)}:${element.lineNumber} [${Thread.currentThread().name}]"
},
)
Timber.plant(CustomLoggingTree.create())
}
}
}
4 changes: 4 additions & 0 deletions app/src/main/java/com/kelsos/mbrc/AppModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,8 @@ import com.kelsos.mbrc.features.settings.ConnectionRepository
import com.kelsos.mbrc.features.settings.ConnectionRepositoryImpl
import com.kelsos.mbrc.features.settings.SettingsManager
import com.kelsos.mbrc.features.settings.SettingsManagerImpl
import com.kelsos.mbrc.features.widgets.WidgetUpdater
import com.kelsos.mbrc.features.widgets.WidgetUpdaterImpl
import com.kelsos.mbrc.networking.ApiBase
import com.kelsos.mbrc.networking.RequestManager
import com.kelsos.mbrc.networking.RequestManagerImpl
Expand Down Expand Up @@ -274,6 +276,8 @@ val appModule =
factoryOf(::HandleHandshake)
factoryOf(::TerminateServiceCommand)

singleOf(::WidgetUpdaterImpl) { bind<WidgetUpdater>() }

single<Scheduler>(named("main")) { AndroidSchedulers.mainThread() }
single<Scheduler>(named("io")) { Schedulers.io() }

Expand Down
6 changes: 1 addition & 5 deletions app/src/main/java/com/kelsos/mbrc/BaseActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -233,11 +233,7 @@ abstract class BaseActivity :
if (drawer.isDrawerOpen(GravityCompat.START)) {
drawer.closeDrawer(GravityCompat.START)
} else {
if (this@BaseActivity is PlayerActivity) {
finish()
} else {
onBackPressedDispatcher.onBackPressed()
}
finish()
}
}
},
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.kelsos.mbrc.common.utilities

import timber.log.Timber

class CustomLoggingTree : Timber.DebugTree() {
override fun createStackElementTag(element: StackTraceElement): String =
with(element) {
val thread = with(Thread.currentThread()) { name }
val createStackElementTag = super.createStackElementTag(element)
val className = createStackElementTag?.split("$")?.get(0)
"($className.kt:$lineNumber)#$methodName {$thread}"
}

companion object {
fun create(): CustomLoggingTree = CustomLoggingTree()
}
}
7 changes: 7 additions & 0 deletions app/src/main/java/com/kelsos/mbrc/common/utilities/Helpers.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.kelsos.mbrc.common.utilities

inline fun <T1 : Any, T2 : Any, R : Any> whenNotNull(
p1: T1?,
p2: T2?,
block: (T1, T2) -> R?,
): R? = if (p1 != null && p2 != null) block(p1, p2) else null
35 changes: 35 additions & 0 deletions app/src/main/java/com/kelsos/mbrc/features/widgets/BundleData.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.kelsos.mbrc.features.widgets

import android.os.Bundle
import androidx.core.os.BundleCompat
import com.kelsos.mbrc.annotations.PlayerState
import com.kelsos.mbrc.features.player.TrackInfo

class BundleData(
private val bundle: Bundle,
) {
fun isState() = bundle.getBoolean(WidgetUpdater.STATE, false)

fun isInfo() = bundle.getBoolean(WidgetUpdater.INFO, false)

fun isCover() = bundle.getBoolean(WidgetUpdater.COVER, false)

fun cover(): String = bundle.getString(WidgetUpdater.COVER_PATH, "")

fun state(): String = bundle.getString(WidgetUpdater.PLAYER_STATE, PlayerState.UNDEFINED)

fun playingTrack(): TrackInfo =
BundleCompat.getParcelable(
bundle,
WidgetUpdater.TRACK_INFO,
TrackInfo::class.java,
) ?: TrackInfo()

override fun toString(): String =
when {
this.isState() -> "State: ${this.state()}"
this.isInfo() -> "Info: ${this.playingTrack()}"
this.isCover() -> "Cover: ${this.cover()}"
else -> "Unknown"
}
}
167 changes: 167 additions & 0 deletions app/src/main/java/com/kelsos/mbrc/features/widgets/WidgetBase.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
package com.kelsos.mbrc.features.widgets

import android.app.PendingIntent
import android.appwidget.AppWidgetManager
import android.appwidget.AppWidgetProvider
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.widget.RemoteViews
import coil3.imageLoader
import coil3.request.ImageRequest
import coil3.size.Precision
import coil3.size.Scale
import com.kelsos.mbrc.R
import com.kelsos.mbrc.annotations.PlayerState
import com.kelsos.mbrc.common.utilities.whenNotNull
import com.kelsos.mbrc.features.player.PlayerActivity
import com.kelsos.mbrc.features.player.TrackInfo
import timber.log.Timber
import java.io.File

abstract class WidgetBase : AppWidgetProvider() {
abstract val config: WidgetConfig
abstract val type: String

override fun onReceive(
context: Context?,
intent: Intent?,
) {
super.onReceive(context, intent)
if (intent?.action == AppWidgetManager.ACTION_APPWIDGET_UPDATE) {
whenNotNull(context, intent.extras, this::updateWidget)
}
}

private fun updateWidget(
context: Context,
extras: Bundle,
) {
val widgetManager = AppWidgetManager.getInstance(context)
val widgets = ComponentName(context.packageName, config.widgetClass.java.name)
val widgetsIds = widgetManager.getAppWidgetIds(widgets)
val data = BundleData(extras)

if (widgetsIds.isEmpty()) {
Timber.v("No $type widgets found for update")
return
}

Timber.v("Updating $type widgets ${widgetsIds.joinToString(", ")} with extras: $data")

when {
data.isCover() -> {
updateCover(context, widgetManager, widgetsIds, data.cover())
}
data.isInfo() ->
updateInfo(
context,
widgetManager,
widgetsIds,
data.playingTrack(),
)
data.isState() ->
updatePlayState(
context,
widgetManager,
widgetsIds,
data.state(),
)
}
}

override fun onUpdate(
context: Context,
appWidgetManager: AppWidgetManager,
appWidgetIds: IntArray,
) {
super.onUpdate(context, appWidgetManager, appWidgetIds)

if (!appWidgetIds.isEmpty()) {
Timber.v("onUpdate called for $type widgets: ${appWidgetIds.joinToString(", ")}")
}

for (appWidgetId in appWidgetIds) {
val intent = Intent(context, PlayerActivity::class.java)
val pendingIntent =
PendingIntent.getActivity(
context,
0,
intent,
PendingIntent.FLAG_IMMUTABLE,
)
val views = RemoteViews(context.packageName, config.layout)
setupActionIntents(views, pendingIntent, context)
// Tell the AppWidgetManager to perform an set on the current app widget
appWidgetManager.updateAppWidget(appWidgetId, views)
}
}

abstract fun setupActionIntents(
views: RemoteViews,
pendingIntent: PendingIntent,
context: Context,
)

abstract fun setupTrackInfo(
views: RemoteViews,
info: TrackInfo,
)

private fun updateInfo(
context: Context,
widgetManager: AppWidgetManager,
widgetsIds: IntArray,
info: TrackInfo,
) {
val views = RemoteViews(context.packageName, config.layout)
setupTrackInfo(views, info)
widgetManager.updateAppWidget(widgetsIds, views)
}

private fun updateCover(
context: Context,
widgetManager: AppWidgetManager,
widgetsIds: IntArray,
path: String,
) {
val widget = RemoteViews(context.packageName, config.layout)
val coverFile = File(path)
if (coverFile.exists()) {
val request =
ImageRequest
.Builder(context)
.data(coverFile)
.size(R.dimen.widget_small_height)
.scale(Scale.FILL)
.precision(Precision.INEXACT)
.target(RemoteViewsTarget(widgetManager, widget, widgetsIds, config.imageId))
.build()

context.imageLoader.enqueue(request)
} else {
widget.setImageViewResource(config.imageId, R.drawable.ic_image_no_cover)
widgetManager.updateAppWidget(widgetsIds, widget)
}
}

private fun updatePlayState(
context: Context,
manager: AppWidgetManager,
widgetsIds: IntArray,
state: String,
) {
val widget = RemoteViews(context.packageName, config.layout)

widget.setImageViewResource(
config.playButtonId,
if (PlayerState.PLAYING == state) {
R.drawable.ic_action_pause
} else {
R.drawable.ic_action_play
},
)
manager.updateAppWidget(widgetsIds, widget)
}
}
18 changes: 18 additions & 0 deletions app/src/main/java/com/kelsos/mbrc/features/widgets/WidgetConfig.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.kelsos.mbrc.features.widgets

import androidx.annotation.DimenRes
import androidx.annotation.IdRes
import androidx.annotation.LayoutRes
import kotlin.reflect.KClass

data class WidgetConfig(
@LayoutRes
val layout: Int,
@DimenRes
val imageSize: Int,
@IdRes
val imageId: Int,
@IdRes
val playButtonId: Int,
val widgetClass: KClass<out WidgetBase>,
)
Loading

0 comments on commit 4e426b7

Please sign in to comment.