build basic task summary view

This commit is contained in:
Phillip Thelen 2022-11-17 17:33:15 +01:00
parent 3bd68fedf0
commit 2ea1096622
11 changed files with 228 additions and 13 deletions

View file

@ -148,6 +148,16 @@
android:name="android.support.PARENT_ACTIVITY"
android:value=".ui.activities.MainActivity" />
</activity>
<activity
android:name=".ui.activities.TaskSummaryActivity"
android:parentActivityName=".ui.activities.MainActivity"
tools:ignore="UnusedAttribute"
android:configChanges="screenSize | smallestScreenSize | screenLayout | orientation"
android:windowSoftInputMode="stateVisible|adjustResize">
<meta-data
android:name="android.support.PARENT_ACTIVITY"
android:value=".ui.activities.MainActivity" />
</activity>
<activity
android:name=".ui.activities.GroupFormActivity"
android:parentActivityName=".ui.activities.MainActivity"

Binary file not shown.

After

Width:  |  Height:  |  Size: 396 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 509 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 661 B

View file

@ -53,6 +53,7 @@
<string name="authentication_error_body">Your Username and/or Password was incorrect.</string>
<string name="save_changes">Save</string>
<string name="copy">Copy</string>
<string name="title">Title</string>
<string name="notes">Notes</string>
<string name="difficulty">Difficulty</string>
<string name="tags">Tags</string>
@ -1256,6 +1257,13 @@
<string name="copy_shared_tasks">Copy shared tasks</string>
<string name="group_plan_settings">Group Plan Settings</string>
<string name="task_summary">Task Summary</string>
<string name="habit_summary_description">This is a **%s** Habit that is **%s**.</string>
<string name="todo_summary_description_duedate">This is a **%s** Task that is due **%s**.</string>
<string name="todo_summary_description">This is a **%s** Task that does not have a due date.</string>
<string name="daily_summary_description">This is a **%s** Task that repeats **%s**.</string>
<string name="positive_and_negative">positive and negative</string>
<string name="assigned_to">Assigned to</string>
<string name="completed_at">Completed at %s</string>
<plurals name="you_x_others">
<item quantity="zero">You</item>
<item quantity="one">You, %d other</item>

View file

@ -70,6 +70,6 @@ object MainNavigationController {
}
fun navigateBack() {
navController?.navigateUp()
navController?.popBackStack()
}
}

View file

@ -0,0 +1,52 @@
package com.habitrpg.android.habitica.helpers
import android.content.Context
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.models.tasks.Task
import com.habitrpg.shared.habitica.models.tasks.TaskType
import java.text.DateFormat
import java.util.Date
class TaskDescriptionBuilder(private val context: Context) {
fun describe(task: Task): String {
return when (task.type) {
TaskType.HABIT -> context.getString(R.string.habit_summary_description, describeHabitDirections(task.up ?: false, task.down ?: false), describeDifficulty(task.priority))
TaskType.TODO -> {
if (task.dueDate != null) {
context.getString(R.string.todo_summary_description_duedate, describeDifficulty(task.priority), describeDate(task.dueDate!!))
} else {
context.getString(R.string.todo_summary_description, describeDifficulty(task.priority))
}
}
TaskType.DAILY -> context.getString(R.string.daily_summary_description, describeDifficulty(task.priority), "sometimes")
else -> ""
}
}
private val dateFormatter = DateFormat.getDateInstance()
private fun describeDate(date: Date): String {
return dateFormatter.format(date)
}
private fun describeHabitDirections(up: Boolean, down: Boolean): String {
return if (up && down) {
context.getString(R.string.positive_and_negative)
} else if (up) {
context.getString(R.string.positive_habit_form)
} else {
context.getString(R.string.negative_habit_form)
}
}
private fun describeDifficulty(difficulty: Float): String {
return when (difficulty) {
0.1f -> context.getString(R.string.trivial)
1.0f -> context.getString(R.string.easy)
1.5f -> context.getString(R.string.medium)
2.0f -> context.getString(R.string.hard)
else -> ""
}
}
}

View file

