Rewrite CreateAddressBookActivity to Compose (#559)

* Added initial layout

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Improved UI

Signed-off-by: Arnau Mora <arnyminerz@proton.me>

* Replaced autofill with radio buttons

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Got rid of unnecessary errors

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Got rid of null indicators

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Added default home set selection

Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>

* Minor UI changes

* Drop displayNameError

---------

Signed-off-by: Arnau Mora <arnyminerz@proton.me>
Signed-off-by: Arnau Mora Gras <arnyminerz@proton.me>
Co-authored-by: Ricki Hirner <hirner@bitfire.at>
This commit is contained in:
Arnau Mora 2024-02-12 13:28:11 +01:00 committed by GitHub
parent 3dd63df5c8
commit a7f6192177
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 186 additions and 126 deletions

View file

@ -151,7 +151,7 @@
</activity>
<activity
android:name=".ui.account.CreateAddressBookActivity"
android:label="@string/create_addressbook"
android:theme="@style/AppTheme.NoActionBar"
android:parentActivityName=".ui.account.AccountActivity" />
<activity
android:name=".ui.account.CreateCalendarActivity"

View file

@ -7,30 +7,53 @@ package at.bitfire.davdroid.ui.account
import android.accounts.Account
import android.content.Intent
import android.os.Bundle
import android.text.Editable
import android.view.Menu
import android.view.MenuItem
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.Icon
import androidx.compose.material.IconButton
import androidx.compose.material.MaterialTheme
import androidx.compose.material.OutlinedTextField
import androidx.compose.material.RadioButton
import androidx.compose.material.Scaffold
import androidx.compose.material.Text
import androidx.compose.material.TopAppBar
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowBack
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.core.app.TaskStackBuilder
import androidx.databinding.DataBindingUtil
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
import at.bitfire.davdroid.R
import at.bitfire.davdroid.databinding.ActivityCreateAddressBookBinding
import at.bitfire.davdroid.db.AppDatabase
import at.bitfire.davdroid.db.Collection
import at.bitfire.davdroid.db.HomeSet
import at.bitfire.davdroid.db.Service
import at.bitfire.davdroid.ui.HomeSetAdapter
import com.google.accompanist.themeadapter.material.MdcTheme
import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import okhttp3.HttpUrl.Companion.toHttpUrl
import org.apache.commons.lang3.StringUtils
import java.util.UUID
import javax.inject.Inject
@ -53,36 +76,32 @@ class CreateAddressBookActivity: AppCompatActivity() {
}
}
lateinit var binding: ActivityCreateAddressBookBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
binding = DataBindingUtil.setContentView(this, R.layout.activity_create_address_book)
binding.lifecycleOwner = this
binding.model = model
setContent {
MdcTheme {
val displayName by model.displayName.observeAsState()
val description by model.description.observeAsState()
val homeSet by model.homeSet.observeAsState()
val homeSets by model.homeSets.observeAsState()
val homeSetAdapter = HomeSetAdapter(this)
model.homeSets.observe(this) { homeSets ->
homeSetAdapter.clear()
if (homeSets.isNotEmpty()) {
homeSetAdapter.addAll(homeSets)
val firstHomeSet = homeSets.first()
binding.homeset.setText(firstHomeSet.url.toString(), false)
model.homeSet = firstHomeSet
Content(
isCreateEnabled =
displayName != null &&
homeSet != null,
displayName = displayName,
onDisplayNameChange = model.displayName::setValue,
description = description,
onDescriptionChange = model.description::setValue,
homeSet = homeSet,
homeSets = homeSets,
onHomeSetClicked = model.homeSet::setValue
)
}
}
binding.homeset.setAdapter(homeSetAdapter)
binding.homeset.setOnItemClickListener { parent, view, position, id ->
model.homeSet = parent.getItemAtPosition(position) as HomeSet?
}
}
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.activity_create_collection, menu)
return true
}
override fun supportShouldUpRecreateTask(targetIntent: Intent) = true
@ -92,29 +111,150 @@ class CreateAddressBookActivity: AppCompatActivity() {
}
fun onCreateCollection(item: MenuItem) {
@Composable
private fun Content(
isCreateEnabled: Boolean = false,
displayName: String? = null,
onDisplayNameChange: (String) -> Unit = {},
description: String? = null,
onDescriptionChange: (String) -> Unit = {},
homeSet: HomeSet? = null,
homeSets: List<HomeSet>? = null,
onHomeSetClicked: (HomeSet) -> Unit = {}
) {
Scaffold(
topBar = { TopBar(isCreateEnabled) }
) { paddingValues ->
CreateAddressBookForm(
paddingValues,
displayName,
onDisplayNameChange,
description,
onDescriptionChange,
homeSet,
homeSets,
onHomeSetClicked
)
}
}
@Composable
@Preview(showBackground = true, showSystemUi = true)
private fun Content_Preview() {
Content(
displayName = "Display Name",
description = "Description",
homeSets = listOf(
HomeSet(1, 0, false, "http://example.com/".toHttpUrl()),
HomeSet(2, 0, false, "http://example.com/".toHttpUrl(), displayName = "Home Set 2"),
)
)
}
@Composable
private fun CreateAddressBookForm(
paddingValues: PaddingValues,
displayName: String?,
onDisplayNameChange: (String) -> Unit,
description: String?,
onDescriptionChange: (String) -> Unit,
homeSet: HomeSet?,
homeSets: List<HomeSet>?,
onHomeSetClicked: (HomeSet) -> Unit
) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(paddingValues)
.verticalScroll(rememberScrollState())
.padding(8.dp)
) {
OutlinedTextField(
value = displayName ?: "",
onValueChange = onDisplayNameChange,
label = { Text(stringResource(R.string.create_collection_display_name)) },
modifier = Modifier.fillMaxWidth()
)
OutlinedTextField(
value = description ?: "",
onValueChange = onDescriptionChange,
label = { Text(stringResource(R.string.create_collection_description_optional)) },
modifier = Modifier
.fillMaxWidth()
.padding(top = 8.dp)
)
Text(
text = stringResource(R.string.create_collection_home_set),
style = MaterialTheme.typography.body1,
modifier = Modifier
.fillMaxWidth()
.padding(top = 16.dp)
)
if (homeSets != null) {
for (item in homeSets) {
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically
) {
RadioButton(
selected = homeSet == item,
onClick = { onHomeSetClicked(item) }
)
Text(
text = item.displayName ?: item.url.encodedPath,
style = MaterialTheme.typography.body2,
modifier = Modifier.weight(1f)
)
}
}
}
}
}
@Composable
private fun TopBar(isCreateEnabled: Boolean) {
TopAppBar(
title = { Text(stringResource(R.string.create_addressbook)) },
navigationIcon = {
IconButton(onClick = ::finish) {
Icon(Icons.AutoMirrored.Filled.ArrowBack, null)
}
},
actions = {
IconButton(
enabled = isCreateEnabled,
onClick = ::onCreateCollection
) {
Text(stringResource(R.string.create_collection_create).uppercase())
}
}
)
}
private fun onCreateCollection() {
var ok = true
val args = Bundle()
args.putString(CreateCollectionFragment.ARG_SERVICE_TYPE, Service.TYPE_CARDDAV)
val parent = model.homeSet
val parent = model.homeSet.value
if (parent != null) {
binding.homesetLayout.error = null
args.putString(CreateCollectionFragment.ARG_URL, parent.url.resolve(UUID.randomUUID().toString() + "/").toString())
args.putString(
CreateCollectionFragment.ARG_URL,
parent.url.resolve(UUID.randomUUID().toString() + "/").toString()
)
} else {
binding.homesetLayout.error = getString(R.string.create_collection_home_set_required)
ok = false
}
val displayName = model.displayName.value
if (displayName.isNullOrBlank()) {
model.displayNameError.value = getString(R.string.create_collection_display_name_required)
if (displayName.isNullOrBlank())
ok = false
} else {
else
args.putString(CreateCollectionFragment.ARG_DISPLAY_NAME, displayName)
model.displayNameError.value = null
}
StringUtils.trimToNull(model.description.value)?.let {
args.putString(CreateCollectionFragment.ARG_DESCRIPTION, it)
@ -141,26 +281,22 @@ class CreateAddressBookActivity: AppCompatActivity() {
}
val displayName = MutableLiveData<String>()
val displayNameError = MutableLiveData<String>()
val description = MutableLiveData<String>()
val homeSets = MutableLiveData<List<HomeSet>>()
var homeSet: HomeSet? = null
var homeSet = MutableLiveData<HomeSet>()
init {
viewModelScope.launch(Dispatchers.IO) {
// load account info
db.serviceDao().getByAccountAndType(account.name, Service.TYPE_CARDDAV)?.let { service ->
homeSets.postValue(db.homeSetDao().getBindableByService(service.id))
val homesets = db.homeSetDao().getBindableByService(service.id)
homeSets.postValue(homesets)
if (homeSet.value == null)
homeSet.postValue(homesets.firstOrNull())
}
}
}
fun clearNameError(s: Editable) {
displayNameError.value = null
}
}
}

