AsssistantScreen.kt 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. /*
  2. * Nextcloud Android client application
  3. *
  4. * @author Alper Ozturk
  5. * Copyright (C) 2024 Alper Ozturk
  6. * Copyright (C) 2024 Nextcloud GmbH
  7. *
  8. * This program is free software: you can redistribute it and/or modify
  9. * it under the terms of the GNU Affero General Public License as published by
  10. * the Free Software Foundation, either version 3 of the License, or
  11. * (at your option) any later version.
  12. *
  13. * This program is distributed in the hope that it will be useful,
  14. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. * GNU Affero General Public License for more details.
  17. *
  18. * You should have received a copy of the GNU Affero General Public License
  19. * along with this program. If not, see <https://www.gnu.org/licenses/>.
  20. */
  21. package com.nextcloud.client.assistant
  22. import android.app.Activity
  23. import androidx.compose.foundation.ExperimentalFoundationApi
  24. import androidx.compose.foundation.layout.Box
  25. import androidx.compose.foundation.layout.Column
  26. import androidx.compose.foundation.layout.Spacer
  27. import androidx.compose.foundation.layout.fillMaxSize
  28. import androidx.compose.foundation.layout.fillMaxWidth
  29. import androidx.compose.foundation.layout.height
  30. import androidx.compose.foundation.layout.padding
  31. import androidx.compose.foundation.lazy.LazyColumn
  32. import androidx.compose.foundation.lazy.items
  33. import androidx.compose.material.icons.Icons
  34. import androidx.compose.material.icons.filled.Add
  35. import androidx.compose.material3.ExperimentalMaterial3Api
  36. import androidx.compose.material3.FloatingActionButton
  37. import androidx.compose.material3.Icon
  38. import androidx.compose.material3.LinearProgressIndicator
  39. import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState
  40. import androidx.compose.runtime.Composable
  41. import androidx.compose.runtime.LaunchedEffect
  42. import androidx.compose.runtime.collectAsState
  43. import androidx.compose.runtime.getValue
  44. import androidx.compose.runtime.mutableStateOf
  45. import androidx.compose.runtime.remember
  46. import androidx.compose.runtime.setValue
  47. import androidx.compose.ui.Alignment
  48. import androidx.compose.ui.Modifier
  49. import androidx.compose.ui.graphics.Color
  50. import androidx.compose.ui.input.nestedscroll.nestedScroll
  51. import androidx.compose.ui.platform.LocalContext
  52. import androidx.compose.ui.res.stringResource
  53. import androidx.compose.ui.unit.dp
  54. import com.nextcloud.client.assistant.component.AddTaskAlertDialog
  55. import com.nextcloud.client.assistant.component.CenterText
  56. import com.nextcloud.client.assistant.component.TaskTypesRow
  57. import com.nextcloud.client.assistant.component.TaskView
  58. import com.nextcloud.ui.composeComponents.alertDialog.SimpleAlertDialog
  59. import com.owncloud.android.R
  60. import com.owncloud.android.lib.resources.assistant.model.Task
  61. import com.owncloud.android.lib.resources.assistant.model.TaskType
  62. import com.owncloud.android.utils.DisplayUtils
  63. import kotlinx.coroutines.delay
  64. @Suppress("LongMethod")
  65. @OptIn(ExperimentalMaterial3Api::class)
  66. @Composable
  67. fun AssistantScreen(viewModel: AssistantViewModel) {
  68. val activity = LocalContext.current as Activity
  69. val loading by viewModel.loading.collectAsState()
  70. val selectedTaskType by viewModel.selectedTaskType.collectAsState()
  71. val filteredTaskList by viewModel.filteredTaskList.collectAsState()
  72. val isTaskCreated by viewModel.isTaskCreated.collectAsState()
  73. val isTaskDeleted by viewModel.isTaskDeleted.collectAsState()
  74. val taskTypes by viewModel.taskTypes.collectAsState()
  75. var showAddTaskAlertDialog by remember { mutableStateOf(false) }
  76. var showDeleteTaskAlertDialog by remember { mutableStateOf(false) }
  77. var taskIdToDeleted: Long? by remember {
  78. mutableStateOf(null)
  79. }
  80. val pullRefreshState = rememberPullToRefreshState()
  81. @Suppress("MagicNumber")
  82. if (pullRefreshState.isRefreshing) {
  83. LaunchedEffect(true) {
  84. delay(1500)
  85. viewModel.getTaskList(onCompleted = {
  86. pullRefreshState.endRefresh()
  87. })
  88. }
  89. }
  90. Box(Modifier.nestedScroll(pullRefreshState.nestedScrollConnection)) {
  91. if (loading || pullRefreshState.isRefreshing) {
  92. CenterText(text = stringResource(id = R.string.assistant_screen_loading))
  93. } else {
  94. if (filteredTaskList.isNullOrEmpty()) {
  95. EmptyTaskList(selectedTaskType, taskTypes, viewModel)
  96. } else {
  97. AssistantContent(
  98. filteredTaskList!!,
  99. taskTypes,
  100. selectedTaskType,
  101. viewModel,
  102. showDeleteTaskAlertDialog = { taskId ->
  103. taskIdToDeleted = taskId
  104. showDeleteTaskAlertDialog = true
  105. }
  106. )
  107. }
  108. }
  109. if (pullRefreshState.isRefreshing) {
  110. LinearProgressIndicator(modifier = Modifier.fillMaxWidth())
  111. } else {
  112. LinearProgressIndicator(progress = { pullRefreshState.progress }, modifier = Modifier.fillMaxWidth())
  113. }
  114. FloatingActionButton(
  115. modifier = Modifier
  116. .align(Alignment.BottomEnd)
  117. .padding(16.dp),
  118. onClick = {
  119. if (selectedTaskType?.id != null) {
  120. showAddTaskAlertDialog = true
  121. } else {
  122. DisplayUtils.showSnackMessage(
  123. activity,
  124. activity.getString(R.string.assistant_screen_select_different_task_type_to_add)
  125. )
  126. }
  127. }
  128. ) {
  129. Icon(Icons.Filled.Add, "Add Task Icon")
  130. }
  131. }
  132. checkTaskAdd(isTaskCreated, activity, viewModel)
  133. checkTaskDeletion(isTaskDeleted, activity, viewModel)
  134. if (showDeleteTaskAlertDialog) {
  135. taskIdToDeleted?.let { id ->
  136. SimpleAlertDialog(
  137. backgroundColor = Color.White,
  138. textColor = Color.Black,
  139. title = stringResource(id = R.string.assistant_screen_delete_task_alert_dialog_title),
  140. description = stringResource(id = R.string.assistant_screen_delete_task_alert_dialog_description),
  141. dismiss = { showDeleteTaskAlertDialog = false },
  142. onComplete = { viewModel.deleteTask(id) }
  143. )
  144. }
  145. }
  146. if (showAddTaskAlertDialog) {
  147. selectedTaskType?.let {
  148. AddTaskAlertDialog(viewModel, it) {
  149. showAddTaskAlertDialog = false
  150. }
  151. }
  152. }
  153. }
  154. @Composable
  155. private fun checkTaskAdd(isTaskCreated: Boolean?, activity: Activity, viewModel: AssistantViewModel) {
  156. isTaskCreated?.let {
  157. val messageId = if (it) {
  158. R.string.assistant_screen_task_create_success_message
  159. } else {
  160. R.string.assistant_screen_task_create_fail_message
  161. }
  162. DisplayUtils.showSnackMessage(
  163. activity,
  164. stringResource(id = messageId)
  165. )
  166. viewModel.resetTaskAddState()
  167. }
  168. }
  169. @Composable
  170. private fun checkTaskDeletion(isTaskDeleted: Boolean?, activity: Activity, viewModel: AssistantViewModel) {
  171. isTaskDeleted?.let {
  172. val messageId = if (it) {
  173. R.string.assistant_screen_task_delete_success_message
  174. } else {
  175. R.string.assistant_screen_task_delete_fail_message
  176. }
  177. DisplayUtils.showSnackMessage(
  178. activity,
  179. stringResource(id = messageId)
  180. )
  181. viewModel.resetTaskDeletionState()
  182. }
  183. }
  184. @OptIn(ExperimentalFoundationApi::class)
  185. @Composable
  186. private fun AssistantContent(
  187. taskList: List<Task>,
  188. taskTypes: List<TaskType>?,
  189. selectedTaskType: TaskType?,
  190. viewModel: AssistantViewModel,
  191. showDeleteTaskAlertDialog: (Long) -> Unit
  192. ) {
  193. LazyColumn(
  194. modifier = Modifier
  195. .fillMaxSize()
  196. .padding(16.dp)
  197. ) {
  198. stickyHeader {
  199. TaskTypesRow(selectedTaskType, data = taskTypes) { task ->
  200. viewModel.selectTaskType(task)
  201. }
  202. Spacer(modifier = Modifier.height(8.dp))
  203. }
  204. items(taskList) { task ->
  205. TaskView(task, showDeleteTaskAlertDialog = { showDeleteTaskAlertDialog(task.id) })
  206. Spacer(modifier = Modifier.height(8.dp))
  207. }
  208. }
  209. }
  210. @Composable
  211. private fun EmptyTaskList(selectedTaskType: TaskType?, taskTypes: List<TaskType>?, viewModel: AssistantViewModel) {
  212. Column(
  213. modifier = Modifier
  214. .fillMaxSize()
  215. .padding(16.dp)
  216. ) {
  217. TaskTypesRow(selectedTaskType, data = taskTypes) { task ->
  218. viewModel.selectTaskType(task)
  219. }
  220. Spacer(modifier = Modifier.height(8.dp))
  221. CenterText(
  222. text = stringResource(
  223. id = R.string.assistant_screen_no_task_available_text,
  224. selectedTaskType?.name ?: ""
  225. )
  226. )
  227. }
  228. }