Rebuild unit converter in Jetpack Compose

This commit is contained in:
MM20 2021-12-11 17:48:40 +01:00
parent 89a2754eaf
commit 120bca4b39
No known key found for this signature in database
GPG Key ID: 0B61A8F2DEAFA389
7 changed files with 149 additions and 262 deletions

View File

@ -109,8 +109,6 @@ dependencies {
implementation(libs.bundles.materialdialogs) implementation(libs.bundles.materialdialogs)
implementation(libs.bundles.groupie)
implementation(libs.draglinearlayout) implementation(libs.draglinearlayout)
implementation(libs.viewpropertyobjectanimator) implementation(libs.viewpropertyobjectanimator)

View File

@ -100,13 +100,6 @@ val OpenSourceLicenses = arrayOf(
copyrightNote = "Copyright (C) 2020 Wasabeef", copyrightNote = "Copyright (C) 2020 Wasabeef",
url = "https://github.com/afollestad/material-dialogs" url = "https://github.com/afollestad/material-dialogs"
), ),
OpenSourceLibrary(
name = "Groupie",
description = "Groupie is a simple, flexible library for complex RecyclerView layouts.",
licenseName = R.string.mit_license_name,
licenseText = R.raw.license_mit,
url = "https://github.com/lisawray/groupie"
),
OpenSourceLibrary( OpenSourceLibrary(
name = "DragLinearLayout", name = "DragLinearLayout",
description = "An Android LinearLayout that supports draggable and swappable child Views", description = "An Android LinearLayout that supports draggable and swappable child Views",

View File

@ -321,21 +321,6 @@ dependencyResolutionManagement {
) )
) )
version("groupie", "2.8.0")
alias("groupie.core")
.to("com.xwray", "groupie")
.versionRef("groupie")
alias("groupie.ktx")
.to("com.xwray", "groupie-kotlin-android-extensions")
.versionRef("groupie")
bundle(
"groupie",
listOf(
"groupie.core",
"groupie.ktx"
)
)
alias("draglinearlayout") alias("draglinearlayout")
.to("com.jmedeisis", "draglinearlayout") .to("com.jmedeisis", "draglinearlayout")
.version("1.1.0") .version("1.1.0")

View File