@ -501,10 +501,6 @@ open class Task : RealmObject, BaseMainObject, Parcelable, BaseTask {
return daysOfMonth
}
fun canEdit(userID: String): Boolean {
return true
}
companion object CREATOR : Parcelable.Creator<Task> {
override fun createFromParcel(source: Parcel): Task = Task(source)

View file

@ -9,11 +9,12 @@ import java.util.Date
@RealmClass(embedded = true)
open class GroupAssignedDetails: RealmObject(), BaseObject {
var assignedDate: String? = null
var assignedDate: Date? = null
var assignedUsername: String? = null
var assignedUserID: String? = null
var assigningUsername: String? = null
var completed: Boolean = false
var completedDate: Date? = null
}
@RealmClass(embedded = true)

View file

@ -1,21 +1,49 @@
package com.habitrpg.android.habitica.ui.activities
import android.os.Bundle
import android.view.Window
import android.view.WindowManager
import androidx.activity.compose.setContent
import androidx.activity.viewModels
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Button
import androidx.compose.material.ButtonDefaults
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
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.graphics.ColorFilter
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.colorResource
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import androidx.core.content.ContextCompat
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.asLiveData
import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.components.UserComponent
import com.habitrpg.android.habitica.data.TaskRepository
import com.habitrpg.android.habitica.helpers.MainNavigationController
import com.habitrpg.android.habitica.helpers.TaskDescriptionBuilder
import com.habitrpg.android.habitica.ui.viewmodels.BaseViewModel
import com.habitrpg.common.habitica.helpers.MarkdownParser
import com.habitrpg.shared.habitica.models.tasks.TaskType
import java.text.DateFormat
import javax.inject.Inject
class TaskSummaryViewModel(val taskId: String) : BaseViewModel() {
@ -26,18 +54,36 @@ class TaskSummaryViewModel(val taskId: String) : BaseViewModel() {
override fun inject(component: UserComponent) {
component.inject(this)
}
@Suppress("UNCHECKED_CAST")
class Factory(private val taskID: String): ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return TaskSummaryViewModel(taskID) as T
}
}
}
class TaskSummaryActivity: BaseActivity() {
override fun getLayoutResId(): Int? = null
private val viewModel: TaskSummaryViewModel by viewModels { TaskSummaryViewModel.Factory(intent.extras?.getString(
TaskFormActivity.TASK_ID_KEY
) ?: "") }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
TaskSummaryView(viewModel = viewModel)
}
}
override fun onStart() {
super.onStart()
val window: Window = window
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)
window.statusBarColor = ContextCompat.getColor(this, viewModel.task.value?.lightTaskColor ?: R.color.brand_300)
}
override fun injectActivity(component: UserComponent?) {
component?.inject(this)
}
@ -45,9 +91,113 @@ class TaskSummaryActivity: BaseActivity() {
@Composable
fun TaskSummaryView(viewModel: TaskSummaryViewModel) {
val taskDescriptionBuilder = TaskDescriptionBuilder(LocalContext.current)
val task by viewModel.task.observeAsState()
Column {
Text(stringResource(R.string.task_summary), Modifier)
val titleModifier = Modifier.padding(top = 30.dp)
val textModifier = Modifier.padding(top = 4.dp)
val completedTimeFormat = DateFormat.getTimeInstance()
val darkestColor = colorResource(task?.darkestTaskColor ?: R.color.text_primary)
Column(Modifier.background(colorResource(task?.lightTaskColor ?: R.color.brand_300))) {
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(0.dp, 4.dp)) {
Button(onClick = {
MainNavigationController.navigateBack()
}, colors = ButtonDefaults.textButtonColors(contentColor = darkestColor), elevation = ButtonDefaults.elevation(0.dp)) {
Image(painterResource(R.drawable.ic_arrow_back_white_36dp), stringResource(R.string.action_back), colorFilter = ColorFilter.tint(colorResource(task?.darkestTaskColor ?: R.color.white)))
}
Text(
stringResource(R.string.task_summary),
fontSize = 18.sp,
fontWeight = FontWeight.Medium,
color = darkestColor,
modifier = Modifier.padding(start = 6.dp)
)
}
Column(
Modifier
.background(
MaterialTheme.colors.background,
RoundedCornerShape(topStart = 12.dp, topEnd = 12.dp)
)
.padding(20.dp, 5.dp)
.fillMaxWidth()) {
Text(stringResource(R.string.title), fontSize = 16.sp, color = darkestColor, fontWeight = FontWeight.Medium, modifier = titleModifier)
Text(task?.text ?: "", fontSize = 16.sp, color = MaterialTheme.colors.onBackground, modifier = textModifier)
if (task?.notes?.isNotBlank() == true) {
Text(
stringResource(R.string.notes),
fontSize = 16.sp,
color = MaterialTheme.colors.onSecondary,
fontWeight = FontWeight.Medium,
modifier = titleModifier
)
Text(
task?.notes ?: "",
fontSize = 16.sp,
color = MaterialTheme.colors.onBackground,
modifier = textModifier
)
}
if (task?.type != TaskType.REWARD) {
Text(
stringResource(R.string.description),
fontSize = 16.sp,
color = darkestColor,
fontWeight = FontWeight.Medium,
modifier = titleModifier
)
Text(MarkdownParser.parseMarkdown(task?.let { taskDescriptionBuilder.describe(it) } ?: "").toString(),
fontSize = 16.sp,
color = MaterialTheme.colors.onBackground,
modifier = textModifier)
}
if (task?.checklist?.isNotEmpty() == true) {
task?.checklist?.let { checklist ->
Text(stringResource(R.string.checklist), fontSize = 16.sp, color = darkestColor, fontWeight = FontWeight.Medium, modifier = titleModifier)
for (item in checklist) {
Text(item.text ?: "", fontSize = 16.sp, fontWeight = FontWeight.Medium, modifier = Modifier
.background(colorResource(R.color.gray_700))
.padding(15.dp)
.fillMaxWidth())
}
}
}
if (task?.group?.assignedUsersDetail?.isNotEmpty() == true) {
Text(stringResource(R.string.assigned_to), fontSize = 16.sp, color = darkestColor, fontWeight = FontWeight.Medium, modifier = titleModifier.padding(bottom = 4.dp))
for (item in task?.group?.assignedUsersDetail ?: emptyList()) {
UserRow(item.assignedUsername ?: "", Modifier
.padding(vertical = 4.dp)
.background(colorResource(R.color.gray_700), RoundedCornerShape(8.dp))
.padding(15.dp, 12.dp)
.heightIn(min = 24.dp)
.fillMaxWidth(),
extraContent = if (item.completed) ({
Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(top = 4.dp)) {
Image(painterResource(R.drawable.completed), null)
Text(stringResource(R.string.completed_at,
item.completedDate?.let { completedTimeFormat.format(it) } ?: ""),
fontSize = 14.sp,
color = colorResource(R.color.green_10), modifier = Modifier.padding(start = 4.dp))
}
}) else null)
}
}
}
}
}
@Composable
fun UserRow(username: String, modifier: Modifier = Modifier, extraContent: @Composable (() -> Unit)? = null) {
Row(verticalAlignment = Alignment.CenterVertically, modifier = modifier) {
Column {
Text(
"@$username", fontSize = 16.sp, fontWeight = FontWeight.Medium, modifier = Modifier
.background(colorResource(R.color.gray_700))
.fillMaxWidth()
)
if (extraContent != null) {
extraContent()
}
}
}
}

