/* * Nextcloud Android client application * * @author Alper Ozturk * Copyright (C) 2024 Alper Ozturk * Copyright (C) 2024 Nextcloud GmbH * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see . */ package com.nextcloud.client.assistant import android.app.Activity import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Add import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.Icon import androidx.compose.material3.LinearProgressIndicator import androidx.compose.material3.pulltorefresh.rememberPullToRefreshState import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.nestedscroll.nestedScroll import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import com.nextcloud.client.assistant.component.AddTaskAlertDialog import com.nextcloud.client.assistant.component.CenterText import com.nextcloud.client.assistant.component.TaskTypesRow import com.nextcloud.client.assistant.component.TaskView import com.nextcloud.ui.composeComponents.alertDialog.SimpleAlertDialog import com.owncloud.android.R import com.owncloud.android.lib.resources.assistant.model.Task import com.owncloud.android.lib.resources.assistant.model.TaskType import com.owncloud.android.utils.DisplayUtils import kotlinx.coroutines.delay @Suppress("LongMethod") @OptIn(ExperimentalMaterial3Api::class) @Composable fun AssistantScreen(viewModel: AssistantViewModel) { val activity = LocalContext.current as Activity val loading by viewModel.loading.collectAsState() val selectedTaskType by viewModel.selectedTaskType.collectAsState() val filteredTaskList by viewModel.filteredTaskList.collectAsState() val isTaskCreated by viewModel.isTaskCreated.collectAsState() val isTaskDeleted by viewModel.isTaskDeleted.collectAsState() val taskTypes by viewModel.taskTypes.collectAsState() var showAddTaskAlertDialog by remember { mutableStateOf(false) } var showDeleteTaskAlertDialog by remember { mutableStateOf(false) } var taskIdToDeleted: Long? by remember { mutableStateOf(null) } val pullRefreshState = rememberPullToRefreshState() @Suppress("MagicNumber") if (pullRefreshState.isRefreshing) { LaunchedEffect(true) { delay(1500) viewModel.getTaskList(onCompleted = { pullRefreshState.endRefresh() }) } } Box(Modifier.nestedScroll(pullRefreshState.nestedScrollConnection)) { if (loading || pullRefreshState.isRefreshing) { CenterText(text = stringResource(id = R.string.assistant_screen_loading)) } else { if (filteredTaskList.isNullOrEmpty()) { EmptyTaskList(selectedTaskType, taskTypes, viewModel) } else { AssistantContent( filteredTaskList!!, taskTypes, selectedTaskType, viewModel, showDeleteTaskAlertDialog = { taskId -> taskIdToDeleted = taskId showDeleteTaskAlertDialog = true } ) } } if (pullRefreshState.isRefreshing) { LinearProgressIndicator(modifier = Modifier.fillMaxWidth()) } else { LinearProgressIndicator(progress = { pullRefreshState.progress }, modifier = Modifier.fillMaxWidth()) } FloatingActionButton( modifier = Modifier .align(Alignment.BottomEnd) .padding(16.dp), onClick = { if (selectedTaskType?.id != null) { showAddTaskAlertDialog = true } else { DisplayUtils.showSnackMessage( activity, activity.getString(R.string.assistant_screen_select_different_task_type_to_add) ) } } ) { Icon(Icons.Filled.Add, "Add Task Icon") } } checkTaskAdd(isTaskCreated, activity, viewModel) checkTaskDeletion(isTaskDeleted, activity, viewModel) if (showDeleteTaskAlertDialog) { taskIdToDeleted?.let { id -> SimpleAlertDialog( backgroundColor = Color.White, textColor = Color.Black, title = stringResource(id = R.string.assistant_screen_delete_task_alert_dialog_title), description = stringResource(id = R.string.assistant_screen_delete_task_alert_dialog_description), dismiss = { showDeleteTaskAlertDialog = false }, onComplete = { viewModel.deleteTask(id) } ) } } if (showAddTaskAlertDialog) { selectedTaskType?.let { AddTaskAlertDialog(viewModel, it) { showAddTaskAlertDialog = false } } } } @Composable private fun checkTaskAdd(isTaskCreated: Boolean?, activity: Activity, viewModel: AssistantViewModel) { isTaskCreated?.let { val messageId = if (it) { R.string.assistant_screen_task_create_success_message } else { R.string.assistant_screen_task_create_fail_message } DisplayUtils.showSnackMessage( activity, stringResource(id = messageId) ) viewModel.resetTaskAddState() } } @Composable private fun checkTaskDeletion(isTaskDeleted: Boolean?, activity: Activity, viewModel: AssistantViewModel) { isTaskDeleted?.let { val messageId = if (it) { R.string.assistant_screen_task_delete_success_message } else { R.string.assistant_screen_task_delete_fail_message } DisplayUtils.showSnackMessage( activity, stringResource(id = messageId) ) viewModel.resetTaskDeletionState() } } @OptIn(ExperimentalFoundationApi::class) @Composable private fun AssistantContent( taskList: List, taskTypes: List?, selectedTaskType: TaskType?, viewModel: AssistantViewModel, showDeleteTaskAlertDialog: (Long) -> Unit ) { LazyColumn( modifier = Modifier .fillMaxSize() .padding(16.dp) ) { stickyHeader { TaskTypesRow(selectedTaskType, data = taskTypes) { task -> viewModel.selectTaskType(task) } Spacer(modifier = Modifier.height(8.dp)) } items(taskList) { task -> TaskView(task, showDeleteTaskAlertDialog = { showDeleteTaskAlertDialog(task.id) }) Spacer(modifier = Modifier.height(8.dp)) } } } @Composable private fun EmptyTaskList(selectedTaskType: TaskType?, taskTypes: List?, viewModel: AssistantViewModel) { Column( modifier = Modifier .fillMaxSize() .padding(16.dp) ) { TaskTypesRow(selectedTaskType, data = taskTypes) { task -> viewModel.selectTaskType(task) } Spacer(modifier = Modifier.height(8.dp)) CenterText( text = stringResource( id = R.string.assistant_screen_no_task_available_text, selectedTaskType?.name ?: "" ) ) } }