@ -71,7 +71,6 @@ dependencies {
implementation(libs.glide) implementation(libs.glide)
implementation(libs.draglinearlayout) implementation(libs.draglinearlayout)
implementation(libs.lottie.core) implementation(libs.lottie.core)
implementation(libs.bundles.groupie)
implementation(libs.glidetransformations) implementation(libs.glidetransformations)
implementation(libs.accompanist.insets) implementation(libs.accompanist.insets)

View File

@ -1,49 +1,37 @@
package de.mm20.launcher2.ui.legacy.component package de.mm20.launcher2.ui.legacy.component
import android.content.Context import android.content.Context
import android.net.Uri
import android.text.SpannableStringBuilder
import android.text.Spanned
import android.text.method.LinkMovementMethod
import android.text.style.ClickableSpan
import android.util.AttributeSet import android.util.AttributeSet
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.widget.FrameLayout import android.widget.FrameLayout
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.browser.customtabs.CustomTabsIntent import androidx.compose.foundation.layout.Column
import androidx.compose.material3.LocalContentColor
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.LinearLayoutManager
import com.afollestad.materialdialogs.MaterialDialog
import com.xwray.groupie.ExpandableGroup
import com.xwray.groupie.ExpandableItem
import com.xwray.groupie.GroupAdapter
import com.xwray.groupie.Section
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
import com.xwray.groupie.kotlinandroidextensions.Item
import de.mm20.launcher2.ktx.sp
import de.mm20.launcher2.search.data.CurrencyUnitConverter
import de.mm20.launcher2.search.data.UnitConverter import de.mm20.launcher2.search.data.UnitConverter
import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.LegacyLauncherTheme
import de.mm20.launcher2.ui.databinding.ViewUnitconverterBinding import de.mm20.launcher2.ui.databinding.ViewUnitconverterBinding
import de.mm20.launcher2.ui.search.UnitConverterItem
import de.mm20.launcher2.unitconverter.UnitConverterViewModel import de.mm20.launcher2.unitconverter.UnitConverterViewModel
import de.mm20.launcher2.unitconverter.UnitValue
import org.koin.androidx.viewmodel.ext.android.viewModel import org.koin.androidx.viewmodel.ext.android.viewModel
import java.text.DateFormat
import java.util.*
import kotlin.math.min
class UnitConverterView : FrameLayout { class UnitConverterView : FrameLayout {
constructor(context: Context) : super(context) constructor(context: Context) : super(context)
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context, attrs: AttributeSet?, defStyleRes: Int) : super(context, attrs, defStyleRes) constructor(context: Context, attrs: AttributeSet?, defStyleRes: Int) : super(
context,
attrs,
defStyleRes
)
private val unitConverter: LiveData<UnitConverter?> private val unitConverter: LiveData<UnitConverter?>
private val adapter: GroupAdapter<GroupieViewHolder>
private val binding = ViewUnitconverterBinding.inflate(LayoutInflater.from(context), this, true) private val binding = ViewUnitconverterBinding.inflate(LayoutInflater.from(context), this, true)
@ -54,141 +42,24 @@ class UnitConverterView : FrameLayout {
if (it == null) visibility = View.GONE if (it == null) visibility = View.GONE
else { else {
visibility = View.VISIBLE visibility = View.VISIBLE
bind(it)
} }
}) })
adapter = GroupAdapter()
binding.unitConverterValues.also {
it.adapter = adapter
it.addItemDecoration(DividerItemDecoration(context, LinearLayoutManager.VERTICAL))
}
}
binding.composeView.setContent {
private fun bind(converter: UnitConverter) { val converter by unitConverter.observeAsState()
val title = converter.inputValue.formattedValue + " " + converter.inputValue.formattedName LegacyLauncherTheme {
binding.unitConverterInput.text = title // TODO: Temporary solution until parent widget card is rewritten in Compose
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onSurface) {
/*val sb = StringBuilder() Column {
for (unit in converter.values) { converter?.let {
UnitConverterItem(
sb.append("${unit.formatted}\n") unitConverter = it,
} )
sb.removeSuffix("\n")
unitConverterValues.text = sb.toString()
unitConverterIcon.setImageResource(when (converter.dimension) {
Dimension.LENGTH -> R.drawable.ic_unit_length
Dimension.MASS -> R.drawable.ic_unit_mass
Dimension.TIME -> R.drawable.ic_unit_time
Dimension.DATA -> R.drawable.ic_unit_datasize
Dimension.VELOCITY -> R.drawable.ic_unit_velocity
else -> 0
})*/
adapter.clear()
val maxValueLength = converter.values.maxByOrNull { it.formattedValue.length }?.formattedValue?.length
?: 0
val section = Section().apply {
addAll(converter.values.subList(0, min(converter.values.size, 5)).map {
ValueItem(it, maxValueLength * 8f * sp)
})
}.also {
adapter.add(it)
}
if (converter.values.size > 5) {
binding.showAllButton.visibility = View.VISIBLE
binding.showAllButton.setOnClickListener {
section.addAll(converter.values.subList( 5, converter.values.size).map {
ValueItem(it, maxValueLength * 8f * sp)
})
binding.showAllButton.visibility = View.GONE
binding.showAllButton.setOnClickListener(null)
}
} else {
binding.showAllButton.visibility = View.GONE
}
if (converter is CurrencyUnitConverter) {
val df = DateFormat.getDateInstance(DateFormat.SHORT)
val date = Date().apply {
time = converter.updateTimestamp
}
val infoText = SpannableStringBuilder()
.append("European Central Bank (${df.format(date)})", object : ClickableSpan() {
override fun onClick(widget: View) {
CustomTabsIntent
.Builder()
.setToolbarColor(0xFF003299.toInt())
.build()
.launchUrl(context,
Uri.parse("https://www.ecb.europa.eu/stats/policy_and_exchange_rates/euro_reference_exchange_rates/html/index.en.html"))
} }
}, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) }
.append("") }
.append(context.getString(R.string.disclaimer), object : ClickableSpan() {
override fun onClick(widget: View) {
MaterialDialog(context).show {
title(res = R.string.disclaimer)
message(res = R.string.disclaimer_currency_converter)
positiveButton(res = R.string.close) {
dismiss()
}
}
}
}, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)
binding.unitConverterInfo.apply {
text = infoText
visibility = View.VISIBLE
movementMethod = LinkMovementMethod.getInstance()
}
} else {
binding.unitConverterInfo.visibility = View.GONE
}
}
}
class ValueItem(private val value: UnitValue, val valueWidth: Float) : Item() {
override fun getLayout(): Int {
return R.layout.unit_converter_row
}
override fun bind(viewHolder: GroupieViewHolder, position: Int) {
viewHolder.itemView.findViewById<TextView>(R.id.value).let {
it.text = value.formattedValue
it.layoutParams = it.layoutParams.apply {
width = valueWidth.toInt()
} }
} }
viewHolder.itemView.findViewById<TextView>(R.id.name).text = value.formattedName
viewHolder.itemView.findViewById<TextView>(R.id.symbol).text = value.symbol
}
}
class ExpandItem : Item(), ExpandableItem {
private lateinit var expandableGroup: ExpandableGroup
override fun bind(viewHolder: GroupieViewHolder, position: Int) {
viewHolder.itemView.visibility = if (expandableGroup.isExpanded) View.GONE else View.VISIBLE
viewHolder.itemView.setOnClickListener {
expandableGroup.onToggleExpanded()
viewHolder.itemView.visibility = View.GONE
}
}
override fun getLayout(): Int {
return R.layout.unit_converter_show_all
}
override fun setExpandableGroup(onToggleListener: ExpandableGroup) {
expandableGroup = onToggleListener
} }
} }

View File