View file

@ -1,77 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
tools:context=".ui.account.CreateAddressBookActivity">
<data>
<import type="android.view.View"/>
<variable
name="model"
type="at.bitfire.davdroid.ui.account.CreateAddressBookActivity.Model"/>
</data>
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="@dimen/activity_margin">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/display_name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:hint="@string/create_collection_display_name"
app:error="@{model.displayNameError}"
app:layout_constraintHorizontal_weight="1"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent">
<!--suppress AndroidUnknownAttribute -->
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:afterTextChanged="@{model::clearNameError}"
android:text="@={model.displayName}" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/description"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
android:hint="@string/create_collection_description"
app:helperText="@string/create_collection_optional"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/display_name">
<com.google.android.material.textfield.TextInputEditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@={model.description}" />
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
android:id="@+id/homeset_layout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="8dp"
app:layout_constraintTop_toBottomOf="@+id/description"
app:layout_constraintStart_toStartOf="parent"
style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox.ExposedDropdownMenu"
android:hint="@string/create_collection_home_set">
<AutoCompleteTextView
android:id="@+id/homeset"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:inputType="none" />
</com.google.android.material.textfield.TextInputLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView>
</layout>

View file

@ -428,6 +428,7 @@
<string name="create_collection_display_name">Title</string>
<string name="create_collection_display_name_required">Title is required</string>
<string name="create_collection_description">Description</string>
<string name="create_collection_description_optional">Description (optional)</string>
<string name="create_collection_optional">optional</string>
<string name="create_collection_home_set">Storage location</string>
<string name="create_collection_home_set_required">Storage location is required</string>