More compose view implementations

This commit is contained in:
MM20 2024-06-26 19:33:30 +02:00
parent 15cb4b4f29
commit 7ff161308b
No known key found for this signature in database
GPG Key ID: 0B61A8F2DEAFA389
14 changed files with 295 additions and 37 deletions

View File

@ -115,7 +115,7 @@ dependencies {
implementation(libs.androidx.core) implementation(libs.androidx.core)
implementation(libs.androidx.exifinterface) implementation(libs.androidx.exifinterface)
implementation(libs.materialcomponents.core) implementation(libs.materialcomponents.core)
implementation(libs.androidx.constraintlayout) implementation(libs.androidx.constraintlayout.views)
implementation(libs.bundles.androidx.lifecycle) implementation(libs.bundles.androidx.lifecycle)

View File

@ -78,6 +78,7 @@ dependencies {
implementation(libs.androidx.compose.materialicons) implementation(libs.androidx.compose.materialicons)
implementation(libs.androidx.compose.animation) implementation(libs.androidx.compose.animation)
implementation(libs.androidx.compose.animationgraphics) implementation(libs.androidx.compose.animationgraphics)
implementation(libs.androidx.constraintlayout.compose)
implementation(libs.androidx.navigation.compose) implementation(libs.androidx.navigation.compose)

View File

@ -5,21 +5,24 @@ import android.graphics.drawable.Drawable
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.FrameLayout import android.widget.FrameLayout
import android.widget.ImageButton
import android.widget.ImageView import android.widget.ImageView
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.ListView import android.widget.ListView
import android.widget.RelativeLayout
import android.widget.TextView import android.widget.TextView
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.combinedClickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.width import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.material3.Text import androidx.compose.material3.Text
@ -32,6 +35,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.composed import androidx.compose.ui.composed
import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.draw.drawBehind
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.graphics.nativeCanvas import androidx.compose.ui.graphics.nativeCanvas
@ -81,6 +85,8 @@ fun ComposeAndroidView(
when (view) { when (view) {
is FrameLayout -> ComposeFrameLayout(view, mod) is FrameLayout -> ComposeFrameLayout(view, mod)
is LinearLayout -> ComposeLinearLayout(view, mod) is LinearLayout -> ComposeLinearLayout(view, mod)
is RelativeLayout -> ComposeRelativeLayout(view, mod)
is ImageButton -> ComposeImageButton(view, mod)
is ListView -> ComposeListView(view, mod) is ListView -> ComposeListView(view, mod)
is TextView -> ComposeTextView(view, mod) is TextView -> ComposeTextView(view, mod)
is ImageView -> ComposeImageView(view, mod) is ImageView -> ComposeImageView(view, mod)
@ -115,6 +121,17 @@ internal fun Modifier.view(view: View): Modifier = this then Modifier.composed {
else -> Modifier else -> Modifier
} }
val foregroundDrawable = view.foreground
val foreground = if (foregroundDrawable != null) {
Modifier.drawWithContent {
drawContent()
foregroundDrawable.setBounds(
0, 0, size.width.roundToInt(), size.height.roundToInt()
)
foregroundDrawable.draw(this.drawContext.canvas.nativeCanvas)
}
} else Modifier
val padding = with(density) { val padding = with(density) {
Modifier.padding( Modifier.padding(
start = view.paddingStart.toDp(), start = view.paddingStart.toDp(),
@ -153,7 +170,22 @@ internal fun Modifier.view(view: View): Modifier = this then Modifier.composed {
cameraDistance = view.cameraDistance cameraDistance = view.cameraDistance
} }
background then clickable then padding then graphicsLayer val minWidth = if (view.minimumWidth > 0)
Modifier.widthIn(
min = with(density) { view.minimumWidth.toDp() }
)
else Modifier
val minHeight = if (view.minimumHeight > 0)
Modifier.heightIn(
min = with(density) { view.minimumHeight.toDp() }
)
else Modifier
minWidth then minHeight then
background then foreground then
clickable then padding then graphicsLayer
} }

View File

@ -36,7 +36,7 @@ private fun Modifier.frameLayoutChild(scope: BoxScope, params: ViewGroup.LayoutP
Modifier.layoutParams(params) then Modifier.layoutParams(params) then
with(scope) { with(scope) {
if (params !is FrameLayout.LayoutParams) return@with Modifier if (params !is FrameLayout.LayoutParams) return@with Modifier
val alignment = when (params.gravity) { val alignment = when (params.gravity and (Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK or Gravity.VERTICAL_GRAVITY_MASK)) {
Gravity.START or Gravity.TOP -> Alignment.TopStart Gravity.START or Gravity.TOP -> Alignment.TopStart
Gravity.START or Gravity.BOTTOM -> Alignment.BottomStart Gravity.START or Gravity.BOTTOM -> Alignment.BottomStart
Gravity.END or Gravity.TOP -> Alignment.TopEnd Gravity.END or Gravity.TOP -> Alignment.TopEnd

View File

@ -0,0 +1,50 @@
package de.mm20.launcher2.ui.component.view
import android.graphics.BlendModeColorFilter
import android.graphics.ColorMatrixColorFilter
import android.widget.ImageButton
import android.widget.ImageView
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.ColorMatrix
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.layout.ContentScale
import coil.compose.AsyncImage
@Composable
internal fun ComposeImageButton(
view: ImageButton,
modifier: Modifier,
) {
AsyncImage(
modifier = modifier.alpha(view.imageAlpha / 255f),
model = view.drawable,
contentDescription = view.contentDescription?.toString(),
contentScale = when (view.scaleType) {
ImageView.ScaleType.CENTER -> ContentScale.None
ImageView.ScaleType.FIT_XY -> ContentScale.FillBounds
ImageView.ScaleType.FIT_START,
ImageView.ScaleType.FIT_CENTER,
ImageView.ScaleType.FIT_END -> ContentScale.Fit
ImageView.ScaleType.CENTER_CROP -> ContentScale.Crop
ImageView.ScaleType.CENTER_INSIDE -> ContentScale.Inside
else -> ContentScale.None
},
alignment = when (view.scaleType) {
ImageView.ScaleType.FIT_XY,
ImageView.ScaleType.CENTER,
ImageView.ScaleType.FIT_CENTER,
ImageView.ScaleType.CENTER_CROP,
ImageView.ScaleType.CENTER_INSIDE -> Alignment.Center
ImageView.ScaleType.FIT_START -> Alignment.TopStart
ImageView.ScaleType.FIT_END -> Alignment.BottomEnd
else -> Alignment.Center
},
)
}

View File

@ -1,16 +1,10 @@
package de.mm20.launcher2.ui.component.view package de.mm20.launcher2.ui.component.view
import android.graphics.BlendModeColorFilter
import android.graphics.ColorMatrixColorFilter
import android.widget.ImageView import android.widget.ImageView
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.alpha
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.ColorFilter
import androidx.compose.ui.graphics.ColorMatrix
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.layout.ContentScale
import coil.compose.AsyncImage import coil.compose.AsyncImage

View File

@ -8,7 +8,6 @@ import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
@ -25,7 +24,7 @@ internal fun ComposeLinearLayout(
Gravity.START -> Alignment.Start Gravity.START -> Alignment.Start
Gravity.CENTER_HORIZONTAL -> Alignment.CenterHorizontally Gravity.CENTER_HORIZONTAL -> Alignment.CenterHorizontally
Gravity.END -> Alignment.End Gravity.END -> Alignment.End
else -> Alignment.Start else -> Alignment.CenterHorizontally
} }
val verticalArrangement = when (view.gravity and Gravity.VERTICAL_GRAVITY_MASK) { val verticalArrangement = when (view.gravity and Gravity.VERTICAL_GRAVITY_MASK) {
Gravity.TOP -> Arrangement.Top Gravity.TOP -> Arrangement.Top
@ -60,7 +59,7 @@ internal fun ComposeLinearLayout(
Gravity.TOP -> Alignment.Top Gravity.TOP -> Alignment.Top
Gravity.CENTER_VERTICAL -> Alignment.CenterVertically Gravity.CENTER_VERTICAL -> Alignment.CenterVertically
Gravity.BOTTOM -> Alignment.Bottom Gravity.BOTTOM -> Alignment.Bottom
else -> Alignment.Top else -> Alignment.CenterVertically
} }
Row( Row(
modifier = modifier, modifier = modifier,

View File

@ -1,9 +1,12 @@
package de.mm20.launcher2.ui.component.view package de.mm20.launcher2.ui.component.view
import android.widget.ListView import android.widget.ListView
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import de.mm20.launcher2.ui.ktx.toDp
@Composable @Composable
fun ComposeListView( fun ComposeListView(
@ -21,7 +24,9 @@ fun ComposeListView(
val itemView = adapter.getView(index, null, view) val itemView = adapter.getView(index, null, view)
ComposeAndroidView( ComposeAndroidView(
itemView, itemView,
modifier = Modifier.layoutParams(itemView.layoutParams) modifier = Modifier
.padding(top = if (index != 0) view.dividerHeight.toDp() else 0.dp)
.layoutParams(itemView.layoutParams)
) )
} }
} }

View File

@ -0,0 +1,158 @@
package de.mm20.launcher2.ui.component.view
import android.view.View
import android.view.ViewGroup
import android.widget.LinearLayout
import android.widget.RelativeLayout
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.constraintlayout.compose.ConstrainedLayoutReference
import androidx.constraintlayout.compose.ConstraintLayout
import androidx.constraintlayout.compose.ConstraintLayoutScope
import androidx.core.view.children
@Composable
internal fun ComposeRelativeLayout(
view: RelativeLayout,
modifier: Modifier,
) {
ConstraintLayout(
modifier = modifier,
) {
val refs = view.children.filterNot { it.id == View.NO_ID }.associate { it.id to createRef() }
for (child in view.children) {
ComposeAndroidView(
child,
modifier = Modifier.relativeLayoutChild(
this@ConstraintLayout,
child.layoutParams,
child.id,
refs,
)
)
}
}
}
private fun Modifier.relativeLayoutChild(
scope: ConstraintLayoutScope,
params: ViewGroup.LayoutParams,
id: Int,
refs: Map<Int, ConstrainedLayoutReference>
) = this then
Modifier.layoutParams(params) then
with(scope) {
if (params !is RelativeLayout.LayoutParams) return@with Modifier
val ref = refs[id] ?: return@with Modifier
Modifier.constrainAs(ref) {
val above = refs[params.getRule(RelativeLayout.ABOVE)]
if (above != null) {
bottom.linkTo(above.top)
}
val alignBaseline = refs[params.getRule(RelativeLayout.ALIGN_BASELINE)]
if (alignBaseline != null) {
baseline.linkTo(alignBaseline.baseline)
}
val alignBottom = refs[params.getRule(RelativeLayout.ALIGN_BOTTOM)]
if (alignBottom != null) {
bottom.linkTo(alignBottom.bottom)
}
val alignEnd = refs[params.getRule(RelativeLayout.ALIGN_END)]
if (alignEnd != null) {
end.linkTo(alignEnd.end)
}
val alignLeft = refs[params.getRule(RelativeLayout.ALIGN_LEFT)]
if (alignLeft != null) {
absoluteLeft.linkTo(alignLeft.absoluteLeft)
}
val alignParentBottom = params.getRule(RelativeLayout.ALIGN_PARENT_BOTTOM)
if (alignParentBottom == RelativeLayout.TRUE) {
bottom.linkTo(parent.bottom)
}
val alignParentEnd = params.getRule(RelativeLayout.ALIGN_PARENT_END)
if (alignParentEnd == RelativeLayout.TRUE) {
end.linkTo(parent.end)
}
val alignParentLeft = params.getRule(RelativeLayout.ALIGN_PARENT_LEFT)
if (alignParentLeft == RelativeLayout.TRUE) {
absoluteLeft.linkTo(parent.absoluteLeft)
}
val alignParentRight = params.getRule(RelativeLayout.ALIGN_PARENT_RIGHT)
if (alignParentRight == RelativeLayout.TRUE) {
absoluteRight.linkTo(parent.absoluteRight)
}
val alignParentStart = params.getRule(RelativeLayout.ALIGN_PARENT_START)
if (alignParentStart == RelativeLayout.TRUE) {
start.linkTo(parent.start)
}
val alignParentTop = params.getRule(RelativeLayout.ALIGN_PARENT_TOP)
if (alignParentTop == RelativeLayout.TRUE) {
top.linkTo(parent.top)
}
val alignRight = refs[params.getRule(RelativeLayout.ALIGN_RIGHT)]
if (alignRight != null) {
absoluteRight.linkTo(alignRight.absoluteRight)
}
val alignStart = refs[params.getRule(RelativeLayout.ALIGN_START)]
if (alignStart != null) {
start.linkTo(alignStart.start)
}
val alignTop = refs[params.getRule(RelativeLayout.ALIGN_TOP)]
if (alignTop != null) {
top.linkTo(alignTop.top)
}
val below = refs[params.getRule(RelativeLayout.BELOW)]
if (below != null) {
top.linkTo(below.bottom)
}
val centerHorizontal = params.getRule(RelativeLayout.CENTER_HORIZONTAL)
if (centerHorizontal == RelativeLayout.TRUE) {
centerHorizontallyTo(parent)
}
val centerInParent = params.getRule(RelativeLayout.CENTER_IN_PARENT)
if (centerInParent == RelativeLayout.TRUE) {
centerTo(parent)
}
val centerVertical = params.getRule(RelativeLayout.CENTER_VERTICAL)
if (centerVertical == RelativeLayout.TRUE) {
centerVerticallyTo(parent)
}
val toEndOf = refs[params.getRule(RelativeLayout.END_OF)]
if (toEndOf != null) {
start.linkTo(toEndOf.end)
}
val toLeftOf = refs[params.getRule(RelativeLayout.LEFT_OF)]
if (toLeftOf != null) {
absoluteRight.linkTo(toLeftOf.absoluteLeft)
}
val toRightOf = refs[params.getRule(RelativeLayout.RIGHT_OF)]
if (toRightOf != null) {
absoluteLeft.linkTo(toRightOf.absoluteRight)
}
val toStartOf = refs[params.getRule(RelativeLayout.START_OF)]
if (toStartOf != null) {
end.linkTo(toStartOf.start)
}
}
}

View File

@ -8,10 +8,10 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.font.DeviceFontFamilyName import androidx.compose.ui.text.font.DeviceFontFamilyName
import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontStyle
import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.font.SystemFontFamily
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import de.mm20.launcher2.ktx.isAtLeastApiLevel import de.mm20.launcher2.ktx.isAtLeastApiLevel
@ -24,29 +24,46 @@ internal fun ComposeTextView(
Text( Text(
text = view.text.toString(), text = view.text.toString(),
color = Color(view.textColors.defaultColor), color = Color(view.textColors.defaultColor),
style = MaterialTheme.typography.bodyMedium.copy( style = MaterialTheme.typography.bodyMedium,
fontSize = with(density) { lineHeight = with(density) {
view.textSize.toSp() view.lineHeight.toSp()
}, },
fontWeight = if (isAtLeastApiLevel(28)) { maxLines = view.maxLines.coerceAtLeast(1),
FontWeight(view.typeface.weight)
} else if (view.typeface.isBold) {
FontWeight.Bold
} else {
FontWeight.Normal
},
fontStyle = if (view.typeface.isItalic) {
FontStyle.Italic
} else {
FontStyle.Normal
},
),
modifier = modifier, modifier = modifier,
textAlign = when (view.textAlignment) { textAlign = when (view.textAlignment) {
TextView.TEXT_ALIGNMENT_CENTER -> TextAlign.Center TextView.TEXT_ALIGNMENT_CENTER -> TextAlign.Center
TextView.TEXT_ALIGNMENT_TEXT_START -> TextAlign.Start TextView.TEXT_ALIGNMENT_TEXT_START -> TextAlign.Start
TextView.TEXT_ALIGNMENT_TEXT_END -> TextAlign.End TextView.TEXT_ALIGNMENT_TEXT_END -> TextAlign.End
else -> TextAlign.Start else -> TextAlign.Start
} },
letterSpacing = with(density) {
view.letterSpacing.toSp()
},
fontStyle = if (view.typeface.isItalic) {
FontStyle.Italic
} else {
FontStyle.Normal
},
fontSize = with(density) {
view.textSize.toSp()
},
fontWeight = if (isAtLeastApiLevel(28)) {
FontWeight(view.typeface.weight)
} else if (view.typeface.isBold) {
FontWeight.Bold
} else {
FontWeight.Normal
},
minLines = view.minLines.coerceAtLeast(1),
fontFamily = if (isAtLeastApiLevel(34)) {
view.typeface?.systemFontFamilyName?.let {
try {
FontFamily(Font(DeviceFontFamilyName(it)))
} catch (e: IllegalArgumentException) {
null
}
}
} else null
) )
} }

View File

@ -38,7 +38,7 @@ dependencies {
implementation(libs.bundles.kotlin) implementation(libs.bundles.kotlin)
implementation(libs.androidx.core) implementation(libs.androidx.core)
implementation(libs.androidx.appcompat) implementation(libs.androidx.appcompat)
implementation(libs.androidx.constraintlayout) implementation(libs.androidx.constraintlayout.views)
implementation(libs.bundles.androidx.lifecycle) implementation(libs.bundles.androidx.lifecycle)

View File

@ -33,6 +33,7 @@ androidx-browser = "1.8.0"
androidx-palette = "1.0.0" androidx-palette = "1.0.0"
androidx-media2 = "1.3.0" androidx-media2 = "1.3.0"
androidx-room = "2.6.1" androidx-room = "2.6.1"
androidx-constraint-layout = "1.1.0-alpha13"
accompanist = "0.33.2-alpha" accompanist = "0.33.2-alpha"
coil = "2.3.0" coil = "2.3.0"
@ -97,7 +98,8 @@ accompanist-pagerindicators = { group = "com.google.accompanist", name = "accomp
accompanist-flowlayout = { group = "com.google.accompanist", name = "accompanist-flowlayout", version.ref = "accompanist" } accompanist-flowlayout = { group = "com.google.accompanist", name = "accompanist-flowlayout", version.ref = "accompanist" }
accompanist-navigationanimation = { group = "com.google.accompanist", name = "accompanist-navigation-animation", version.ref = "accompanist" } accompanist-navigationanimation = { group = "com.google.accompanist", name = "accompanist-navigation-animation", version.ref = "accompanist" }
androidx-constraintlayout = { group = "androidx.constraintlayout", name = "constraintlayout", version = "2.1.4" } androidx-constraintlayout-views = { group = "androidx.constraintlayout", name = "constraintlayout", version = "2.1.4" }
androidx-constraintlayout-compose = { group = "androidx.constraintlayout", name = "constraintlayout-compose", version.ref = "androidx-constraint-layout" }
androidx-transition = { group = "androidx.transition", name = "transition", version = "1.4.1" } androidx-transition = { group = "androidx.transition", name = "transition", version = "1.4.1" }
androidx-exifinterface = { group = "androidx.exifinterface", name = "exifinterface", version = "1.3.7" } androidx-exifinterface = { group = "androidx.exifinterface", name = "exifinterface", version = "1.3.7" }
androidx-securitycrypto = { group = "androidx.security", name = "security-crypto", version = "1.1.0-alpha03" } androidx-securitycrypto = { group = "androidx.security", name = "security-crypto", version = "1.1.0-alpha03" }

View File

@ -43,7 +43,7 @@ dependencies {
implementation(libs.androidx.appcompat) implementation(libs.androidx.appcompat)
implementation(libs.materialcomponents.core) implementation(libs.materialcomponents.core)
implementation(libs.androidx.browser) implementation(libs.androidx.browser)
implementation(libs.androidx.constraintlayout) implementation(libs.androidx.constraintlayout.views)
implementation(libs.androidx.securitycrypto) implementation(libs.androidx.securitycrypto)
implementation(libs.bundles.androidx.lifecycle) implementation(libs.bundles.androidx.lifecycle)

View File

@ -43,7 +43,7 @@ dependencies {
implementation(libs.androidx.appcompat) implementation(libs.androidx.appcompat)
implementation(libs.materialcomponents.core) implementation(libs.materialcomponents.core)
implementation(libs.androidx.browser) implementation(libs.androidx.browser)
implementation(libs.androidx.constraintlayout) implementation(libs.androidx.constraintlayout.views)
implementation(libs.androidx.securitycrypto) implementation(libs.androidx.securitycrypto)
implementation(libs.bundles.androidx.lifecycle) implementation(libs.bundles.androidx.lifecycle)