From 7ff161308babb1d4801a9af618da0fb86d2a02be Mon Sep 17 00:00:00 2001 From: MM20 <15646950+MM2-0@users.noreply.github.com> Date: Wed, 26 Jun 2024 19:33:30 +0200 Subject: [PATCH] More compose view implementations --- app/app/build.gradle.kts | 2 +- app/ui/build.gradle.kts | 1 + .../ui/component/view/AndroidView.kt | 36 +++- .../ui/component/view/FrameLayout.kt | 2 +- .../ui/component/view/ImageButton.kt | 50 ++++++ .../launcher2/ui/component/view/ImageView.kt | 6 - .../ui/component/view/LinearLayout.kt | 5 +- .../launcher2/ui/component/view/ListView.kt | 7 +- .../ui/component/view/RelativeLayout.kt | 158 ++++++++++++++++++ .../launcher2/ui/component/view/TextView.kt | 55 +++--- data/widgets/build.gradle.kts | 2 +- gradle/libs.versions.toml | 4 +- libs/nextcloud/build.gradle.kts | 2 +- libs/owncloud/build.gradle.kts | 2 +- 14 files changed, 295 insertions(+), 37 deletions(-) create mode 100644 app/ui/src/main/java/de/mm20/launcher2/ui/component/view/ImageButton.kt create mode 100644 app/ui/src/main/java/de/mm20/launcher2/ui/component/view/RelativeLayout.kt diff --git a/app/app/build.gradle.kts b/app/app/build.gradle.kts index e894d861..a75118cf 100644 --- a/app/app/build.gradle.kts +++ b/app/app/build.gradle.kts @@ -115,7 +115,7 @@ dependencies { implementation(libs.androidx.core) implementation(libs.androidx.exifinterface) implementation(libs.materialcomponents.core) - implementation(libs.androidx.constraintlayout) + implementation(libs.androidx.constraintlayout.views) implementation(libs.bundles.androidx.lifecycle) diff --git a/app/ui/build.gradle.kts b/app/ui/build.gradle.kts index dc39c6bf..8d69585f 100644 --- a/app/ui/build.gradle.kts +++ b/app/ui/build.gradle.kts @@ -78,6 +78,7 @@ dependencies { implementation(libs.androidx.compose.materialicons) implementation(libs.androidx.compose.animation) implementation(libs.androidx.compose.animationgraphics) + implementation(libs.androidx.constraintlayout.compose) implementation(libs.androidx.navigation.compose) diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/component/view/AndroidView.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/component/view/AndroidView.kt index 5ac30fc8..47490009 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/component/view/AndroidView.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/component/view/AndroidView.kt @@ -5,21 +5,24 @@ import android.graphics.drawable.Drawable import android.view.View import android.view.ViewGroup import android.widget.FrameLayout +import android.widget.ImageButton import android.widget.ImageView import android.widget.LinearLayout import android.widget.ListView +import android.widget.RelativeLayout import android.widget.TextView import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.combinedClickable -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.layout.wrapContentHeight import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.material3.Text @@ -32,6 +35,7 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.composed import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.draw.drawWithContent import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.graphics.nativeCanvas @@ -81,6 +85,8 @@ fun ComposeAndroidView( when (view) { is FrameLayout -> ComposeFrameLayout(view, mod) is LinearLayout -> ComposeLinearLayout(view, mod) + is RelativeLayout -> ComposeRelativeLayout(view, mod) + is ImageButton -> ComposeImageButton(view, mod) is ListView -> ComposeListView(view, mod) is TextView -> ComposeTextView(view, mod) is ImageView -> ComposeImageView(view, mod) @@ -115,6 +121,17 @@ internal fun Modifier.view(view: View): Modifier = this then Modifier.composed { 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) { Modifier.padding( start = view.paddingStart.toDp(), @@ -153,7 +170,22 @@ internal fun Modifier.view(view: View): Modifier = this then Modifier.composed { 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 } diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/component/view/FrameLayout.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/component/view/FrameLayout.kt index 4772eba5..14e556a4 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/component/view/FrameLayout.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/component/view/FrameLayout.kt @@ -36,7 +36,7 @@ private fun Modifier.frameLayoutChild(scope: BoxScope, params: ViewGroup.LayoutP Modifier.layoutParams(params) then with(scope) { 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.BOTTOM -> Alignment.BottomStart Gravity.END or Gravity.TOP -> Alignment.TopEnd diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/component/view/ImageButton.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/component/view/ImageButton.kt new file mode 100644 index 00000000..ec1dd2c1 --- /dev/null +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/component/view/ImageButton.kt @@ -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 + }, + ) +} diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/component/view/ImageView.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/component/view/ImageView.kt index 41f60465..76d560a8 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/component/view/ImageView.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/component/view/ImageView.kt @@ -1,16 +1,10 @@ package de.mm20.launcher2.ui.component.view -import android.graphics.BlendModeColorFilter -import android.graphics.ColorMatrixColorFilter 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 diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/component/view/LinearLayout.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/component/view/LinearLayout.kt index f0100771..55fc1162 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/component/view/LinearLayout.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/component/view/LinearLayout.kt @@ -8,7 +8,6 @@ import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.RowScope -import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -25,7 +24,7 @@ internal fun ComposeLinearLayout( Gravity.START -> Alignment.Start Gravity.CENTER_HORIZONTAL -> Alignment.CenterHorizontally Gravity.END -> Alignment.End - else -> Alignment.Start + else -> Alignment.CenterHorizontally } val verticalArrangement = when (view.gravity and Gravity.VERTICAL_GRAVITY_MASK) { Gravity.TOP -> Arrangement.Top @@ -60,7 +59,7 @@ internal fun ComposeLinearLayout( Gravity.TOP -> Alignment.Top Gravity.CENTER_VERTICAL -> Alignment.CenterVertically Gravity.BOTTOM -> Alignment.Bottom - else -> Alignment.Top + else -> Alignment.CenterVertically } Row( modifier = modifier, diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/component/view/ListView.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/component/view/ListView.kt index 656f84da..852f0853 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/component/view/ListView.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/component/view/ListView.kt @@ -1,9 +1,12 @@ package de.mm20.launcher2.ui.component.view import android.widget.ListView +import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import de.mm20.launcher2.ui.ktx.toDp @Composable fun ComposeListView( @@ -21,7 +24,9 @@ fun ComposeListView( val itemView = adapter.getView(index, null, view) ComposeAndroidView( itemView, - modifier = Modifier.layoutParams(itemView.layoutParams) + modifier = Modifier + .padding(top = if (index != 0) view.dividerHeight.toDp() else 0.dp) + .layoutParams(itemView.layoutParams) ) } } diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/component/view/RelativeLayout.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/component/view/RelativeLayout.kt new file mode 100644 index 00000000..269766f9 --- /dev/null +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/component/view/RelativeLayout.kt @@ -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 +) = 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) + } + } + } \ No newline at end of file diff --git a/app/ui/src/main/java/de/mm20/launcher2/ui/component/view/TextView.kt b/app/ui/src/main/java/de/mm20/launcher2/ui/component/view/TextView.kt index d5780627..15596206 100644 --- a/app/ui/src/main/java/de/mm20/launcher2/ui/component/view/TextView.kt +++ b/app/ui/src/main/java/de/mm20/launcher2/ui/component/view/TextView.kt @@ -8,10 +8,10 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalDensity 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.FontStyle import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.font.SystemFontFamily import androidx.compose.ui.text.style.TextAlign import de.mm20.launcher2.ktx.isAtLeastApiLevel @@ -24,29 +24,46 @@ internal fun ComposeTextView( Text( text = view.text.toString(), color = Color(view.textColors.defaultColor), - style = MaterialTheme.typography.bodyMedium.copy( - fontSize = with(density) { - view.textSize.toSp() - }, - fontWeight = if (isAtLeastApiLevel(28)) { - FontWeight(view.typeface.weight) - } else if (view.typeface.isBold) { - FontWeight.Bold - } else { - FontWeight.Normal - }, - fontStyle = if (view.typeface.isItalic) { - FontStyle.Italic - } else { - FontStyle.Normal - }, - ), + style = MaterialTheme.typography.bodyMedium, + lineHeight = with(density) { + view.lineHeight.toSp() + }, + maxLines = view.maxLines.coerceAtLeast(1), modifier = modifier, textAlign = when (view.textAlignment) { TextView.TEXT_ALIGNMENT_CENTER -> TextAlign.Center TextView.TEXT_ALIGNMENT_TEXT_START -> TextAlign.Start TextView.TEXT_ALIGNMENT_TEXT_END -> TextAlign.End 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 + ) } \ No newline at end of file diff --git a/data/widgets/build.gradle.kts b/data/widgets/build.gradle.kts index b43b221d..84602713 100644 --- a/data/widgets/build.gradle.kts +++ b/data/widgets/build.gradle.kts @@ -38,7 +38,7 @@ dependencies { implementation(libs.bundles.kotlin) implementation(libs.androidx.core) implementation(libs.androidx.appcompat) - implementation(libs.androidx.constraintlayout) + implementation(libs.androidx.constraintlayout.views) implementation(libs.bundles.androidx.lifecycle) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e220347c..795cb5ca 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -33,6 +33,7 @@ androidx-browser = "1.8.0" androidx-palette = "1.0.0" androidx-media2 = "1.3.0" androidx-room = "2.6.1" +androidx-constraint-layout = "1.1.0-alpha13" accompanist = "0.33.2-alpha" 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-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-exifinterface = { group = "androidx.exifinterface", name = "exifinterface", version = "1.3.7" } androidx-securitycrypto = { group = "androidx.security", name = "security-crypto", version = "1.1.0-alpha03" } diff --git a/libs/nextcloud/build.gradle.kts b/libs/nextcloud/build.gradle.kts index e9e7d2ac..81a75a3b 100644 --- a/libs/nextcloud/build.gradle.kts +++ b/libs/nextcloud/build.gradle.kts @@ -43,7 +43,7 @@ dependencies { implementation(libs.androidx.appcompat) implementation(libs.materialcomponents.core) implementation(libs.androidx.browser) - implementation(libs.androidx.constraintlayout) + implementation(libs.androidx.constraintlayout.views) implementation(libs.androidx.securitycrypto) implementation(libs.bundles.androidx.lifecycle) diff --git a/libs/owncloud/build.gradle.kts b/libs/owncloud/build.gradle.kts index 97895c18..2d6f5689 100644 --- a/libs/owncloud/build.gradle.kts +++ b/libs/owncloud/build.gradle.kts @@ -43,7 +43,7 @@ dependencies { implementation(libs.androidx.appcompat) implementation(libs.materialcomponents.core) implementation(libs.androidx.browser) - implementation(libs.androidx.constraintlayout) + implementation(libs.androidx.constraintlayout.views) implementation(libs.androidx.securitycrypto) implementation(libs.bundles.androidx.lifecycle)