View file

@ -66,7 +66,6 @@ open class TaskRecyclerViewFragment : BaseFragment<FragmentRefreshRecyclerviewBi
private var taskFlowJob: Job? = null
val viewModel: TasksViewModel by viewModels({ requireParentFragment() })
internal var canEditTasks: Boolean = true
internal var canScoreTaks: Boolean = true
override var binding: FragmentRefreshRecyclerviewBinding? = null
@ -159,7 +158,6 @@ open class TaskRecyclerViewFragment : BaseFragment<FragmentRefreshRecyclerviewBi
}
viewModel.ownerID.observe(viewLifecycleOwner) {
canEditTasks = viewModel.isPersonalBoard
canScoreTaks = viewModel.isPersonalBoard
updateTaskSubscription(it)
}
@ -555,7 +553,7 @@ private fun setPreferenceTaskFilters() {
private fun openTaskForm(task: Task) {
if (Date().time - (TasksFragment.lastTaskFormOpen?.time
?: 0) < 2000 || !task.isValid || !canEditTasks
?: 0) < 2000 || !task.isValid
) {
return
}
@ -565,7 +563,7 @@ private fun openTaskForm(task: Task) {
bundle.putString(TaskFormActivity.TASK_ID_KEY, task.id)
bundle.putDouble(TaskFormActivity.TASK_VALUE_KEY, task.value)
if (task.canEdit(viewModel.userViewModel.userID)) {
if (viewModel.canEditTask(task)) {
MainNavigationController.navigate(R.id.taskFormActivity, bundle)
} else {
MainNavigationController.navigate(R.id.taskSummaryActivity, bundle)