Skip to content

Commit

Permalink
Add maxWidth option to GlanceText
Browse files Browse the repository at this point in the history
  • Loading branch information
toasterofbread committed Oct 23, 2024
1 parent 8f27a53 commit 5f65bff
Show file tree
Hide file tree
Showing 10 changed files with 134 additions and 78 deletions.
4 changes: 2 additions & 2 deletions shared/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -151,8 +151,8 @@ kotlin {
implementation(deps.get("io.ktor:ktor-client-cio"))

// Widget
implementation("androidx.glance:glance-appwidget:1.1.0")
implementation("androidx.glance:glance-material3:1.1.0")
implementation("androidx.glance:glance-appwidget:1.1.1")
implementation("androidx.glance:glance-material3:1.1.1")
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,19 @@ import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.LayoutDirection
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.glance.GlanceId
import androidx.glance.GlanceModifier
import androidx.glance.action.Action
import androidx.glance.action.ActionParameters
import androidx.glance.action.actionParametersOf
import androidx.glance.LocalSize
import androidx.glance.action.clickable
import androidx.glance.appwidget.AppWidgetId
import androidx.glance.appwidget.GlanceAppWidget
import androidx.glance.appwidget.GlanceAppWidgetManager
import androidx.glance.appwidget.action.ActionCallback
import androidx.glance.appwidget.action.actionRunCallback
import androidx.glance.appwidget.SizeMode
import androidx.glance.appwidget.cornerRadius
import androidx.glance.appwidget.provideContent
import androidx.glance.background
Expand Down Expand Up @@ -77,19 +75,13 @@ import dev.toastbits.composekit.utils.common.thenIf
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import kotlinx.serialization.encodeToString
import org.jetbrains.compose.resources.FontResource

@Suppress("UNCHECKED_CAST")
abstract class SpMpWidget<A: TypeWidgetClickAction, T: TypeWidgetConfig<A>>: GlanceAppWidget() {
companion object {
private val active_widgets: MutableMap<Int, SpMpWidget<*, *>> = mutableMapOf()

fun runActionOnWidget(action: WidgetClickAction<TypeWidgetClickAction>, widget_glance_id: GlanceId) {
val widget: SpMpWidget<*, *> = SpMpWidget.active_widgets[widget_glance_id.getDatabaseId()]!!
widget.runAction(action, widget_glance_id)
}
}
abstract class SpMpWidget<A: TypeWidgetClickAction, T: TypeWidgetConfig<A>>(exact_size: Boolean): GlanceAppWidget() {
override val sizeMode: SizeMode =
if (exact_size) SizeMode.Exact
else SizeMode.Single

protected lateinit var context: AppContext

Expand Down Expand Up @@ -135,8 +127,6 @@ abstract class SpMpWidget<A: TypeWidgetClickAction, T: TypeWidgetConfig<A>>: Gla
ObserveConfiguration(widget_id!!)
active_widgets[widget_id!!] = this

println("Widget $widget_id ($widget_type) updated")

CompositionLocalProvider(
// App
LocalPlayerState provides state,
Expand Down Expand Up @@ -205,7 +195,12 @@ abstract class SpMpWidget<A: TypeWidgetClickAction, T: TypeWidgetConfig<A>>: Gla
GlanceModifier.fillMaxSize().defaultWeight(),
contentAlignment = Alignment.Center
) {
Content(GlanceModifier.wrapContentSize(), PaddingValues(15.dp))
val padding: Dp = 15.dp
CompositionLocalProvider(
LocalSize provides LocalSize.current.minus(DpSize(padding * 2, padding * 2))
) {
Content(GlanceModifier.wrapContentSize(), PaddingValues(padding))
}
}
}
}
Expand Down Expand Up @@ -327,7 +322,8 @@ abstract class SpMpWidget<A: TypeWidgetClickAction, T: TypeWidgetConfig<A>>: Gla
text: String,
modifier: GlanceModifier = GlanceModifier,
font_size: TextUnit = 15.sp,
alpha: Float = 1f
alpha: Float = 1f,
max_width: Dp? = null
) {
val ui_language: String by context.observeUiLanguage()
val app_font_mode: FontMode by context.settings.system.FONT.observe()
Expand All @@ -338,9 +334,19 @@ abstract class SpMpWidget<A: TypeWidgetClickAction, T: TypeWidgetConfig<A>>: Gla
font = font,
modifier = modifier,
font_size = font_size * base_configuration.font_size,
alpha = alpha
alpha = alpha,
max_width = max_width
)
}

