You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
tasks/app/src/main/java/org/tasks/tags/TagPickerActivityCompose.kt

293 lines
10 KiB
Kotlin

package org.tasks.tags
/*
* TagPickerActivityCompose is a replacement for TagPickerActivity reimplemented
* using JetPack Compose framework.
*
* The modification eliminates TagRecycleAdapter.
* TriStateCheckbox from Compose is used instead of CheckBoxTriState.
* Source code for TagRecycleAdapter and TagPickerActivity were deleted because they became incompatible
* with modified TagPickerViewModel, but were not excluded from build due to the hilt logic.
*
* There is a tag "FeatureReady" in the git commits log which marks the state when the main logic was
* already implemented via Compose but but viewModel was modified with backward compatibility so to
* switch back to View implementation its enough to find lines like this
* //val intent = Intent(context, TagPickerActivity::class.java)
* val intent = Intent(context, TagPickerActivityCompose::class.java)
* in TaskListFragment.kt and TagsControlSet.kt and move comment mark to another line.
*/
import android.app.Activity
import android.content.Intent
import android.os.Bundle
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material.Icon
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.material.TextField
import androidx.compose.material.TextFieldDefaults
import androidx.compose.material.TriStateCheckbox
import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.state.ToggleableState
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewModelScope
import com.google.android.material.composethemeadapter.MdcTheme
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.launch
import org.tasks.R
import org.tasks.Strings
import org.tasks.billing.Inventory
import org.tasks.data.TagData
import org.tasks.extensions.addBackPressedCallback
import org.tasks.injection.ThemedInjectingAppCompatActivity
import org.tasks.themes.ColorProvider
import org.tasks.themes.CustomIcons
import org.tasks.themes.Theme
import javax.inject.Inject
@AndroidEntryPoint
class TagPickerActivityCompose : ThemedInjectingAppCompatActivity() {
@Inject lateinit var theme: Theme
@Inject lateinit var inventory: Inventory
@Inject lateinit var colorProvider: ColorProvider
private val viewModel: TagPickerViewModel by viewModels()
private var taskIds: ArrayList<Long>? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val intent = intent
taskIds = intent.getSerializableExtra(EXTRA_TASKS) as ArrayList<Long>?
if (savedInstanceState == null) {
val selected = intent.getParcelableArrayListExtra<TagData>(EXTRA_SELECTED)
if ( selected != null ) {
viewModel.setSelected(
selected, intent.getParcelableArrayListExtra<TagData>(EXTRA_PARTIALLY_SELECTED)
)
}
}
addBackPressedCallback { handleBackPressed() }
viewModel.search("")
setContent {
MdcTheme {
TagPicker(
viewModel,
onBackClicked = { handleBackPressed() },
getTagIcon = { tagData -> getIcon(tagData) },
getTagColor = { tagData -> getColor(tagData) }
)
} /* setContent */
}
} /* onCreate */
private fun handleBackPressed() {
if (Strings.isNullOrEmpty(viewModel.searchText.value)) {
val data = Intent()
data.putExtra(EXTRA_TASKS, taskIds)
data.putParcelableArrayListExtra(EXTRA_PARTIALLY_SELECTED, viewModel.getPartiallySelected())
data.putParcelableArrayListExtra(EXTRA_SELECTED, viewModel.getSelected())
setResult(Activity.RESULT_OK, data)
finish()
} else {
viewModel.search("")
}
} /* handleBackPressed */
private fun getColor(tagData: TagData): Color {
if (tagData.getColor() != 0) {
val themeColor = colorProvider.getThemeColor(tagData.getColor()!!, true)
if (inventory.purchasedThemes() || themeColor.isFree) {
return Color(themeColor.primaryColor)
}
}
return Color(getColor(R.color.icon_tint_with_alpha))
}
private fun getIcon(tagData: TagData): Int
{
val iconIndex = tagData.getIcon()
var iconResource = R.drawable.ic_outline_label_24px
if ( (iconIndex != null) && (iconIndex < 1000 || inventory.hasPro) ) {
iconResource = CustomIcons.getIconResId(iconIndex) ?: R.drawable.ic_outline_label_24px
}
return iconResource
}
/* Copy of the TagPickerActivity's companion object */
companion object {
const val EXTRA_SELECTED = "extra_tags"
const val EXTRA_PARTIALLY_SELECTED = "extra_partial"
const val EXTRA_TASKS = "extra_tasks"
}
}
@Composable
internal fun TagPicker(
viewModel: TagPickerViewModel,
onBackClicked: () -> Unit,
getTagIcon: (TagData) -> Int,
getTagColor: (TagData) -> Color
) {
Box ( modifier = Modifier.fillMaxSize() )
{
Column (modifier = Modifier.padding(horizontal = 12.dp)) {
Box( modifier = Modifier.fillMaxWidth() ) {
SearchBar(viewModel, onBackClicked)
}
Box (
modifier = Modifier.weight(1f)
) {
PickerBox(viewModel, viewModel.tagsList.observeAsState(initial = emptyList()), getTagIcon, getTagColor)
}
}
}
}
@Composable
internal fun SearchBar(
viewModel: TagPickerViewModel,
onBack: () -> Unit
) {
val searchPattern = remember { viewModel.searchText }
val invitation = LocalContext.current.getString(R.string.enter_tag_name)
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(
ImageVector.vectorResource(id = R.drawable.ic_outline_arrow_back_24px),
"Done",
modifier = Modifier
.padding(6.dp)
.clickable { onBack() }
)
TextField(
value = searchPattern.value,
onValueChange = { viewModel.search(it) },
placeholder = { Text(invitation) },
colors = TextFieldDefaults.textFieldColors(
textColor = MaterialTheme.colors.onBackground,
backgroundColor = Color.Transparent,
focusedIndicatorColor = Color.Transparent,
unfocusedIndicatorColor = Color.Transparent
),
modifier = Modifier.padding(start = 6.dp)
)
}
} /* SearchBar */
@Composable
internal fun PickerBox (
viewModel: TagPickerViewModel,
tags: State<List<TagData>>,
getTagIcon: (TagData) -> Int = { R.drawable.ic_outline_label_24px },
getTagColor: (TagData) -> Color = { Color.Gray }
) {
val onClick: (TagData) -> Unit = {
viewModel.viewModelScope.launch {
viewModel.toggle(it, viewModel.getState(it) != ToggleableState.On) }
}
val newItem: (String) -> Unit = {
viewModel.viewModelScope.launch { viewModel.createNew(it); viewModel.search("") }
}
LazyColumn {
if (viewModel.tagToCreate.value != "") {
item(key = -1) {
val text = LocalContext.current.getString(R.string.new_tag) + " \"${viewModel.tagToCreate.value}\""
TagRow(
icon = ImageVector.vectorResource(R.drawable.ic_outline_add_24px),
iconColor = Color(LocalContext.current.getColor(R.color.icon_tint_with_alpha)),
text = text,
onClick = { newItem(viewModel.searchText.value) }
)
}
}
items( tags.value, key = { tag -> tag.id!! } )
{
val checked = remember { mutableStateOf ( viewModel.getState(it) ) }
val clickChecked: () -> Unit = { onClick(it); checked.value = viewModel.getState(it) }
TagRow(
icon = ImageVector.vectorResource(getTagIcon(it)),
iconColor = getTagColor(it),
text = it.name!!,
onClick = clickChecked
) {
TriStateCheckbox(
modifier = Modifier.padding(6.dp),
state = checked.value,
onClick = clickChecked
)
}
}
}
} /* PickerBox */
@Composable
internal fun TagRow (
icon: ImageVector,
iconColor: Color,
text: String,
onClick: () -> Unit,
checkBox: @Composable RowScope.() -> Unit = {}
) {
Row(modifier = Modifier.fillMaxWidth().clickable{ onClick() },
verticalAlignment = Alignment.CenterVertically,
) {
Icon(
imageVector = icon,
contentDescription = "",
modifier = Modifier.padding(6.dp),
tint = iconColor
)
Text(
text,
modifier = Modifier.weight(1f).padding(horizontal = 24.dp)
)
checkBox()
}
} /* TagRow */
/*
internal fun genTestTags(): List<TagData>
{
var idcc: Long = 1
return "alpha beta gamma delta kappa theta alfa1 beta1 gamma1 delta1 kappa1 theta1"
.split(" ")
.map { name -> TagData(name).also{ it.id = idcc++ } }
}
@Composable
@Preview(showBackground = true, backgroundColor = 0xffffff)
internal fun PickerBoxPreview() {
val list = remember { mutableStateOf( genTestTags() ) }
PickerBox(list, getTagColor = { Color.Green })
}
*/