@ -0,0 +1,121 @@
package de.mm20.launcher2.ui.search
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import de.mm20.launcher2.search.data.UnitConverter
import de.mm20.launcher2.ui.R
import de.mm20.launcher2.unitconverter.Dimension
@Composable
fun UnitConverterItem(
unitConverter: UnitConverter,
) {
var showAll by remember { mutableStateOf(false) }
Column(
modifier = Modifier.padding(bottom = 8.dp)
) {
Row(
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = unitConverter.inputValue.let { "${it.formattedValue} ${it.formattedName}" },
style = MaterialTheme.typography.headlineSmall,
modifier = Modifier
.weight(1f)
.padding(horizontal = 16.dp),
overflow = TextOverflow.Ellipsis,
softWrap = false
)
Surface(
modifier = Modifier
.padding(12.dp)
.size(48.dp),
color = MaterialTheme.colorScheme.primaryContainer,
shape = RoundedCornerShape(8.dp)
) {
Box(
contentAlignment = Alignment.Center
) {
Icon(
imageVector = getDimensionIcon(unitConverter.dimension),
contentDescription = null,
tint = MaterialTheme.colorScheme.primary,
modifier = Modifier.size(24.dp)
)
}
}
}
Row {
Column {
for ((i, unit) in unitConverter.values.withIndex()) {
if (!showAll && i >= 5) break
Text(
text = unit.formattedValue,
style = MaterialTheme.typography.bodyMedium.copy(
fontWeight = FontWeight.Bold
),
modifier = Modifier
.padding(start = 16.dp, bottom = 12.dp)
)
}
}
Column {
for ((i, unit) in unitConverter.values.withIndex()) {
if (!showAll && i >= 5) break
val text = AnnotatedString.Builder().apply {
append(" ${unit.formattedName} (${unit.symbol})")
}.toAnnotatedString()
Text(
text = "${unit.formattedName} (${unit.symbol})",
style = MaterialTheme.typography.bodyMedium,
modifier = Modifier
.padding(end = 16.dp, bottom = 12.dp, start = 8.dp)
)
}
}
}
if (!showAll && unitConverter.values.size > 5) {
TextButton(
onClick = { showAll = true },
modifier = Modifier
.align(Alignment.End)
.padding(horizontal = 12.dp)
) {
Text(text = stringResource(id = R.string.unit_converter_show_all))
}
}
}
}
fun getDimensionIcon(dimension: Dimension): ImageVector {
return when (dimension) {
Dimension.Mass -> Icons.Rounded.FitnessCenter
Dimension.Length -> Icons.Rounded.Straighten
Dimension.Velocity -> Icons.Rounded.Speed
Dimension.Volume -> TODO()
Dimension.Area -> Icons.Rounded.SquareFoot
Dimension.Currency -> Icons.Rounded.Toll
Dimension.Data -> Icons.Rounded.Storage
Dimension.Bitrate -> TODO()
Dimension.Pressure -> TODO()
Dimension.Energy -> Icons.Rounded.Bolt
Dimension.Frequency -> TODO()
Dimension.Temperature -> Icons.Rounded.Thermostat
Dimension.Time -> Icons.Rounded.Schedule
}
}

View File

@ -10,89 +10,9 @@
android:clipToPadding="false" android:clipToPadding="false"
android:elevation="@dimen/card_elevation"> android:elevation="@dimen/card_elevation">
<androidx.constraintlayout.widget.ConstraintLayout <androidx.compose.ui.platform.ComposeView
android:id="@+id/composeView"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"/>
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="4dp"
android:animateLayoutChanges="true"
android:orientation="vertical">
<TextView
android:id="@+id/unitConverterInput"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.AppCompat.Headline"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:text="13 kilometers" />
<TextView
android:id="@+id/unitConverterInfo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:paddingTop="4dp"
android:textColor="@color/text_color_secondary"
android:textSize="10sp"
android:textStyle="italic"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@+id/showAllButton"
tools:text="European Central Bank (24.04.2020)" />
<!--<TextView
android:id="@+id/unitConverterValues"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:layout_marginEnd="8dp"
app:layout_constraintEnd_toStartOf="@+id/unitConverterIcon"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/unitConverterInput"
tools:text="0.013 kilometers (km)\n130 centimeters (cm)\n1300 millimeters (mm)\n 13.1234 yards (yd)"/>
<ImageView
android:id="@+id/unitConverterIcon"
android:layout_width="48dp"
android:layout_height="48dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:srcCompat="@tools:sample/avatars"/>-->
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/unitConverterValues"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
android:nestedScrollingEnabled="false"
android:orientation="vertical"
android:overScrollMode="never"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/unitConverterInput" />
<com.google.android.material.button.MaterialButton
android:id="@+id/showAllButton"
android:visibility="gone"
style="@style/Widget.MaterialComponents.Button.TextButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/unit_converter_show_all"
android:paddingBottom="4dp"
android:textAppearance="@style/TextAppearance.MaterialComponents.Subtitle2"
android:textColor="?android:textColorPrimary"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/unitConverterValues" />
</androidx.constraintlayout.widget.ConstraintLayout>
</de.mm20.launcher2.ui.legacy.view.LauncherCardView> </de.mm20.launcher2.ui.legacy.view.LauncherCardView>