companion object {
private val active_widgets: MutableMap<Int, SpMpWidget<*, *>> = mutableMapOf()

fun runActionOnWidget(action: WidgetClickAction<TypeWidgetClickAction>, widget_glance_id: GlanceId) {
val widget: SpMpWidget<*, *> = SpMpWidget.active_widgets[widget_glance_id.getDatabaseId()]!!
widget.runAction(action, widget_glance_id)
}
}
}

@SuppressLint("RestrictedApi")
Expand All @@ -349,25 +355,3 @@ fun GlanceId.getDatabaseId(): Int =
is AppWidgetId -> appWidgetId
else -> throw NotImplementedError(this::class.toString())
}

// Must not be private
internal class WidgetActionCallback: ActionCallback {
override suspend fun onAction(
context: Context,
glanceId: GlanceId,
parameters: ActionParameters
) {
val serialisedAction: String = parameters[keyAction] ?: return
val configuration: SpMpWidgetConfiguration<TypeWidgetClickAction> = SpMpWidgetConfiguration.json.decodeFromString(serialisedAction)
SpMpWidget.runActionOnWidget(configuration.type_configuration.click_action, glanceId)
}

companion object {
val keyAction: ActionParameters.Key<String> = ActionParameters.Key("action")

operator fun invoke(configuration: SpMpWidgetConfiguration<out TypeWidgetClickAction>): Action =
actionRunCallback<WidgetActionCallback>(
actionParametersOf(keyAction to SpMpWidgetConfiguration.json.encodeToString(configuration))
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -36,13 +36,11 @@ object SpMpWidgetUpdater {
update_values[this]!!

suspend fun SpMpWidgetType.updateAll(context: Context) {
println("Updating all widgets of type $this")
incrementUpdateValue()
widget_instances[this]!!.updateAll(context)
}

suspend fun SpMpWidgetType.update(context: Context, id: GlanceId) {
println("Updating widget $id of type $this")
incrementUpdateValue()
widget_instances[this]!!.update(context, id)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.toasterofbread.spmp.widget

import android.content.Context
import androidx.glance.GlanceId
import androidx.glance.action.Action
import androidx.glance.action.ActionParameters
import androidx.glance.action.actionParametersOf
import androidx.glance.appwidget.action.ActionCallback
import androidx.glance.appwidget.action.actionRunCallback
import com.toasterofbread.spmp.widget.action.TypeWidgetClickAction
import com.toasterofbread.spmp.widget.configuration.SpMpWidgetConfiguration
import kotlinx.serialization.encodeToString

internal class WidgetActionCallback: ActionCallback {
override suspend fun onAction(
context: Context,
glanceId: GlanceId,
parameters: ActionParameters
) {
val serialisedAction: String = parameters[keyAction] ?: return
val configuration: SpMpWidgetConfiguration<TypeWidgetClickAction> = SpMpWidgetConfiguration.json.decodeFromString(serialisedAction)
SpMpWidget.runActionOnWidget(configuration.type_configuration.click_action, glanceId)
}

companion object {
val keyAction: ActionParameters.Key<String> = ActionParameters.Key("action")

operator fun invoke(configuration: SpMpWidgetConfiguration<out TypeWidgetClickAction>): Action =
actionRunCallback<WidgetActionCallback>(
actionParametersOf(
keyAction to SpMpWidgetConfiguration.json.encodeToString(
configuration
)
)
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.toasterofbread.spmp.widget
import android.content.Context
import androidx.media3.common.MediaItem
import androidx.media3.common.Player
import androidx.media3.common.Timeline
import androidx.media3.common.Tracks
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
Expand Down Expand Up @@ -40,7 +41,7 @@ class WidgetUpdateListener(private val context: Context): Player.Listener {
}
}

override fun onTracksChanged(tracks: Tracks) {
override fun onTimelineChanged(timeline: Timeline, reason: Int) {
song_transition_widget_update_coroutine_scope.launch {
for (type in SpMpWidgetType.entries) {
if (type.update_types.contains(WidgetUpdateType.OnQueueChange)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,20 @@ import android.graphics.Paint
import android.graphics.Typeface
import android.text.TextPaint
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.text.font.Font
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.sp
import androidx.core.graphics.withClip
import androidx.core.util.TypedValueCompat.spToPx
import androidx.glance.GlanceModifier
import androidx.glance.Image
import androidx.glance.ImageProvider
import androidx.glance.LocalContext
import androidx.glance.layout.ContentScale
import androidx.glance.layout.wrapContentWidth
import com.toasterofbread.spmp.widget.mapper.toAndroidTypeface
import dev.toastbits.composekit.platform.composable.theme.LocalApplicationTheme
Expand All @@ -33,27 +33,34 @@ fun GlanceText(
modifier: GlanceModifier = GlanceModifier,
font: FontResource? = null,
font_size: TextUnit = 15.sp,
alpha: Float = 1f
alpha: Float = 1f,
max_width: Dp? = null
) {
val context: Context = LocalContext.current
val colour: Color = LocalApplicationTheme.current.on_background.copy(alpha)
val typeface: Typeface? = font?.let { Font(it) }?.toAndroidTypeface()

val max_width_px: Int? = with (LocalDensity.current) {
max_width?.roundToPx()
}

val image: Bitmap =
remember(text, font_size, colour, typeface) {
remember(text, font_size, colour, typeface, max_width_px) {
context.textAsBitmap(
text = text,
fontSize = font_size,
color = colour,
font = typeface,
letterSpacing = 0.03.sp.value
letterSpacing = 0.03.sp.value,
maxWidth = max_width_px
)
} ?: return

Image(
modifier = modifier.wrapContentWidth(),
provider = ImageProvider(image),
contentDescription = text
contentDescription = text,
contentScale = ContentScale.Crop
)
}

Expand All @@ -63,7 +70,8 @@ private fun Context.textAsBitmap(
fontSize: TextUnit,
color: Color = Color.Black,
letterSpacing: Float = 0.1f,
font: Typeface? = null
font: Typeface? = null,
maxWidth: Int? = null
): Bitmap? {
val paint: TextPaint = TextPaint(Paint.ANTI_ALIAS_FLAG)
paint.textSize = spToPx(fontSize.value, this.resources.displayMetrics)
Expand All @@ -72,15 +80,29 @@ private fun Context.textAsBitmap(
paint.typeface = font ?: Typeface.DEFAULT

val baseline: Float = -paint.ascent()
val width: Int = (paint.measureText(text)).toInt()
val width: Int = paint.measureText(text).toInt()
val height: Int = (baseline + paint.descent()).toInt()

if (width <= 0 || height <= 0) {
return null
}

val image: Bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
val image: Bitmap = Bitmap.createBitmap(minOf(width, maxWidth ?: width), height, Bitmap.Config.ARGB_8888)
val canvas: Canvas = Canvas(image)
canvas.drawText(text, 0f, baseline, paint)

if (maxWidth == null || width <= maxWidth) {
canvas.drawText(text, 0f, baseline, paint)
return image
}

val ellipsis: String = "..."
val ellipsisWidth: Float = paint.measureText(ellipsis)

canvas.withClip(0f, 0f, maxWidth - ellipsisWidth, height.toFloat()) {
drawText(text, 0f, baseline, paint)
}

canvas.drawText(ellipsis, maxWidth - ellipsisWidth, baseline, paint)

return image
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import org.jetbrains.compose.resources.stringResource
import spmp.shared.generated.resources.Res
import spmp.shared.generated.resources.widget_lyrics_status_no_lyrics

internal abstract class LyricsWidget: SpMpWidget<LyricsWidgetClickAction, LyricsWidgetConfig>() {
internal abstract class LyricsWidget: SpMpWidget<LyricsWidgetClickAction, LyricsWidgetConfig>(false) {
private var current_song: Song? by mutableStateOf(null)
private var lyrics_state: SongLyricsLoader.ItemState? by mutableStateOf(null)
private var show_readings: Boolean by mutableStateOf(false)
Expand Down
Loading

0 comments on commit 5f65bff

Please sign in to comment.