From 90b1f8e177c9c5e2320bd205d1f3bc62a2af48fc Mon Sep 17 00:00:00 2001 From: CrozCamel Date: Sat, 5 Apr 2025 18:40:00 -0300 Subject: [PATCH 1/7] =?UTF-8?q?refatora=C3=A7=C3=A3o=20das=20task?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit modificação das respostas das atividades para a nova versão; --- .../routinely/data/auth/api/AuthApiImpl.kt | 17 +- .../routinely/data/auth/model/TaskRequest.kt | 13 +- .../routinely/data/task/api/TaskApiImpl.kt | 53 +- .../data/task/extensions/taskExtensions.kt | 139 +++-- .../routinely/routinely/home/HomeScreen.kt | 154 +++--- .../routinely/routinely/home/HomeViewModel.kt | 1 + .../routinely/navigation/SetupNavGraph.kt | 91 ++-- .../routinely/routinely/task/AddTaskScreen.kt | 153 ++++-- .../routinely/task/AddTaskViewModel.kt | 34 +- .../task/DropdownWeeklyFrequencyScreen.kt | 339 ++++++++++++ .../routinely/task/EditTaskScreen.kt | 101 ++-- .../routinely/task/EditTaskViewModel.kt | 33 +- .../routinely/ui/components/CardTask.kt | 189 +++---- .../ui/components/DescriptionTextField.kt | 9 + .../ui/components/DropdownActivityFilter.kt | 129 +++++ .../ui/components/DropdownRoutinely.kt | 29 +- ...ownTaksFilter.kt => DropdownTaskFilter.kt} | 46 +- .../routinely/routinely/ui/components/Task.kt | 73 ++- .../ui/components/TasksViewerRoutinely.kt | 502 ------------------ .../routinely/routinely/util/ActivityTag.kt | 22 +- .../routinely/routinely/util/TaskCategory.kt | 45 +- .../routinely/routinely/util/TaskCateogry.kt | 11 + .../routinely/routinely/util/TaskFields.kt | 17 +- .../com/routinely/routinely/util/TaskItem.kt | 26 +- .../routinely/util/TaskItemRemote.kt | 22 + .../routinely/routinely/util/TaskMapper.kt | 26 + .../com/routinely/routinely/util/TaskType.kt | 18 + .../baseline_keyboard_arrow_down_24.xml | 5 + .../baseline_keyboard_arrow_left_24.xml | 5 + .../baseline_keyboard_arrow_right_24.xml | 5 + .../baseline_keyboard_arrow_up_24.xml | 5 + app/src/main/res/values-pt-rBR/strings.xml | 7 +- app/src/main/res/values/strings.xml | 7 +- 33 files changed, 1254 insertions(+), 1072 deletions(-) create mode 100644 app/src/main/java/com/routinely/routinely/task/DropdownWeeklyFrequencyScreen.kt create mode 100644 app/src/main/java/com/routinely/routinely/ui/components/DropdownActivityFilter.kt rename app/src/main/java/com/routinely/routinely/ui/components/{DropdownTaksFilter.kt => DropdownTaskFilter.kt} (78%) delete mode 100644 app/src/main/java/com/routinely/routinely/ui/components/TasksViewerRoutinely.kt create mode 100644 app/src/main/java/com/routinely/routinely/util/TaskCateogry.kt create mode 100644 app/src/main/java/com/routinely/routinely/util/TaskItemRemote.kt create mode 100644 app/src/main/java/com/routinely/routinely/util/TaskMapper.kt create mode 100644 app/src/main/java/com/routinely/routinely/util/TaskType.kt create mode 100644 app/src/main/res/drawable/baseline_keyboard_arrow_down_24.xml create mode 100644 app/src/main/res/drawable/baseline_keyboard_arrow_left_24.xml create mode 100644 app/src/main/res/drawable/baseline_keyboard_arrow_right_24.xml create mode 100644 app/src/main/res/drawable/baseline_keyboard_arrow_up_24.xml diff --git a/app/src/main/java/com/routinely/routinely/data/auth/api/AuthApiImpl.kt b/app/src/main/java/com/routinely/routinely/data/auth/api/AuthApiImpl.kt index 4f22736..0974792 100644 --- a/app/src/main/java/com/routinely/routinely/data/auth/api/AuthApiImpl.kt +++ b/app/src/main/java/com/routinely/routinely/data/auth/api/AuthApiImpl.kt @@ -27,6 +27,7 @@ import io.ktor.client.statement.HttpResponse import io.ktor.http.ContentType import io.ktor.http.HttpStatusCode import io.ktor.http.contentType +import timber.log.Timber internal class AuthApiImpl( private val client: HttpClient @@ -51,9 +52,9 @@ internal class AuthApiImpl( contentType(ContentType.Application.Json) }.toSignInResult() } catch(e: ResponseException){ - handleSignInErrorResponse(e.response.status) + handleSignInError(e.response.status) } catch(e: Exception){ - handleSignInErrorResponse(HttpStatusCode(900, e.message ?: "Unknown Exception")) + handleSignInError(HttpStatusCode(900, e.message ?: "Unknown Exception")) } } @@ -104,27 +105,25 @@ internal class AuthApiImpl( } } - private fun handleSignInErrorResponse(httpStatusCode: HttpStatusCode): SignInResult { - println("Error SignIn: ${httpStatusCode.description}") - return SignInResult.DefaultError + private fun handleSignInError(httpStatusCode: HttpStatusCode): SignInResult { + return when (httpStatusCode) { + HttpStatusCode.Unauthorized -> SignInResult.DefaultError + else -> SignInResult.DefaultError + } } private fun handleCreateAccountErrorResponse(httpStatusCode: HttpStatusCode): CreateAccountResult { - println("Error SignIn: ${httpStatusCode.description}") return CreateAccountResult.Error(R.string.api_unexpected_error) } private fun handleForgotPasswordError(httpStatusCode: HttpStatusCode): ForgotPasswordResult { - println("Error: ${httpStatusCode.description}") return ForgotPasswordResult.DefaultError } private fun handleValidateCodeError(httpStatusCode: HttpStatusCode): ValidateCodeResult { - println("Error: ${httpStatusCode.description}") return ValidateCodeResult.DefaultError } private fun handleCreateNewPasswordError(httpStatusCode: HttpStatusCode): CreateNewPasswordResult { - println("Error: ${httpStatusCode.description}") return CreateNewPasswordResult.DefaultError } } diff --git a/app/src/main/java/com/routinely/routinely/data/auth/model/TaskRequest.kt b/app/src/main/java/com/routinely/routinely/data/auth/model/TaskRequest.kt index 8bc95ae..9a839b6 100644 --- a/app/src/main/java/com/routinely/routinely/data/auth/model/TaskRequest.kt +++ b/app/src/main/java/com/routinely/routinely/data/auth/model/TaskRequest.kt @@ -1,11 +1,14 @@ package com.routinely.routinely.data.auth.model +import kotlinx.serialization.Serializable + +@Serializable data class TaskRequest( val name: String, - val date: String, - val hour: String, val description: String, - val priority: String, - val tag: String, - val category: String + val date: String, + val category: String, + val finallyDate: String, + val weekDays: List, + val type: String, ) diff --git a/app/src/main/java/com/routinely/routinely/data/task/api/TaskApiImpl.kt b/app/src/main/java/com/routinely/routinely/data/task/api/TaskApiImpl.kt index 832fdbe..a20fedd 100644 --- a/app/src/main/java/com/routinely/routinely/data/task/api/TaskApiImpl.kt +++ b/app/src/main/java/com/routinely/routinely/data/task/api/TaskApiImpl.kt @@ -32,25 +32,26 @@ internal class TaskApiImpl( ) : TaskApi { override suspend fun addTask(taskRequest: TaskRequest): ApiResponse { return try { - client.post(HttpRoutes.TASK) { + val response = client.post(HttpRoutes.TASK) { setBody(taskRequest) contentType(ContentType.Application.Json) - }.taskToApiResponse() - } catch(e: RedirectResponseException){ - // 3xx - responses + } + response.taskToApiResponse() + } catch (e: RedirectResponseException) { handleErrorApiErrorResponse() - } catch(e: ClientRequestException){ - // 4xx - responses + } catch (e: ClientRequestException) { handleErrorApiErrorResponse() - } catch(e: ServerResponseException){ - // 5xx - responses + } catch (e: ServerResponseException) { handleErrorApiErrorResponse() - } catch(e: Exception){ + } catch (e: Exception) { handleErrorApiErrorResponse() } } - override suspend fun getMonthTasks(month: Int, year: Int): Flow>> = flow { + override suspend fun getMonthTasks( + month: Int, + year: Int, + ): Flow>> = flow { emit(ApiResponseWithData.Loading()) try { emit( @@ -61,7 +62,7 @@ internal class TaskApiImpl( } }.toTaskItemList() ) - } catch (e:Exception){ + } catch (e: Exception) { e.printStackTrace() emit(ApiResponseWithData.Error()) } @@ -74,7 +75,7 @@ internal class TaskApiImpl( appendPathSegments(taskId.toString()) } }.toTaskItem() - } catch (e:Exception){ + } catch (e: Exception) { e.printStackTrace() null } @@ -87,16 +88,16 @@ internal class TaskApiImpl( appendPathSegments(taskId.toString()) } }.excludeToApiResponse() - } catch(e: RedirectResponseException){ - // 3xx - responses + } catch (e: RedirectResponseException) { + handleErrorApiErrorResponse() - } catch(e: ClientRequestException){ - // 4xx - responses + } catch (e: ClientRequestException) { + handleErrorApiErrorResponse() - } catch(e: ServerResponseException){ - // 5xx - responses + } catch (e: ServerResponseException) { + handleErrorApiErrorResponse() - } catch(e: Exception){ + } catch (e: Exception) { handleErrorApiErrorResponse() } } @@ -112,16 +113,16 @@ internal class TaskApiImpl( } val test = response.taskUpdateToApiResponse() return test - } catch(e: RedirectResponseException){ - // 3xx - responses + } catch (e: RedirectResponseException) { + handleErrorApiErrorResponse() - } catch(e: ClientRequestException){ - // 4xx - responses + } catch (e: ClientRequestException) { + handleErrorApiErrorResponse() - } catch(e: ServerResponseException){ - // 5xx - responses + } catch (e: ServerResponseException) { + handleErrorApiErrorResponse() - } catch(e: Exception){ + } catch (e: Exception) { handleErrorApiErrorResponse() } } diff --git a/app/src/main/java/com/routinely/routinely/data/task/extensions/taskExtensions.kt b/app/src/main/java/com/routinely/routinely/data/task/extensions/taskExtensions.kt index d7009dc..9a934dc 100644 --- a/app/src/main/java/com/routinely/routinely/data/task/extensions/taskExtensions.kt +++ b/app/src/main/java/com/routinely/routinely/data/task/extensions/taskExtensions.kt @@ -1,36 +1,59 @@ package com.routinely.routinely.data.task.extensions +import com.routinely.routinely.R import com.routinely.routinely.data.auth.model.ApiResponse import com.routinely.routinely.data.auth.model.ApiResponseWithData -import com.routinely.routinely.util.TaskCategory import com.routinely.routinely.util.TaskItem import com.routinely.routinely.util.TaskItemRemote +import com.routinely.routinely.util.TaskListResponse import com.routinely.routinely.util.TaskPriorities import com.routinely.routinely.util.TaskTag import io.ktor.client.call.body import io.ktor.client.statement.HttpResponse import io.ktor.http.HttpStatusCode import kotlinx.serialization.Serializable +import timber.log.Timber -suspend fun HttpResponse.taskToApiResponse() : ApiResponse { - return when(this.status) { +@Serializable +data class ErrorResponse( + val errors: List, +) + +@Serializable +data class ErrorDetail( + val property: String?, + val message: String?, +) + +suspend fun HttpResponse.taskToApiResponse(): ApiResponse { + return when (this.status) { HttpStatusCode.Created -> { ApiResponse.Success } + else -> { - val test = this.body() - println("Property ${test.errors?.property} -- ${test.errors?.message}") - ApiResponse.DefaultError + try { + val errorResponse = this.body() + if (errorResponse.errors.isNotEmpty()) { + ApiResponse.Error(message = R.string.api_unexpected_error) + } else { + ApiResponse.DefaultError + } + } catch (e: Exception) { + println("Erro ao processar resposta: ${e.message}") + ApiResponse.DefaultError + } } } } -suspend fun HttpResponse.taskUpdateToApiResponse() : ApiResponse { - return when(this.status) { +suspend fun HttpResponse.taskUpdateToApiResponse(): ApiResponse { + return when (this.status) { HttpStatusCode.OK -> { ApiResponse.Success } + else -> { val test = this.body() println("Property ${test.errors?.property} -- ${test.errors?.message}") @@ -39,51 +62,58 @@ suspend fun HttpResponse.taskUpdateToApiResponse() : ApiResponse { } } -suspend fun HttpResponse.toTaskItemList() : ApiResponseWithData> { - return when(this.status) { +suspend fun HttpResponse.toTaskItemList(): ApiResponseWithData> { + return when (this.status) { HttpStatusCode.OK -> { - val remote = this.body?>() ?: return ApiResponseWithData.EmptyData() - if(remote.isEmpty()) return ApiResponseWithData.EmptyData() - - return ApiResponseWithData.Success( - remote.map { - TaskItem( - id = it.id, - name = it.name, - date = it.date, - hour = it.hour, - description = it.description, - tag = mapApiStringToTag(it.tag), - priority = mapApiStringToPriority(it.priority), - category = mapApiStringToCategory(it.category) - ) - } - ) + try { + val response = + this.body() ?: return ApiResponseWithData.EmptyData() + if (response.tasks.isEmpty()) return ApiResponseWithData.EmptyData() + + ApiResponseWithData.Success( + response.tasks.map { + TaskItem( + id = it.id, + name = it.name, + date = it.date, + type = it.type.lowercase(), + category = it.category, + description = it.description, + finallyDate = it.finallyDate, + weekDays = it.weekDays + ) + } + ) + } catch (e: Exception) { + ApiResponseWithData.DefaultError() + } } + else -> { ApiResponseWithData.DefaultError() } } } -suspend fun HttpResponse.toTaskItem() : TaskItem? { - return when(this.status) { +suspend fun HttpResponse.toTaskItem(): TaskItem? { + return when (this.status) { HttpStatusCode.OK -> { val remote = this.body() - if(remote == null) return remote + if (remote == null) return remote remote.let { TaskItem( id = it.id, name = it.name, date = it.date, - hour = it.hour, + type = it.type, + category = it.category, description = it.description, - tag = mapApiStringToTag(it.tag), - priority = mapApiStringToPriority(it.priority), - category = mapApiStringToCategory(it.category) + finallyDate = it.finallyDate, + weekDays = it.weekDays ) } } + else -> { println(this.status) null @@ -93,54 +123,23 @@ suspend fun HttpResponse.toTaskItem() : TaskItem? { @Serializable data class Error( - val errors: Errors? + val errors: Errors?, ) @Serializable data class Errors( val property: String, - val message: String + val message: String, ) -fun HttpResponse.excludeToApiResponse() : ApiResponse { - return when(this.status) { +fun HttpResponse.excludeToApiResponse(): ApiResponse { + return when (this.status) { HttpStatusCode.OK -> { ApiResponse.Success } + else -> { ApiResponse.DefaultError } } } - -fun mapApiStringToCategory(apiString: String): TaskCategory { - return when (apiString) { - "career" -> TaskCategory.Career - "personal" -> TaskCategory.Personal - "health" -> TaskCategory.Health - "finance" -> TaskCategory.Finances - "study" -> TaskCategory.Studies - else -> throw IllegalArgumentException("Unknown category: $apiString") - } -} - -private fun mapApiStringToPriority(apiString: String): TaskPriorities { - return when (apiString) { - "urgent" -> TaskPriorities.Urgent - "high" -> TaskPriorities.High - "medium" -> TaskPriorities.Medium - "low" -> TaskPriorities.Low - else -> throw IllegalArgumentException("Unknown priority: $apiString") - } -} - -private fun mapApiStringToTag(apiString: String): TaskTag { - return when (apiString) { - "application" -> TaskTag.Candidacy - "account" -> TaskTag.Bill - "exercise" -> TaskTag.Exercise - "beauty" -> TaskTag.Beauty - "literature" -> TaskTag.Literature - else -> throw IllegalArgumentException("Unknown tag: $apiString") - } -} \ No newline at end of file diff --git a/app/src/main/java/com/routinely/routinely/home/HomeScreen.kt b/app/src/main/java/com/routinely/routinely/home/HomeScreen.kt index 7667f30..2f21dbd 100644 --- a/app/src/main/java/com/routinely/routinely/home/HomeScreen.kt +++ b/app/src/main/java/com/routinely/routinely/home/HomeScreen.kt @@ -1,12 +1,13 @@ package com.routinely.routinely.home import androidx.compose.foundation.layout.Column +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.material3.Scaffold -import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.mutableStateListOf @@ -17,25 +18,21 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshots.SnapshotStateMap import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp import com.kizitonwose.calendar.compose.weekcalendar.rememberWeekCalendarState import com.routinely.routinely.R import com.routinely.routinely.data.auth.model.ApiResponseWithData import com.routinely.routinely.ui.components.BottomAppBarRoutinely import com.routinely.routinely.ui.components.CalendarRoutinely import com.routinely.routinely.ui.components.CardTask -import com.routinely.routinely.ui.components.DropdownTaskFilter +import com.routinely.routinely.ui.components.DropdownActivityFilter import com.routinely.routinely.ui.components.Task import com.routinely.routinely.ui.components.TaskAlertDialog import com.routinely.routinely.ui.components.TopAppBarRoutinely -import com.routinely.routinely.ui.theme.GrayRoutinely import com.routinely.routinely.util.ActivityTag import com.routinely.routinely.util.BottomNavItems import com.routinely.routinely.util.MenuItem -import com.routinely.routinely.util.TaskFields import com.routinely.routinely.util.TaskItem import java.time.LocalDate @@ -69,35 +66,46 @@ fun HomeScreen( var expanded by remember { mutableStateOf(false) } var showDeleteDialog by rememberSaveable { mutableStateOf(false) } - val temporaryDeleteId by remember { mutableStateOf(null) } - var selectedActivityTag by remember { mutableIntStateOf(ActivityTag.AllActivity.stringId) } + var temporaryDeleteId by remember { mutableStateOf(null) } + var selectedActivityTag by remember { mutableIntStateOf(ActivityTag.Task.stringId) } var selectedDate by remember { mutableStateOf(null) } val taskSelections by rememberSaveable(stateSaver = snapshotStateMapSaver()) { mutableStateOf(SnapshotStateMap()) } - val originalCategories by rememberSaveable(stateSaver = snapshotStateMapSaver()) { - mutableStateOf(SnapshotStateMap()) - } - val tasks = remember { mutableStateListOf().apply { addAll(menuTask) } } - menuTask.forEach { task -> - if (!taskSelections.containsKey(task.id)) { - taskSelections[task.id] = task.isSelected - } - if (!originalCategories.containsKey(task.id)) { - originalCategories[task.id] = task.category + val tasks = remember { mutableStateListOf() } + + LaunchedEffect(getTasksResponse) { + if (getTasksResponse is ApiResponseWithData.Success) { + tasks.clear() + getTasksResponse.data?.let { taskItems -> + tasks.addAll(taskItems.mapNotNull { taskItem -> + try { + Task.fromApi( + id = taskItem.id, + title = taskItem.name, + description = taskItem.description ?: "", + category = taskItem.category, + date = LocalDate.parse(taskItem.date.substring(0, 10)), + type = taskItem.type + ) + } catch (e: Exception) { + null + } + }) + } } } - val filteredTasks = menuTask.filter { task -> - val matchesCategory = when (selectedActivityTag) { - ActivityTag.AllActivity.stringId -> task.category != ActivityTag.Completed.stringId - ActivityTag.Completed.stringId -> task.category == ActivityTag.Completed.stringId - else -> task.category == selectedActivityTag + val filteredTasks = tasks.filter { task -> + val matchesType = when (selectedActivityTag) { + ActivityTag.Task.stringId -> task.type.value == "task" + ActivityTag.Habit.stringId -> task.type.value == "habit" + else -> true } val matchesDate = selectedDate?.let { task.date == it } ?: true - matchesCategory && matchesDate + matchesType && matchesDate } Scaffold( @@ -128,78 +136,62 @@ fun HomeScreen( state = weekCalendarState, onDateSelected = { newDate -> selectedDate = newDate + onSelectDayChange( + newDate.monthValue, + newDate.year, + newDate.dayOfMonth + ) } ) - DropdownTaskFilter( + DropdownActivityFilter( modifier = Modifier.padding(top = 9.dp), - labelRes = selectedActivityTag, - onValueChange = { newLabelRes -> - selectedActivityTag = newLabelRes + labelRes = R.string.label_tag_dropdown, + onValueChange = { stringId -> + selectedActivityTag = stringId }, - list = TaskFields.getAllOptions(), + list = listOf(ActivityTag.Task, ActivityTag.Habit), + option = selectedActivityTag ) - if (filteredTasks.isEmpty()) { - Text( - text = "Você ainda não tem atividades para hoje", - modifier = Modifier.padding(top = 32.dp, ), - fontSize = 14.sp, - color = Color.Gray - ) - } - LazyColumn( modifier = Modifier + ) { items(filteredTasks) { task -> val isSelected = taskSelections[task.id] ?: false - val originalCategory = originalCategories[task.id] ?: task.category - - if (isSelected) { - task.category = ActivityTag.Completed.stringId - } else { - task.category = originalCategory - } - val shouldDisplay = when (selectedActivityTag) { - ActivityTag.AllActivity.stringId -> true - ActivityTag.Completed.stringId -> task.category == ActivityTag.Completed.stringId - else -> task.category == selectedActivityTag - } - if (shouldDisplay) { - CardTask( - title = task.title, - description = task.description, - category = task.category, - originalCategory = if (task.category == ActivityTag.Completed.stringId) originalCategory.toString() else null, - isSelected = isSelected, - onSelected = { selected -> - taskSelections[task.id] = selected - if (selected) { - tasks.find { it.id == task.id }?.category = ActivityTag.Completed.stringId.toInt() - } else { - tasks.find { it.id == task.id}?.category = originalCategory - } - } - ) + val taskType = when (task.type.value) { + "task" -> ActivityTag.Task.stringId + "habit" -> ActivityTag.Habit.stringId + else -> ActivityTag.Task.stringId } + CardTask( + title = task.title, + description = task.description, + taskType = taskType, + category = task.category.id, + isSelected = isSelected, + onSelected = { selected -> + taskSelections[task.id] = selected + } + ) } } + } - if (showDeleteDialog) { - TaskAlertDialog( - textRes = R.string.delete_task_confirmation, - onConfirm = { - showDeleteDialog = false - temporaryDeleteId?.let { onDeleteTaskClicked(it) } - }, - onCancel = { - showDeleteDialog = false - }, - onDismissRequest = { - showDeleteDialog = false - } - ) - } + if (showDeleteDialog) { + TaskAlertDialog( + textRes = R.string.delete_task_confirmation, + onConfirm = { + showDeleteDialog = false + temporaryDeleteId?.let { onDeleteTaskClicked(it) } + }, + onCancel = { + showDeleteDialog = false + }, + onDismissRequest = { + showDeleteDialog = false + } + ) } } ) diff --git a/app/src/main/java/com/routinely/routinely/home/HomeViewModel.kt b/app/src/main/java/com/routinely/routinely/home/HomeViewModel.kt index 91cff5d..57d81c8 100644 --- a/app/src/main/java/com/routinely/routinely/home/HomeViewModel.kt +++ b/app/src/main/java/com/routinely/routinely/home/HomeViewModel.kt @@ -13,6 +13,7 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.launch import java.util.Calendar +import timber.log.Timber class HomeViewModel( private val getUserTasksFromMonthUseCase: GetUserTasksFromMonthUseCase, diff --git a/app/src/main/java/com/routinely/routinely/navigation/SetupNavGraph.kt b/app/src/main/java/com/routinely/routinely/navigation/SetupNavGraph.kt index 387bae3..8e0f9b0 100644 --- a/app/src/main/java/com/routinely/routinely/navigation/SetupNavGraph.kt +++ b/app/src/main/java/com/routinely/routinely/navigation/SetupNavGraph.kt @@ -1,5 +1,6 @@ package com.routinely.routinely.navigation +import android.util.Log import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable @@ -47,6 +48,7 @@ import com.routinely.routinely.ui.components.Task import com.routinely.routinely.util.ActivityTag import com.routinely.routinely.util.MenuItem import com.routinely.routinely.util.TaskItem +import com.routinely.routinely.util.TaskMapper import org.koin.androidx.compose.koinViewModel import java.time.LocalDate @@ -121,7 +123,7 @@ fun SetupNavGraph( } ) forgotPasswordRoute( - navigateToCodeVerificationScreen = {accountId -> + navigateToCodeVerificationScreen = { accountId -> navController.navigate(Screen.VerificationCodeScreen.withArgs(accountId)) } ) @@ -237,7 +239,7 @@ fun NavGraphBuilder.createAccountRoute( } fun NavGraphBuilder.newPasswordRoute( - navigateToLoginScreen: () -> Unit + navigateToLoginScreen: () -> Unit, ) { composable( route = Screen.NewPasswordScreen.route, @@ -258,10 +260,10 @@ fun NavGraphBuilder.newPasswordRoute( password = password, accountId = accountId, code = code - ),confirmPassword + ), confirmPassword ) }, - passwordStateValidation = {password -> + passwordStateValidation = { password -> viewModel.passwordState(password) }, navigateToLoginScreen = { @@ -335,81 +337,62 @@ fun NavGraphBuilder.homeScreenRoute( onNotificationClicked: () -> Unit, onNewTaskClicked: () -> Unit, navigateToLoginScreen: () -> Unit, - navigateToEditScreen: (taskId: Int) -> Unit, + navigateToEditScreen: (Int) -> Unit, ) { composable(route = Screen.HomeScreen.route) { navBackStackEntry -> val viewModel: HomeViewModel = koinViewModel() + val getTasksResponse by viewModel.getTasksResponse.collectAsStateWithLifecycle() + val deleteTaskResponse by viewModel.deleteTaskResponse.collectAsState() + val menuItems = listOf( - MenuItem( - text = stringResource(R.string.menu_configuration), - onItemClick = { } - ), - MenuItem( - text = stringResource(R.string.menu_goal), - onItemClick = { } - ), - MenuItem( - text = stringResource(R.string.menu_notification), - onItemClick = { } - ), MenuItem( text = stringResource(R.string.menu_logout), onItemClick = { viewModel.logout() - navigateToScreenOnlyIfResumed(navBackStackEntry, navigateToLoginScreen) + navigateToLoginScreen() } - ), - ) - val menuTask = listOf( - Task(id = 1, title = "Title 1", description = "Description 1", category = ActivityTag.Project.stringId, date = LocalDate.now()), - Task(id = 2, title = "Title 2", description = "Description 2", category = ActivityTag.Task.stringId, date = LocalDate.now()), - Task(id = 3, title = "Title 3", description = "Description 3", category = ActivityTag.Habit.stringId, date = LocalDate.now()), - Task(id = 4, title = "Title 4", description = "Description 4", category = ActivityTag.Project.stringId, date = LocalDate.now()), + ) ) - val deleteTaskResponse by viewModel.deleteTaskResponse.collectAsStateWithLifecycle() - val getTasksResponse = viewModel.getTasksResponse.collectAsStateWithLifecycle() - - LaunchedEffect(key1 = deleteTaskResponse) { - if (deleteTaskResponse == ApiResponse.Success) { - viewModel.getUserTasks( - month = viewModel.lastMonth, - year = viewModel.lastYear, - day = viewModel.lastDay, - force = true - ) + val menuTask = getTasksResponse.data?.mapNotNull { taskItem -> + try { + TaskMapper.fromApi(taskItem) + } catch (e: Exception) { + null } - } + } ?: emptyList() HomeScreen( - onNotificationClicked = { onNotificationClicked() }, - onNewTaskClicked = { onNewTaskClicked() }, - onEditTaskClicked = { - navigateToEditScreen(it.id) + onNotificationClicked = onNotificationClicked, + onNewTaskClicked = onNewTaskClicked, + onEditTaskClicked = { taskItem -> + navigateToEditScreen(taskItem.id) }, - onDeleteTaskClicked = { - viewModel.excludeTask(it) - viewModel.getUserTasks( - month = viewModel.lastMonth, - year = viewModel.lastYear, - day = viewModel.lastDay, - force = true - ) + onDeleteTaskClicked = { taskItem -> + viewModel.excludeTask(taskItem) }, menuItems = menuItems, - + menuTask = menuTask, onSelectDayChange = { month, year, day -> viewModel.getUserTasks(month, year, day) }, - getTasksResponse = getTasksResponse.value, - menuTask = menuTask, + getTasksResponse = getTasksResponse ) + LaunchedEffect(deleteTaskResponse) { + if (deleteTaskResponse is ApiResponse.Success) { + viewModel.getUserTasks( + viewModel.lastMonth, + viewModel.lastYear, + viewModel.lastDay, + force = true + ) + } + } } } - fun NavGraphBuilder.addTaskScreenRoute( onBackButtonPressed: () -> Unit, onHomeButtonPressed: () -> Unit, @@ -571,7 +554,7 @@ private fun NavBackStackEntry.lifecycleIsresumed() = private fun navigateToScreenOnlyIfResumed( navBackStackEntry: NavBackStackEntry, - navigateToHomeScreen: () -> Unit + navigateToHomeScreen: () -> Unit, ) { if (navBackStackEntry.lifecycleIsresumed()) { navigateToHomeScreen() diff --git a/app/src/main/java/com/routinely/routinely/task/AddTaskScreen.kt b/app/src/main/java/com/routinely/routinely/task/AddTaskScreen.kt index baf75c7..201cc54 100644 --- a/app/src/main/java/com/routinely/routinely/task/AddTaskScreen.kt +++ b/app/src/main/java/com/routinely/routinely/task/AddTaskScreen.kt @@ -25,33 +25,33 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle 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 com.routinely.routinely.R -import com.routinely.routinely.data.auth.model.TaskRequest import com.routinely.routinely.data.auth.model.ApiResponse +import com.routinely.routinely.data.auth.model.TaskRequest import com.routinely.routinely.ui.components.AddTaskButton import com.routinely.routinely.ui.components.BottomAppBarRoutinely import com.routinely.routinely.ui.components.DatePickerDialogRoutinely import com.routinely.routinely.ui.components.DescriptionTextField -import com.routinely.routinely.ui.components.DropdownTaskFilter -import com.routinely.routinely.ui.components.DropdownRoutinelyPriorities +import com.routinely.routinely.ui.components.DropdownActivityFilter import com.routinely.routinely.ui.components.IndeterminateCircularIndicator import com.routinely.routinely.ui.components.LabelError import com.routinely.routinely.ui.components.TaskNameTextField import com.routinely.routinely.ui.components.TimePickerDialog import com.routinely.routinely.ui.components.TopAppBarRoutinely import com.routinely.routinely.ui.theme.PurpleRoutinely +import com.routinely.routinely.util.ActivityTag import com.routinely.routinely.util.BottomNavItems import com.routinely.routinely.util.MenuItem import com.routinely.routinely.util.TaskCategory import com.routinely.routinely.util.TaskFields -import com.routinely.routinely.util.TaskPriorities -import com.routinely.routinely.util.TaskTag import com.routinely.routinely.util.validators.DateTimeInputValid import com.routinely.routinely.util.validators.DescriptionInputValid import com.routinely.routinely.util.validators.DropdownInputValid import com.routinely.routinely.util.validators.TaskNameInputValid +import java.util.Locale @OptIn(ExperimentalMaterial3Api::class) @@ -74,14 +74,26 @@ fun AddTaskScreen( var taskDateState by rememberSaveable { mutableStateOf(DateTimeInputValid.Empty) } var taskTime by rememberSaveable { mutableStateOf("") } var taskTimeState by rememberSaveable { mutableStateOf(DateTimeInputValid.Empty) } - var dropdownPriority by rememberSaveable { mutableStateOf(null) } - var dropdownPriorityState by rememberSaveable { mutableStateOf(DropdownInputValid.Empty) } - var dropdownTags by rememberSaveable { mutableStateOf(null) } - var dropdownTagsState by rememberSaveable { mutableStateOf(DropdownInputValid.Empty) } + var selectedWeekDays by rememberSaveable { mutableStateOf>(emptyList()) } + + var dropdownTags by rememberSaveable { mutableStateOf(null) } + var dropdownTagsState by rememberSaveable { + mutableStateOf( + DropdownInputValid.Empty + ) + } var dropdownCategory by rememberSaveable { mutableStateOf(null) } - var dropdownCategoryState by rememberSaveable { mutableStateOf(DropdownInputValid.Empty) } + var dropdownCategoryState by rememberSaveable { + mutableStateOf( + DropdownInputValid.Empty + ) + } var taskDescription by rememberSaveable { mutableStateOf("") } - var taskDescriptionState by rememberSaveable { mutableStateOf(DescriptionInputValid.Empty) } + var taskDescriptionState by rememberSaveable { + mutableStateOf( + DescriptionInputValid.Empty + ) + } var apiErrorMessage by rememberSaveable { mutableIntStateOf(0) } var showApiErrors by rememberSaveable { mutableStateOf(false) } @@ -93,7 +105,7 @@ fun AddTaskScreen( topBar = { TopAppBarRoutinely( onMenuClick = { expanded = true }, - onNotificationClick = { }, + onNotificationClick = { }, showBackButton = true, onBackButtonClicked = { onBackButtonPressed() }, onDismissMenu = { expanded = false }, @@ -164,37 +176,40 @@ fun AddTaskScreen( Column( verticalArrangement = Arrangement.spacedBy(16.dp) ) { - DropdownRoutinelyPriorities( - labelRes = R.string.label_priority_dropdown, - onValueChange = { stringId -> - dropdownPriority = TaskFields.getTaskFieldByStringId(stringId = stringId) - dropdownPriorityState = DropdownInputValid.Valid - }, - list = TaskPriorities.getAllTaskPriorities(), - ) Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(16.dp), ) { - DropdownTaskFilter( + DropdownActivityFilter( labelRes = R.string.label_category_dropdown, onValueChange = { stringId -> - dropdownCategory = TaskFields.getTaskFieldByStringId(stringId = stringId) + dropdownCategory = TaskCategory.fromId(stringId) dropdownCategoryState = DropdownInputValid.Valid }, - list = TaskFields.getAllOptions(), + list = TaskCategory.entries.map { category: TaskCategory -> + object : TaskFields(category.id, category.name) {} + }, modifier = Modifier.weight(1f), + option = dropdownCategory?.id ) - DropdownTaskFilter( + } + + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(16.dp), + ) { + DropdownActivityFilter( labelRes = R.string.label_tag_dropdown, onValueChange = { stringId -> - dropdownTags = TaskFields.getTaskFieldByStringId(stringId = stringId) + dropdownTags = ActivityTag.fromId(stringId) dropdownTagsState = DropdownInputValid.Valid }, - list = TaskFields.getAllOptions(), + list = listOf(ActivityTag.Task, ActivityTag.Habit), modifier = Modifier.weight(1f), + option = dropdownTags?.stringId ) } + DescriptionTextField( value = taskDescription, onValueChange = { newTaskDescription: String -> @@ -204,60 +219,84 @@ fun AddTaskScreen( labelRes = stringResource(id = R.string.label_task_description), error = taskDescriptionState, ) - if(showApiErrors) { + + DropdownWeeklyFrequencyScreen( + onWeekDaysSelected = { days -> + selectedWeekDays = days + } + ) + + if (showApiErrors) { LabelError(stringResource(apiErrorMessage)) } - AddTaskButton ( + AddTaskButton( { + val formattedDate = if (taskDate.isNotEmpty() && taskTime.isNotEmpty()) { + "$taskDate $taskTime" + } else { + "" + } + + val category = dropdownCategory?.apiName ?: "Several" + val type = dropdownTags?.apiString?.lowercase() ?: "task" + val weekDays = if (selectedWeekDays.isEmpty()) + listOf("Monday") + else + selectedWeekDays.map { it.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() } } + onAddTaskClick( TaskRequest( - name = taskName, - date = taskDate, - priority = dropdownPriority!!.apiString, - description = taskDescription, - hour = taskTime, - tag = dropdownTags!!.apiString, - category = dropdownCategory!!.apiString + name = taskName.trim(), + description = taskDescription.trim(), + date = formattedDate, + category = category, + finallyDate = formattedDate, + weekDays = weekDays, + type = type, ) ) }, areFieldsValid = taskNameState == TaskNameInputValid.Valid && taskDescriptionState == DescriptionInputValid.Valid && taskDateState == DateTimeInputValid.Valid && - dropdownPriorityState == DropdownInputValid.Valid && - dropdownCategoryState == DropdownInputValid.Valid && - dropdownTagsState == DropdownInputValid.Valid && - taskTimeState == DateTimeInputValid.Valid + taskTimeState == DateTimeInputValid.Valid && + dropdownCategory != null && + dropdownTags != null && + selectedWeekDays.isNotEmpty() ) } } }, ) LaunchedEffect(key1 = addTaskResult) { - when(addTaskResult) { + when (addTaskResult) { is ApiResponse.Success -> { showApiErrors = false showLoading = false navigateToHomeScreen() } + is ApiResponse.Error -> { apiErrorMessage = addTaskResult.message showApiErrors = true showLoading = false } + is ApiResponse.DefaultError -> { apiErrorMessage = R.string.api_unexpected_error showApiErrors = true showLoading = false } + is ApiResponse.Loading -> { showLoading = true showApiErrors = false } + else -> Unit } } - if(showLoading) { + if (showLoading) { Box( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center @@ -266,4 +305,34 @@ fun AddTaskScreen( IndeterminateCircularIndicator() } } -} \ No newline at end of file +} + + +@Preview(showBackground = true) +@Composable +fun PreviewAddTaskScreen() { + AddTaskScreen( + onBackButtonPressed = { }, + onHomeButtonPressed = { }, + taskNameStateValidation = { nameTask -> + TaskNameInputValid.Valid + }, + taskDateStateValidation = { dateTask -> + DateTimeInputValid.Valid + }, + taskTimeStateValidation = { timeTask -> + DateTimeInputValid.Valid + }, + taskDescriptionStateValidation = { descriptionTask -> + DescriptionInputValid.Valid + }, + navigateToHomeScreen = { }, + onAddTaskClick = { taskRequest -> + }, + menuItems = listOf( + ), + addTaskResult = ApiResponse.Success + ) +} + + diff --git a/app/src/main/java/com/routinely/routinely/task/AddTaskViewModel.kt b/app/src/main/java/com/routinely/routinely/task/AddTaskViewModel.kt index f19c41a..76f9976 100644 --- a/app/src/main/java/com/routinely/routinely/task/AddTaskViewModel.kt +++ b/app/src/main/java/com/routinely/routinely/task/AddTaskViewModel.kt @@ -3,7 +3,6 @@ package com.routinely.routinely.task import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.routinely.routinely.R -import com.routinely.routinely.core.Session import com.routinely.routinely.core.useCase.LogoutUseCase import com.routinely.routinely.data.auth.model.ApiResponse import com.routinely.routinely.data.auth.model.TaskRequest @@ -23,68 +22,67 @@ class AddTaskViewModel( private val _apiResponse = MutableStateFlow(ApiResponse.Empty) val apiResponse = _apiResponse.asStateFlow() - fun addTask(newTask: TaskRequest) { - val newTaskData = TaskRequest( - name = newTask.name, - date = newTask.date, - hour = newTask.hour, - description = newTask.description, - priority = newTask.priority, - category = newTask.category, - tag = newTask.tag, - ) + companion object { + private const val TAG = "AddTaskViewModel" + } + fun addTask(newTask: TaskRequest) { viewModelScope.launch { _apiResponse.value = ApiResponse.Loading try { - _apiResponse.value = taskApi.addTask(newTaskData) - } catch(e: Exception) { + _apiResponse.value = taskApi.addTask(newTask) + } catch (e: Exception) { _apiResponse.value = ApiResponse.DefaultError } } } - fun taskNameState(taskName: String) : TaskNameInputValid { + fun taskNameState(taskName: String): TaskNameInputValid { return when { taskName.isEmpty() -> { TaskNameInputValid.Error(R.string.empty_field) } + taskName.count { it.isLetter() } > 50 -> { TaskNameInputValid.Error(R.string.task_name_limit) } + else -> { TaskNameInputValid.Valid } } } - fun taskDateState(taskDate: String) : DateTimeInputValid { + fun taskDateState(taskDate: String): DateTimeInputValid { return when { taskDate.isEmpty() -> { DateTimeInputValid.Error(R.string.empty_field) } + else -> { DateTimeInputValid.Valid } } } - fun taskTimeState(taskTime: String) : DateTimeInputValid { + fun taskTimeState(taskTime: String): DateTimeInputValid { return when { taskTime.isEmpty() -> { DateTimeInputValid.Error(R.string.empty_field) } + else -> { DateTimeInputValid.Valid } } } - fun taskDescriptionState(description: String) : DescriptionInputValid { + fun taskDescriptionState(description: String): DescriptionInputValid { return when { description.isEmpty() -> { DescriptionInputValid.Error(R.string.empty_field) } + else -> { DescriptionInputValid.Valid } @@ -96,4 +94,4 @@ class AddTaskViewModel( logoutUseCase() } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/routinely/routinely/task/DropdownWeeklyFrequencyScreen.kt b/app/src/main/java/com/routinely/routinely/task/DropdownWeeklyFrequencyScreen.kt new file mode 100644 index 0000000..144c2da --- /dev/null +++ b/app/src/main/java/com/routinely/routinely/task/DropdownWeeklyFrequencyScreen.kt @@ -0,0 +1,339 @@ +package com.routinely.routinely.task + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.aspectRatio +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.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.kizitonwose.calendar.compose.HorizontalCalendar +import com.kizitonwose.calendar.compose.rememberCalendarState +import com.kizitonwose.calendar.core.CalendarDay +import com.kizitonwose.calendar.core.firstDayOfWeekFromLocale +import com.routinely.routinely.R +import com.routinely.routinely.ui.components.MonthHeader +import com.routinely.routinely.ui.theme.PurpleRoutinely +import com.routinely.routinely.ui.theme.lightGray +import java.time.LocalDate +import java.time.YearMonth +import java.time.format.DateTimeFormatter +import java.util.Locale + +@Composable +fun DropdownWeeklyFrequencyScreen( + onWeekDaysSelected: (List) -> Unit +) { + val daysOfWeek = listOf("D", "S", "T", "Q", "Q", "S", "S") + var currentDate by rememberSaveable { mutableStateOf(LocalDate.now()) } + var selectedDate by rememberSaveable { mutableStateOf(LocalDate.now()) } + var selectedDays by rememberSaveable { mutableStateOf>(emptySet()) } + val formatter = DateTimeFormatter.ofPattern("MMMM", Locale("pt", "BR")) + val monthName = formatter.format(currentDate) + + var isExpanded by remember { mutableStateOf(true) } + + LaunchedEffect(selectedDays) { + val weekDays = selectedDays.map { index -> + when (index) { + 0 -> "Sunday" + 1 -> "Monday" + 2 -> "Tuesday" + 3 -> "Wednesday" + 4 -> "Thursday" + 5 -> "Friday" + 6 -> "Saturday" + else -> "" + } + }.filter { it.isNotEmpty() } + onWeekDaysSelected(weekDays) + } + + Column( + modifier = Modifier + .fillMaxSize(), + verticalArrangement = Arrangement.spacedBy(15.dp) + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text( + modifier = Modifier.padding(horizontal = 5.dp), + text = "Frequência semanal", + fontSize = 16.sp, + fontWeight = FontWeight.W400 + ) + Icon( + painter = painterResource( + id = if (isExpanded) { + R.drawable.baseline_keyboard_arrow_up_24 + } else { + R.drawable.baseline_keyboard_arrow_down_24 + } + ), + contentDescription = "Expandir/Recolher", + modifier = Modifier.clickable { isExpanded = !isExpanded } + ) + } + + if (isExpanded) { + Text( + modifier = Modifier.padding(horizontal = 5.dp), + text = "Dias da semana", + fontSize = 16.sp, + fontWeight = FontWeight.Normal + ) + + Row( + modifier = Modifier + .padding(horizontal = 5.dp) + .fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(16.dp) + ) { + daysOfWeek.forEachIndexed { index, day -> + val isSelected = selectedDays.contains(index) + Box( + modifier = Modifier + .size(24.dp) + .border( + width = 1.dp, + color = if (isSelected) PurpleRoutinely else Color.Gray, + shape = CircleShape + ) + .background( + color = if (isSelected) PurpleRoutinely else Color.Transparent, + shape = CircleShape + ) + .clickable { + selectedDays = if (isSelected) { + selectedDays - index + } else { + selectedDays + index + } + } + .padding(1.dp), + contentAlignment = Alignment.Center + ) { + Text( + text = day, + fontSize = 16.sp, + fontWeight = FontWeight.Medium, + textAlign = TextAlign.Left, + color = if (isSelected) Color.White else Color.Black, + ) + } + } + } + + Text( + modifier = Modifier.padding(horizontal = 5.dp), + text = "Finaliza em:", + fontSize = 16.sp, + fontWeight = FontWeight.Normal + ) + Column( + modifier = Modifier + .background(lightGray) + .border( + border = BorderStroke(1.dp, Color.Blue), + shape = RoundedCornerShape(8.dp) + ) + ) { + Column( + Modifier.padding(10.dp) + ) { + + Row( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 25.dp), + horizontalArrangement = Arrangement.Absolute.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + modifier = Modifier.padding(horizontal = 20.dp), + text = "${monthName.capitalize()} de ${currentDate.year}", + fontWeight = FontWeight.Bold, + fontSize = 16.sp, + color = PurpleRoutinely, + textAlign = TextAlign.Center + ) + Row { + Icon( + painter = painterResource(id = R.drawable.baseline_keyboard_arrow_left_24), + tint = PurpleRoutinely, + contentDescription = "Mês anterior", + modifier = Modifier.clickable { + currentDate = currentDate.minusMonths(1) + } + ) + Spacer(modifier = Modifier.width(12.dp)) + Icon( + painter = painterResource(id = R.drawable.baseline_keyboard_arrow_right_24), + tint = PurpleRoutinely, + contentDescription = "Próximo mês", + modifier = Modifier.clickable { + currentDate = currentDate.plusMonths(1) + } + ) + } + } + Spacer(modifier = Modifier.height(20.dp)) + CalendarGrid( + currentDate = currentDate, + selectedDate = selectedDate, + onDateSelected = { date -> + selectedDate = date + currentDate = date + } + ) + } + } + } + } +} + + +@Composable +fun CalendarGrid( + currentDate: LocalDate, + selectedDate: LocalDate?, + onDateSelected: (LocalDate) -> Unit, +) { + + val currentMonth = YearMonth.from(currentDate) + + val startMonth = remember { currentMonth.minusMonths(100) } + val endMonth = remember { currentMonth.plusMonths(100) } + val firstDayOfWeek = remember { firstDayOfWeekFromLocale() } + + val state = rememberCalendarState( + startMonth = startMonth, + endMonth = endMonth, + firstVisibleMonth = currentMonth, + firstDayOfWeek = firstDayOfWeek, + ) + + LaunchedEffect(currentMonth) { + state.scrollToMonth(currentMonth) + } + + HorizontalCalendar( + state = state, + dayContent = { day -> + val isSelected = selectedDate?.isEqual(day.date) == true + Day( + day = day, + isSelected = isSelected, + onClick = { onDateSelected(day.date) } + ) + }, + monthHeader = { month -> + Box( + modifier = Modifier + .background( + color = lightGray + ) + .fillMaxWidth(0.85f)) { + + val daysOfWeek = month.weekDays.first().map { it.date.dayOfWeek } + MonthHeader(daysOfWeek = daysOfWeek) + } + + }, + monthBody = { _, content -> + Box( + modifier = Modifier + .background( + color = lightGray + ) + .fillMaxWidth(0.85f) + ) { + content() + } + }, + monthContainer = { _, container -> + val configuration = LocalConfiguration.current + val screenWidth = configuration.smallestScreenWidthDp.dp + + Box( + modifier = Modifier + .width(screenWidth) + .padding(0.dp) + .clip(shape = RoundedCornerShape(8.dp)) + + ) { + container() + } + } + ) +} + +@Composable +fun Day( + day: CalendarDay, + isSelected: Boolean, + onClick: () -> Unit, +) { + Box( + modifier = Modifier + .aspectRatio(1f) + .background( + color = if (isSelected) PurpleRoutinely else Color.Transparent, + shape = CircleShape + ) + .clickable { onClick() } + .wrapContentSize(Alignment.Center) + .border( + width = 1.dp, + color = if (isSelected) PurpleRoutinely else Color.Transparent, + ), + contentAlignment = Alignment.Center + ) { + + Text( + text = day.date.dayOfMonth.toString(), + color = if (isSelected) Color.White else Color.Black, + textAlign = TextAlign.Center + ) + } +} + +@Preview(showBackground = true) +@Composable +fun PreviewWeeklyFrequencyScreen() { + DropdownWeeklyFrequencyScreen { _ -> } +} diff --git a/app/src/main/java/com/routinely/routinely/task/EditTaskScreen.kt b/app/src/main/java/com/routinely/routinely/task/EditTaskScreen.kt index 82da5e5..4b33dd2 100644 --- a/app/src/main/java/com/routinely/routinely/task/EditTaskScreen.kt +++ b/app/src/main/java/com/routinely/routinely/task/EditTaskScreen.kt @@ -29,6 +29,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle 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 com.routinely.routinely.R @@ -38,8 +39,7 @@ import com.routinely.routinely.ui.components.BottomAppBarRoutinely import com.routinely.routinely.ui.components.ConfirmTaskAlertDialog import com.routinely.routinely.ui.components.DatePickerDialogRoutinely import com.routinely.routinely.ui.components.DescriptionTextField -import com.routinely.routinely.ui.components.DropdownRoutinelyPriorities -import com.routinely.routinely.ui.components.DropdownTaskFilter +import com.routinely.routinely.ui.components.DropdownActivityFilter import com.routinely.routinely.ui.components.IndeterminateCircularIndicator import com.routinely.routinely.ui.components.RoutinelyTaskButton import com.routinely.routinely.ui.components.TaskAlertDialog @@ -51,10 +51,8 @@ import com.routinely.routinely.ui.theme.RedRoutinely import com.routinely.routinely.util.BottomNavItems import com.routinely.routinely.util.MenuItem import com.routinely.routinely.util.TaskCategory -import com.routinely.routinely.util.TaskFields +import com.routinely.routinely.util.TaskCategoryNew import com.routinely.routinely.util.TaskItem -import com.routinely.routinely.util.TaskPriorities -import com.routinely.routinely.util.TaskTag import com.routinely.routinely.util.validators.DateTimeInputValid import com.routinely.routinely.util.validators.DescriptionInputValid import com.routinely.routinely.util.validators.DropdownInputValid @@ -94,14 +92,13 @@ fun EditTaskScreen( var taskTime by rememberSaveable { mutableStateOf("") } var taskTimeState by rememberSaveable { mutableStateOf(DateTimeInputValid.Empty) } - var dropdownPriority by rememberSaveable { mutableStateOf(initialTask.priority) } var dropdownPriorityState by rememberSaveable { mutableStateOf( DropdownInputValid.Empty ) } - var dropdownTags by rememberSaveable { mutableStateOf(initialTask.tag) } + val dropdownTags by rememberSaveable { mutableStateOf(initialTask.type) } var dropdownTagsState by rememberSaveable { mutableStateOf( DropdownInputValid.Empty @@ -115,7 +112,7 @@ fun EditTaskScreen( ) } - var taskDescription by rememberSaveable { mutableStateOf(initialTask.description) } + var taskDescription by rememberSaveable { mutableStateOf(initialTask.description ?: "") } var taskDescriptionState by rememberSaveable { mutableStateOf( DescriptionInputValid.Empty @@ -129,13 +126,14 @@ fun EditTaskScreen( var hasChanges by remember { mutableStateOf(false) } var taskId by remember { mutableStateOf(0) } - val hourFormatter = DateTimeFormatter.ISO_DATE_TIME - initialTask.let { + taskNameState = taskNameStateValidation(it.name) taskDateState = taskDateStateValidation(it.date) - val dateTime = LocalDateTime.parse(it.hour, hourFormatter) + val dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm") + val dateTime = LocalDateTime.parse(initialTask.date, dateTimeFormatter) + val hour = dateTime.hour.toString() var minute = dateTime.minute.toString() if(minute.length == 1) { @@ -150,7 +148,7 @@ fun EditTaskScreen( dropdownCategoryState = DropdownInputValid.Valid - taskDescriptionState = taskDescriptionStateValidation(it.description) + taskDescriptionState = taskDescriptionStateValidation(it.description ?: "") taskId = it.id } @@ -237,46 +235,23 @@ fun EditTaskScreen( verticalArrangement = Arrangement.spacedBy(16.dp), ) { - DropdownRoutinelyPriorities( - labelRes = R.string.label_priority_dropdown, - onValueChange = { stringId -> - dropdownPriority = - TaskFields.getTaskFieldByStringId(stringId = stringId) - dropdownPriorityState = DropdownInputValid.Valid - hasChanges = true - }, - list = TaskPriorities.getAllTaskPriorities(), - option = dropdownPriority.stringId - ) Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(16.dp), modifier = Modifier.fillMaxWidth() ) { - DropdownTaskFilter( + DropdownActivityFilter( labelRes = R.string.label_category_dropdown, onValueChange = { stringId -> - dropdownCategory = - TaskFields.getTaskFieldByStringId(stringId = stringId) + dropdownCategory = TaskCategory.fromId(stringId).toString() dropdownCategoryState = DropdownInputValid.Valid hasChanges = true }, - list = TaskFields.getAllOptions(), - modifier = Modifier.weight(1f), - option = dropdownCategory.stringId - ) - DropdownTaskFilter( - labelRes = R.string.label_tag_dropdown, - onValueChange = { stringId -> - dropdownTags = - TaskFields.getTaskFieldByStringId(stringId = stringId) - dropdownTagsState = DropdownInputValid.Valid - hasChanges = true - }, - list = TaskFields.getAllOptions(), + list = TaskCategory.toTaskFieldsList(), modifier = Modifier.weight(1f), - option = dropdownTags.stringId + option = TaskCategory.fromString(dropdownCategory).id ) + } DescriptionTextField( value = taskDescription, @@ -334,12 +309,12 @@ fun EditTaskScreen( taskId, TaskRequest( name = taskName, - date = taskDate, - priority = dropdownPriority.apiString, description = taskDescription, - hour = taskTime, - tag = dropdownTags.apiString, - category = dropdownCategory.apiString + date = "$taskDate $taskTime", + category = dropdownCategory, + finallyDate = "$taskDate $taskTime", + weekDays = listOf("Monday"), + type = dropdownTags, ) ) }, @@ -450,3 +425,39 @@ fun EditTaskScreen( } } } + + +@Preview(showBackground = true, showSystemUi = true) +@Composable +fun EditTaskScreenPreview() { + val initialTask = TaskItem( + id = 1, + name = "Tarefa de Exemplo", + date = "2025-04-01 14:30", + type = "habit", + category = TaskCategoryNew.Career.toString(), + description = "Descrição da tarefa", + finallyDate = "2024-04-01 15:30", + weekDays = listOf("Monday", "Wednesday") + ) + + EditTaskScreen( + onBackButtonPressed = { }, + onNotificationClicked = { }, + onHomeButtonPressed = { }, + taskNameStateValidation = { TaskNameInputValid.Valid }, + taskDateStateValidation = { DateTimeInputValid.Valid }, + taskTimeStateValidation = { DateTimeInputValid.Valid }, + taskDescriptionStateValidation = { DescriptionInputValid.Valid }, + menuItems = listOf( + MenuItem("Opção 1", { /* Nada a fazer */ }), + MenuItem("Opção 2", { /* Nada a fazer */ }) + ), + editTaskResult = ApiResponse.Empty, + initialTask = initialTask, + onSaveChanges = { _, _ -> /* Nada a fazer */ }, + onDeleteTask = { /* Nada a fazer */ }, + onDuplicateTask = { true } + ) +} + diff --git a/app/src/main/java/com/routinely/routinely/task/EditTaskViewModel.kt b/app/src/main/java/com/routinely/routinely/task/EditTaskViewModel.kt index bfa4e8d..ab788e7 100644 --- a/app/src/main/java/com/routinely/routinely/task/EditTaskViewModel.kt +++ b/app/src/main/java/com/routinely/routinely/task/EditTaskViewModel.kt @@ -1,13 +1,11 @@ package com.routinely.routinely.task -import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.routinely.routinely.R +import com.routinely.routinely.core.useCase.LogoutUseCase import com.routinely.routinely.data.auth.model.ApiResponse import com.routinely.routinely.data.auth.model.TaskRequest -import com.routinely.routinely.core.Session -import com.routinely.routinely.core.useCase.LogoutUseCase import com.routinely.routinely.data.task.api.TaskApi import com.routinely.routinely.task.data.GetTaskByIdUseCase import com.routinely.routinely.util.TaskItem @@ -35,12 +33,12 @@ class EditTaskViewModel( fun saveTask(taskId: Int, newTask: TaskRequest) { val newTaskData = TaskRequest( name = newTask.name, - date = newTask.date, - hour = newTask.hour, description = newTask.description, - priority = newTask.priority, + date = newTask.date, category = newTask.category, - tag = newTask.tag, + finallyDate = newTask.finallyDate, + weekDays = newTask.weekDays, + type = newTask.type, ) viewModelScope.launch { @@ -67,18 +65,8 @@ class EditTaskViewModel( fun duplicateTask(): Boolean { if(task!!.name.contains("(5)")) return false - val hourFormatter = DateTimeFormatter.ISO_DATE_TIME - - val dateTime = LocalDateTime.parse(task!!.hour, hourFormatter) - val hour = dateTime.hour.toString() - var minute = dateTime.minute.toString() - if(minute.length == 1) { - minute = "0$minute" - } - val name = duplicateItem(task!!.name) - viewModelScope.launch { _apiResponse.value = ApiResponse.Loading try { @@ -86,11 +74,11 @@ class EditTaskViewModel( TaskRequest( name = name, date = task!!.date, - hour = "${hour}:${minute}", - description = task!!.description, - priority = task!!.priority.apiString, - category = task!!.category.apiString, - tag = task!!.tag.apiString, + description = task!!.description ?: "Sem descrição", + category = task!!.category, + finallyDate = task!!.finallyDate ?: "Sem final de data", + weekDays = task!!.weekDays, + type = task!!.type, ) ) } catch (e: Exception) { @@ -100,7 +88,6 @@ class EditTaskViewModel( return true } - fun getTaskById(taskId: Int): TaskItem { return runBlocking { task = getTaskByIdUseCase(taskId) diff --git a/app/src/main/java/com/routinely/routinely/ui/components/CardTask.kt b/app/src/main/java/com/routinely/routinely/ui/components/CardTask.kt index 0c0995c..ec5cef3 100644 --- a/app/src/main/java/com/routinely/routinely/ui/components/CardTask.kt +++ b/app/src/main/java/com/routinely/routinely/ui/components/CardTask.kt @@ -6,12 +6,10 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxHeight +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.layout.width -import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Edit @@ -19,7 +17,6 @@ import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.Icon import androidx.compose.material3.IconToggleButton -import androidx.compose.material3.MaterialTheme import androidx.compose.material3.RadioButtonColors import androidx.compose.material3.RadioButtonDefaults import androidx.compose.material3.Text @@ -30,87 +27,97 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.routinely.routinely.R import com.routinely.routinely.ui.theme.PurpleRoutinely -import com.routinely.routinely.ui.theme.cardHabit -import com.routinely.routinely.ui.theme.cardProject -import com.routinely.routinely.ui.theme.cardTask import com.routinely.routinely.ui.theme.categoryColor import com.routinely.routinely.util.ActivityTag +import com.routinely.routinely.util.TaskCategoryNew @Composable fun CardTask( title: String, description: String, + taskType: Int, category: Int, isSelected: Boolean, - originalCategory: String?, onSelected: (Boolean) -> Unit, - modifier: Modifier = Modifier + modifier: Modifier = Modifier, ) { - val (emoji, cardBackgroundColor, border) = getCategoryAttributes(category,originalCategory?.toInt()) - val categoryAttributes = getCategoryAttributes(category) + val (emoji, cardBackgroundColor, border) = getCategoryAttributes(taskType) + val taskTypeText = when (taskType) { + ActivityTag.Task.stringId -> "Atividade" + ActivityTag.Habit.stringId -> "Hábitos" + else -> "Atividade" + } + val categoryText = when (category) { + 1 -> TaskCategoryNew.Career.name + 2 -> TaskCategoryNew.Finances.name + 3 -> TaskCategoryNew.Studies.name + 4 -> TaskCategoryNew.Health.name + 5 -> TaskCategoryNew.Leisure.name + 6 -> TaskCategoryNew.Productivity.name + else -> TaskCategoryNew.Several.name + } Card( modifier = modifier - .padding(vertical = 12.dp) - .height(124.dp), - shape = RoundedCornerShape(8.dp), + .fillMaxWidth(), + shape = RoundedCornerShape(12.dp), border = BorderStroke(1.dp, border), colors = CardDefaults.cardColors(cardBackgroundColor) ) { Column( - modifier = modifier - .padding(horizontal = 8.dp) - .fillMaxHeight(), - verticalArrangement = Arrangement.SpaceAround - + modifier = Modifier + .padding(8.dp) + .fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(8.dp), ) { Row( - horizontalArrangement = Arrangement.Start, - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.fillMaxWidth() + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(4.dp), + verticalAlignment = Alignment.CenterVertically ) { Text( text = emoji, - fontSize = 14.sp, - modifier = Modifier.padding(end = 2.dp) + fontSize = 16.sp ) Text( - text = title, + text = taskTypeText, fontSize = 14.sp, color = categoryColor, - fontWeight = FontWeight.Bold + fontWeight = FontWeight.Medium ) } Row( - horizontalArrangement = Arrangement.SpaceEvenly, - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.fillMaxWidth() - ) { - Text( - text = description, - modifier = Modifier - .weight(1f) - .padding(start = 2.dp) - .weight(0.5f), - maxLines = 2, - style = MaterialTheme.typography.bodyLarge.copy( - textDecoration = if (isSelected) TextDecoration.LineThrough else TextDecoration.None + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.Bottom + + ) { + Column( + modifier = Modifier.weight(1f) + .fillMaxWidth() + ) { + Text( + text = "00:00", + fontSize = 14.sp, + color = Color.Black ) - ) + Text( + text = title, + fontSize = 14.sp, + color = Color.Black, + fontWeight = FontWeight.Normal, + maxLines = 2 + ) + } CustomRadioButton( - modifier = modifier.padding(start = 10.dp), selected = isSelected, - onSelected = { - onSelected(it) - }, + onSelected = onSelected, selectedIcon = painterResource(id = R.drawable.baseline_check_circle_outline_24), unselectedIcon = painterResource(id = R.drawable.baseline_radio_button_unchecked_24), colors = RadioButtonDefaults.colors( @@ -118,37 +125,35 @@ fun CardTask( selectedColor = categoryColor, disabledSelectedColor = categoryColor, disabledUnselectedColor = categoryColor - ) + ), ) } + Row( + modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.fillMaxWidth() + verticalAlignment = Alignment.CenterVertically ) { Box( modifier = Modifier - .wrapContentWidth() .background( color = categoryColor, shape = RoundedCornerShape(4.dp) ) - .padding(horizontal = 6.dp, vertical = 4.dp) - + .padding(horizontal = 12.dp, vertical = 4.dp) ) { Text( - text = categoryAttributes.originalCategoryName, - textAlign = TextAlign.Center, + text = categoryText, fontSize = 12.sp, - fontWeight = FontWeight(400), color = Color.White, + fontWeight = FontWeight.Normal ) } Icon( imageVector = Icons.Default.Edit, contentDescription = null, tint = PurpleRoutinely, - modifier = Modifier + modifier = Modifier.size(24.dp) ) } } @@ -162,19 +167,19 @@ fun CustomRadioButton( modifier: Modifier = Modifier, selectedIcon: Painter, unselectedIcon: Painter, - colors: RadioButtonColors = RadioButtonDefaults.colors() + colors: RadioButtonColors = RadioButtonDefaults.colors(), ) { IconToggleButton( checked = selected, onCheckedChange = { onSelected(!selected) }, - modifier = modifier - .width(24.dp) - .height(24.dp) - ) { + modifier = modifier.size(24.dp) + + ) { Icon( painter = if (selected) selectedIcon else unselectedIcon, contentDescription = null, - tint = if (selected) colors.selectedColor else colors.unselectedColor + tint = if (selected) colors.selectedColor else colors.unselectedColor, + modifier = modifier.fillMaxSize() ) } @@ -185,68 +190,38 @@ data class CardCategory( val cardColor: Color, val cardBorder: Color, val originalCategoryName: String, - val isSelected: Int ) - -fun darkenColor(color: Color, factor: Float = 0.8f): Color { - return Color( - red = (color.red * factor).coerceIn(0f, 1f), - green = (color.green * factor).coerceIn(0f, 1f), - blue = (color.blue * factor).coerceIn(0f, 1f), - alpha = color.alpha - ) -} -fun getCategoryAttributes(category: Int,originalCategory: Int? = null): CardCategory { +fun getCategoryAttributes(taskType: Int): CardCategory { val defaultBorderColor = Color.Gray - val darkenFactor = 0.8f - val backgroundColor = when { - category == ActivityTag.Completed.stringId && originalCategory != null -> { - val originalColor = when (originalCategory) { - ActivityTag.Task.stringId -> cardTask - ActivityTag.Habit.stringId -> cardHabit - ActivityTag.Project.stringId -> cardProject - else -> Color.Transparent - } - darkenColor(originalColor, darkenFactor) - } - category == ActivityTag.Task.stringId -> cardTask - category == ActivityTag.Habit.stringId -> cardHabit - category == ActivityTag.Project.stringId -> cardProject - category == ActivityTag.Completed.stringId -> darkenColor(Color.Red, darkenFactor) + val backgroundColor = when (taskType) { + ActivityTag.Task.stringId -> Color(0xFFD1EAFF) + ActivityTag.Habit.stringId -> Color(0xF1E0DFFF) else -> Color.Transparent } - val borderColor = when (category) { + val borderColor = when (taskType) { ActivityTag.Task.stringId -> Color(0xFF115D9E) ActivityTag.Habit.stringId -> Color(0xFF5450BC) - ActivityTag.Project.stringId -> Color(0xFF747400) - ActivityTag.Completed.stringId -> Color.Gray else -> defaultBorderColor } - val icon = when (originalCategory ?: category) { + val icon = when (taskType) { ActivityTag.Task.stringId -> "📋" ActivityTag.Habit.stringId -> "📌" - ActivityTag.Project.stringId -> "🚀" - ActivityTag.Completed.stringId -> "✔️" else -> "❓" } - val originalCategoryName = getCategoryName(category) - - return CardCategory(icon, backgroundColor, borderColor, originalCategoryName, category) - + val originalCategoryName = getCategoryName(taskType) + return CardCategory(icon, backgroundColor, borderColor, originalCategoryName) } fun getCategoryName(category: Int): String { return when (category) { - ActivityTag.Task.stringId -> "Task" - ActivityTag.Habit.stringId -> "Habit" - ActivityTag.Project.stringId -> "Project" - ActivityTag.AllActivity.stringId -> "All Activity" - else -> "Unknow" + ActivityTag.Task.stringId -> "Tarefa" + ActivityTag.Habit.stringId -> "Hábito" + else -> "Desconhecido" } } @@ -255,11 +230,11 @@ fun getCategoryName(category: Int): String { @Composable private fun CardTaskPreview() { CardTask( - title = "Title", + title = "Asa sauaygsdyu", description = "Description", isSelected = true, - originalCategory = null, + category = 4, onSelected = {}, - category = ActivityTag.Task.stringId + taskType = ActivityTag.Habit.stringId ) } diff --git a/app/src/main/java/com/routinely/routinely/ui/components/DescriptionTextField.kt b/app/src/main/java/com/routinely/routinely/ui/components/DescriptionTextField.kt index 4767ec7..05bb476 100644 --- a/app/src/main/java/com/routinely/routinely/ui/components/DescriptionTextField.kt +++ b/app/src/main/java/com/routinely/routinely/ui/components/DescriptionTextField.kt @@ -13,6 +13,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.sp import com.routinely.routinely.ui.theme.Gray80 import com.routinely.routinely.ui.theme.GrayRoutinely import com.routinely.routinely.ui.theme.PurpleRoutinely @@ -34,6 +36,7 @@ fun DescriptionTextField( label = { Text( text = labelRes, + fontSize = 16.sp, style = TextStyle(color = Color.Black) ) }, @@ -69,4 +72,10 @@ fun DescriptionTextField( modifier = Modifier .fillMaxWidth() ) +} + +@Preview (showBackground = true) +@Composable +private fun DescriptionTextFieldPreview() { + DescriptionTextField(value = "Description", onValueChange = {}, labelRes = "", error = DescriptionInputValid.Valid) } \ No newline at end of file diff --git a/app/src/main/java/com/routinely/routinely/ui/components/DropdownActivityFilter.kt b/app/src/main/java/com/routinely/routinely/ui/components/DropdownActivityFilter.kt new file mode 100644 index 0000000..d844d1c --- /dev/null +++ b/app/src/main/java/com/routinely/routinely/ui/components/DropdownActivityFilter.kt @@ -0,0 +1,129 @@ +package com.routinely.routinely.ui.components + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExposedDropdownMenuBox +import androidx.compose.material3.ExposedDropdownMenuDefaults +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.OutlinedTextFieldDefaults +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.routinely.routinely.ui.theme.GrayRoutinely +import com.routinely.routinely.ui.theme.PurpleRoutinely +import com.routinely.routinely.ui.theme.lightGray +import com.routinely.routinely.util.ActivityTag +import com.routinely.routinely.util.TaskFields + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun DropdownActivityFilter( + labelRes: Int, + onValueChange: (Int) -> Unit, + list: List, + modifier: Modifier = Modifier, + option: Int? = null +) { + var expanded by remember { mutableStateOf(false) } + + val mapStringToId: Map = list.associate { tag -> + tag.apiString to tag.stringId + } + + var selectedOptionText by remember { mutableStateOf("") } + + selectedOptionText = when { + option != null && option > 0 -> list.find { it.stringId == option }?.apiString ?: "" + else -> stringResource(labelRes) + } + + ExposedDropdownMenuBox( + modifier = modifier + .fillMaxWidth(), + expanded = expanded, + onExpandedChange = { expanded = !expanded }, + ) { + OutlinedTextField( + modifier = modifier + .menuAnchor() + .fillMaxWidth() + .clip(RoundedCornerShape(8.dp)), + readOnly = true, + value = selectedOptionText, + onValueChange = { }, + label = { + Text( + text = stringResource(labelRes), + style = TextStyle(color = PurpleRoutinely), + fontSize = 16.sp, + ) + }, + trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) }, + colors = OutlinedTextFieldDefaults.colors( + unfocusedContainerColor = lightGray, + focusedContainerColor = lightGray, + focusedTextColor = Color.Blue, + unfocusedTextColor = Color.Blue, + focusedBorderColor = GrayRoutinely, + unfocusedBorderColor = GrayRoutinely + ), + textStyle = TextStyle(color = Color.Black) + ) + + ExposedDropdownMenu( + modifier = modifier + .fillMaxWidth(), + expanded = expanded, + onDismissRequest = { expanded = false }, + ) { + mapStringToId.forEach { (string, id) -> + DropdownMenuItem( + text = { + Text( + string, + color = Color.DarkGray + ) + }, + onClick = { + selectedOptionText = string + onValueChange(id) + expanded = false + }, + contentPadding = ExposedDropdownMenuDefaults.ItemContentPadding, + enabled = true + ) + } + } + } +} + +@Preview(showBackground = true) +@Composable +private fun ActivityFilterPreview() { + var selectedActivity by remember { mutableIntStateOf(ActivityTag.Task.stringId) } + DropdownActivityFilter( + labelRes = ActivityTag.Task.stringId, + onValueChange = { newActivity -> + selectedActivity = newActivity + }, + list = listOf( + object : TaskFields(ActivityTag.Task.stringId, "Tarefa") {}, + object : TaskFields(ActivityTag.Habit.stringId, "Hábito") {} + ) + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/routinely/routinely/ui/components/DropdownRoutinely.kt b/app/src/main/java/com/routinely/routinely/ui/components/DropdownRoutinely.kt index 4f4f462..7497f95 100644 --- a/app/src/main/java/com/routinely/routinely/ui/components/DropdownRoutinely.kt +++ b/app/src/main/java/com/routinely/routinely/ui/components/DropdownRoutinely.kt @@ -1,5 +1,6 @@ package com.routinely.routinely.ui.components +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExperimentalMaterial3Api @@ -28,6 +29,9 @@ import com.routinely.routinely.ui.theme.PurpleRoutinely import com.routinely.routinely.util.TaskFields import com.routinely.routinely.util.TaskPriorities import com.routinely.routinely.util.TaskTag +import com.routinely.routinely.util.TaskCategory +import com.routinely.routinely.util.ActivityTag +import com.routinely.routinely.util.TaskCategoryNew @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -214,11 +218,32 @@ fun DropdownRoutinelyPriorities( @Preview @Composable private fun DropdownRoutinelyPreview() { - DropdownRoutinely(labelRes = R.string.label_tag_dropdown, onValueChange = {}, list = TaskFields.getAllOptions()) + Column { + DropdownRoutinely( + labelRes = R.string.label_tag_dropdown, + onValueChange = {}, + list = listOf(ActivityTag.Task, ActivityTag.Habit), + option = ActivityTag.Task.stringId + ) + + DropdownRoutinely( + labelRes = R.string.label_category_dropdown, + onValueChange = {}, + list = TaskCategory.toTaskFieldsList(), + option = 2 + ) + } } @Preview @Composable private fun DropdownRoutinelyWithColorPreview() { - DropdownRoutinelyPriorities(labelRes = R.string.label_priority_dropdown, onValueChange = {}, list = TaskPriorities.getAllTaskPriorities()) + Column { + DropdownRoutinelyPriorities( + labelRes = R.string.label_priority_dropdown, + onValueChange = {}, + list = TaskPriorities.getAllTaskPriorities(), + option = TaskPriorities.High.stringId + ) + } } \ No newline at end of file diff --git a/app/src/main/java/com/routinely/routinely/ui/components/DropdownTaksFilter.kt b/app/src/main/java/com/routinely/routinely/ui/components/DropdownTaskFilter.kt similarity index 78% rename from app/src/main/java/com/routinely/routinely/ui/components/DropdownTaksFilter.kt rename to app/src/main/java/com/routinely/routinely/ui/components/DropdownTaskFilter.kt index 2f5e1f3..b21913d 100644 --- a/app/src/main/java/com/routinely/routinely/ui/components/DropdownTaksFilter.kt +++ b/app/src/main/java/com/routinely/routinely/ui/components/DropdownTaskFilter.kt @@ -1,6 +1,5 @@ package com.routinely.routinely.ui.components - import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.DropdownMenuItem @@ -19,8 +18,6 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -30,32 +27,29 @@ import com.routinely.routinely.ui.theme.PurpleRoutinely import com.routinely.routinely.ui.theme.lightGray import com.routinely.routinely.util.ActivityTag import com.routinely.routinely.util.TaskFields -import com.routinely.routinely.util.TaskTag @OptIn(ExperimentalMaterial3Api::class) @Composable fun DropdownTaskFilter( - labelRes: Int, + label: String, onValueChange: (Int) -> Unit, list: List, modifier: Modifier = Modifier, option: Int? = null ) { var expanded by remember { mutableStateOf(false) } - val context = LocalContext.current val mapStringToId: Map = list.associate { tag -> - context.getString(tag.stringId) to tag.stringId + tag.apiString to tag.stringId } - val labelResAsString = stringResource(id = labelRes) - var selectedOptionText by remember { mutableStateOf(labelResAsString) } + var selectedOptionText by remember { mutableStateOf("") } - option?.let { - selectedOptionText = stringResource(id = option) + selectedOptionText = when { + option != null && option > 0 -> list.find { it.stringId == option }?.apiString ?: "" + else -> label } - ExposedDropdownMenuBox( modifier = modifier .fillMaxWidth(), @@ -72,7 +66,7 @@ fun DropdownTaskFilter( onValueChange = { }, label = { Text( - text = labelResAsString, + text = label, style = TextStyle(color = PurpleRoutinely), fontSize = 16.sp, ) @@ -95,20 +89,7 @@ fun DropdownTaskFilter( expanded = expanded, onDismissRequest = { expanded = false }, ) { - DropdownMenuItem( - text = { - Text( - labelResAsString, - color = Color.Blue - ) - }, - onClick = { }, - contentPadding = ExposedDropdownMenuDefaults.ItemContentPadding, - enabled = false, - ) - mapStringToId.forEach { (string, id) -> - DropdownMenuItem( text = { Text( @@ -129,17 +110,18 @@ fun DropdownTaskFilter( } } - @Preview(showBackground = true) @Composable -private fun TaskFilterRoutinelyPreview() { - var selectedTasktag by remember { mutableIntStateOf(ActivityTag.AllActivity.stringId) } +private fun TaskFilterPreview() { + var selectedTasktag by remember { mutableIntStateOf(ActivityTag.Task.stringId) } DropdownTaskFilter( - labelRes = selectedTasktag, + label = "Tarefa", onValueChange = { newTask -> selectedTasktag = newTask }, - list = TaskFields.getAllOptions() + list = listOf( + object : TaskFields(ActivityTag.Task.stringId, "Tarefa") {}, + object : TaskFields(ActivityTag.Habit.stringId, "Hábito") {} + ) ) - } \ No newline at end of file diff --git a/app/src/main/java/com/routinely/routinely/ui/components/Task.kt b/app/src/main/java/com/routinely/routinely/ui/components/Task.kt index d5755a1..11d826a 100644 --- a/app/src/main/java/com/routinely/routinely/ui/components/Task.kt +++ b/app/src/main/java/com/routinely/routinely/ui/components/Task.kt @@ -1,12 +1,75 @@ package com.routinely.routinely.ui.components +import com.routinely.routinely.util.TaskCategory +import com.routinely.routinely.util.TaskItem +import com.routinely.routinely.util.TaskType import java.time.LocalDate -data class Task ( +data class Task( val id: Int, val title: String, val description: String, - var category: Int, - var isSelected: Boolean = false, - val date: LocalDate -) \ No newline at end of file + val category: TaskCategory, + val isSelected: Boolean, + val date: LocalDate, + val type: TaskType +) { + companion object { + private fun create( + id: Int, + title: String, + description: String, + category: TaskCategory, + date: LocalDate, + type: TaskType = TaskType.Task, + isSelected: Boolean = false + ): Task { + return Task( + id = id, + title = title, + description = description, + category = category, + date = date, + type = type, + isSelected = isSelected + ) + } + + fun fromApi( + id: Int, + title: String, + description: String, + category: String, + date: LocalDate, + type: String, + isSelected: Boolean = false + ): Task { + return create( + id = id, + title = title, + description = description, + category = TaskCategory.fromString(category), + date = date, + type = TaskType.fromString(type), + isSelected = isSelected + ) + } + } + + fun copyWithSelection(isSelected: Boolean): Task { + return copy(isSelected = isSelected) + } + + fun toApiModel(): TaskItem { + return TaskItem( + id = id, + name = title, + description = description, + category = category.name, + date = date.toString(), + type = type.value, + finallyDate = null, + weekDays = emptyList() + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/routinely/routinely/ui/components/TasksViewerRoutinely.kt b/app/src/main/java/com/routinely/routinely/ui/components/TasksViewerRoutinely.kt deleted file mode 100644 index 4fb124a..0000000 --- a/app/src/main/java/com/routinely/routinely/ui/components/TasksViewerRoutinely.kt +++ /dev/null @@ -1,502 +0,0 @@ -package com.routinely.routinely.ui.components - -import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.MarqueeAnimationMode -import androidx.compose.foundation.background -import androidx.compose.foundation.basicMarquee -import androidx.compose.foundation.border -import androidx.compose.foundation.clickable -import androidx.compose.foundation.focusable -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.Arrangement -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.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.layout.width -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Checkbox -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.MutableState -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.focus.FocusManager -import androidx.compose.ui.focus.FocusRequester -import androidx.compose.ui.focus.focusRequester -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.platform.LocalFocusManager -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.res.vectorResource -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.text.style.TextDecoration -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import com.routinely.routinely.R -import com.routinely.routinely.data.auth.model.ApiResponseWithData -import com.routinely.routinely.ui.theme.PurpleRoutinely -import com.routinely.routinely.ui.theme.SecondaryYellowRoutinely -import com.routinely.routinely.ui.theme.textGrayColor -import com.routinely.routinely.util.TaskCategory -import com.routinely.routinely.util.TaskItem -import com.routinely.routinely.util.TaskPriorities -import com.routinely.routinely.util.TaskTag - -@Composable -fun TasksViewerRoutinely( - getTasksResponse: ApiResponseWithData>, - listOfConcludedTaskItems: List, - onEditButtonClicked: (taskItem: TaskItem) -> Unit, - onDeleteButtonClicked: (taskItem: TaskItem) -> Unit, -) { - - Column( - modifier = Modifier - .fillMaxWidth() - .padding(top = 12.dp, bottom = 24.dp, start = 12.dp, end = 12.dp) - .border( - width = 1.dp, - color = PurpleRoutinely, - shape = RoundedCornerShape(10.dp) - ), - ) { - Text( - text = stringResource(R.string.label_tasks_viewer), - fontSize = 16.sp, - fontWeight = FontWeight.SemiBold, - color = PurpleRoutinely, - modifier = Modifier - .padding(top = 16.dp, bottom = 6.dp, end = 16.dp, start = 16.dp) - ) - Column( - Modifier - .fillMaxWidth() - .padding(bottom = 12.dp), - ) { - when (getTasksResponse) { - is ApiResponseWithData.Success -> { - val data = getTasksResponse.data!! - for (item in data) { - TaskItem(item, onEditButtonClicked, onDeleteButtonClicked) - } - } - - is ApiResponseWithData.Error -> { - EmptyDataLayout() - } - - is ApiResponseWithData.EmptyData -> { - EmptyDataLayout() - } - - is ApiResponseWithData.Loading -> { - Box( - modifier = Modifier.fillMaxSize(), - contentAlignment = Alignment.Center - - ) { - IndeterminateCircularIndicator() - } - } - - else -> { - EmptyDataLayout() - } - } - } - - - Spacer( - modifier = Modifier - .border(1.dp, PurpleRoutinely) - .fillMaxWidth() - .height(1.dp) - ) - - Text( - text = stringResource(R.string.label_completed_tasks), - fontSize = 16.sp, - fontWeight = FontWeight.SemiBold, - color = PurpleRoutinely, - modifier = Modifier - .padding(top = 12.dp, bottom = 6.dp, end = 16.dp, start = 16.dp) - ) - Column( - Modifier - .fillMaxWidth() - .padding(bottom = 12.dp), - ) { - if(listOfConcludedTaskItems.isEmpty()) { - Column( - modifier = Modifier.padding( - horizontal = 16.dp - ) - ) { - Text( - text = stringResource(id = R.string.no_tasks_completed), - fontSize = 16.sp, - color = textGrayColor, - ) - } - } else { - for (item in listOfConcludedTaskItems) { - ConcludedTaskItem(item) - } - } - } - } -} - -@Composable -private fun EmptyDataLayout() { - Column( - modifier = Modifier.padding( - horizontal = 16.dp - ) - ) { - Text( - text = stringResource(id = R.string.no_tasks_available), - fontSize = 16.sp, - color = textGrayColor, - ) - } -} - -@Composable -private fun TaskItem( - taskItem: TaskItem, - onEditButtonClicked: (taskItem: TaskItem) -> Unit, - onDeleteButtonClicked: (taskItem: TaskItem) -> Unit -) { - val focusRequester = remember { FocusRequester() } - val focusManager = LocalFocusManager.current - val isFocused = remember { mutableStateOf(false) } - val checkedState = remember { mutableStateOf(false) } - val interactionSource = remember { MutableInteractionSource() } - - Row( - Modifier - .fillMaxWidth() - .clickable( - interactionSource = interactionSource, - indication = null, - ) { - if (!isFocused.value) { - focusRequester.requestFocus() - } else { - focusManager.clearFocus() - } - isFocused.value = !isFocused.value - }, - - ) { - CheckButtonTasks(checkedState, focusManager, focusRequester) - - TextWithMarquee(taskItem.name, focusRequester) - - Row( - verticalAlignment = Alignment.CenterVertically - ){ - CategoryItem(taskItem.category) - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.Center, - modifier = Modifier.fillMaxWidth() - ) { - IconButton( - onClick = { }, - enabled = false, - ) { - Icon( - imageVector = ImageVector.vectorResource(id = taskItem.priority.icon), - contentDescription = stringResource(id = taskItem.priority.contentDescription), - tint = Color.Unspecified, - ) - } - - ActionsForTask( - onEditButtonClicked = { - onEditButtonClicked(taskItem) - }, - onDeleteButtonClicked = { - onDeleteButtonClicked(taskItem) - } - ) - } - } - } -} - -@Composable -private fun ConcludedTaskItem( - taskItem: TaskItem -) { - val focusRequester = remember { FocusRequester() } - val focusManager = LocalFocusManager.current - val isFocused = remember { mutableStateOf(false) } - val interactionSource = remember { MutableInteractionSource() } - - Row( - Modifier - .fillMaxWidth() - .clickable( - interactionSource = interactionSource, - indication = null, - ) { - if (!isFocused.value) { - focusRequester.requestFocus() - } else { - focusManager.clearFocus() - } - isFocused.value = !isFocused.value - }, - - ) { - CheckButtonConcludedTask(focusManager, focusRequester) - - TextConcludedWithMarquee(taskItem.name, focusRequester) - - Row( - verticalAlignment = Alignment.CenterVertically - ) { - CategoryItem(category = taskItem.category) - - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.Start, - modifier = Modifier.fillMaxWidth() - ) { - IconButton( - onClick = { }, - enabled = false, - ) { - Icon( - imageVector = ImageVector.vectorResource(id = taskItem.priority.icon), - contentDescription = stringResource(id = taskItem.priority.contentDescription), - tint = Color.Unspecified, - ) - } - } - } - } -} - -@Composable -private fun CategoryItem(category: TaskCategory) { - Column( - modifier = Modifier - .padding(start = 12.dp, end = 6.dp) - .background(SecondaryYellowRoutinely, shape = RoundedCornerShape(20)) - .width(70.dp), - horizontalAlignment = Alignment.CenterHorizontally - ) { - Text( - text = stringResource(category.stringId), - fontSize = 12.sp, - modifier = Modifier - .padding(horizontal = 6.dp, vertical = 8.dp), - textAlign = TextAlign.Center - ) - } -} - -@Composable -private fun CategoryTask(category: TaskCategory) { - -} - -@Composable -private fun ActionsForTask( - onEditButtonClicked: () -> Unit, - onDeleteButtonClicked: () -> Unit -) { - IconButton( - onClick = { onEditButtonClicked() } - ) { - Icon( - imageVector = ImageVector.vectorResource(id = R.drawable.ic_edit), - contentDescription = stringResource(id = R.string.desc_high_priority), - tint = Color.Unspecified - ) - } - - IconButton( - onClick = { onDeleteButtonClicked() } - ) { - Icon( - imageVector = ImageVector.vectorResource(id = R.drawable.ic_trash), - contentDescription = stringResource(id = R.string.desc_high_priority), - tint = Color.Unspecified - ) - } -} - -@Composable -private fun RowScope.CheckButtonTasks( - checkedState: MutableState, - focusManager: FocusManager, - focusRequester: FocusRequester -) { - Checkbox( - checked = checkedState.value, - /** - * Is this correctly? I don't know, - * but at the moment I'm clearing the focus here to stop basicMarquee from text... - * when user click on CheckBox as well - */ - onCheckedChange = { - focusManager.clearFocus() - checkedState.value = it - }, - modifier = Modifier - .align(Alignment.CenterVertically) - .focusable() - .focusRequester(focusRequester), - ) -} - -@Composable -private fun RowScope.CheckButtonConcludedTask( - focusManager: FocusManager, - focusRequester: FocusRequester -) { - Checkbox( - checked = true, - onCheckedChange = { - focusManager.clearFocus() - }, - modifier = Modifier - .align(Alignment.CenterVertically) - .focusable() - .focusRequester(focusRequester), - enabled = false - ) -} - -@Composable -@OptIn(ExperimentalFoundationApi::class) -private fun RowScope.TextWithMarquee(text: String, focusRequester: FocusRequester) { - Text( - text = text, - /** - * basicMarquee will start once the user click on the text - * It will also stop if the user click again. - * If the user clicks on any component other than this line, it will not interrupt basicMarquee... - * I can't make the background takes the focus - */ - modifier = Modifier - .fillMaxWidth(0.33f) - .align(Alignment.CenterVertically) - .basicMarquee( - animationMode = MarqueeAnimationMode.WhileFocused, - velocity = 40.dp, - delayMillis = 1500 - ) - .focusRequester(focusRequester = focusRequester) - .focusable(), - fontSize = 14.sp, - overflow = TextOverflow.Ellipsis, - maxLines = 1, - ) -} - -@Composable -@OptIn(ExperimentalFoundationApi::class) -private fun RowScope.TextConcludedWithMarquee(text: String, focusRequester: FocusRequester) { - Text( - text = text, - modifier = Modifier - .fillMaxWidth(0.33f) - .align(Alignment.CenterVertically) - .basicMarquee( - animationMode = MarqueeAnimationMode.WhileFocused, - velocity = 40.dp, - delayMillis = 1500 - ) - .focusRequester(focusRequester = focusRequester) - .focusable(), - fontSize = 14.sp, - overflow = TextOverflow.Ellipsis, - maxLines = 1, - textDecoration = TextDecoration.LineThrough, - color = Color.Gray - ) -} -@Composable -@Preview (showBackground = true) -fun PreviewTasksViewerRoutinely() { - val mockTasks = listOf( - TaskItem( - id = 1, - name = "Task 12", - date = "2024-05-11", - hour = "10:00", - tag = TaskTag.Candidacy, - priority = TaskPriorities.High, - category = TaskCategory.Career, - description = "Description of Task 1" - ), - TaskItem( - id = 2, - name = "Task 2", - date = "2024-05-12", - hour = "14:00", - tag = TaskTag.Bill, - priority = TaskPriorities.Medium, - category = TaskCategory.Personal, - description = "Description of Task 2" - ), - TaskItem( - id = 3, - name = "Task 3", - date = "2024-05-13", - hour = "16:00", - tag = TaskTag.Exercise, - priority = TaskPriorities.Low, - category = TaskCategory.Studies, - description = "Description of Task 3" - ) - ) - - val mockCompletedTasks = listOf( - TaskItem( - id = 4, - name = "Completed Task 1", - date = "2024-05-10", - hour = "12:00", - tag = TaskTag.Beauty, - priority = TaskPriorities.High, - category = TaskCategory.Health, - description = "Description of Completed Task 1" - ), - TaskItem( - id = 5, - name = "Completed Task 2", - date = "2024-05-09", - hour = "09:00", - tag = TaskTag.Literature, - priority = TaskPriorities.Medium, - category = TaskCategory.Finances, - description = "Description of Completed Task 2" - ) - ) - TasksViewerRoutinely( - getTasksResponse = ApiResponseWithData.Success(mockTasks), - listOfConcludedTaskItems = mockCompletedTasks, - onEditButtonClicked = { taskItem -> /* Handle edit button click */ }, - onDeleteButtonClicked = { taskItem -> /* Handle delete button click */ } - ) -} diff --git a/app/src/main/java/com/routinely/routinely/util/ActivityTag.kt b/app/src/main/java/com/routinely/routinely/util/ActivityTag.kt index ee66852..b6ae282 100644 --- a/app/src/main/java/com/routinely/routinely/util/ActivityTag.kt +++ b/app/src/main/java/com/routinely/routinely/util/ActivityTag.kt @@ -1,22 +1,24 @@ package com.routinely.routinely.util +import android.os.Parcelable import com.routinely.routinely.R import kotlinx.parcelize.Parcelize sealed class ActivityTag(stringId: Int, apiString: String) : TaskFields(stringId, apiString) { @Parcelize - data object Task : ActivityTag(R.string.text_tag_activity, "Activity") + data object Task : ActivityTag(R.string.text_tag_task, "Tarefa"), Parcelable @Parcelize - data object Habit : ActivityTag(R.string.text_tag_habit, "Habit") + data object Habit : ActivityTag(R.string.text_tag_habit, "Hábito"), Parcelable - @Parcelize - data object Project : ActivityTag(R.string.text_tag_project, "Project") - - @Parcelize - data object AllActivity : ActivityTag(R.string.text_tag_activities, "All_Activity") - - @Parcelize - data object Completed : ActivityTag(R.string.text_tag_completed, "Completed") + companion object { + fun fromId(id: Int): ActivityTag? { + return when (id) { + Task.stringId -> Task + Habit.stringId -> Habit + else -> null + } + } + } } \ No newline at end of file diff --git a/app/src/main/java/com/routinely/routinely/util/TaskCategory.kt b/app/src/main/java/com/routinely/routinely/util/TaskCategory.kt index 9f054fe..b4ad6ba 100644 --- a/app/src/main/java/com/routinely/routinely/util/TaskCategory.kt +++ b/app/src/main/java/com/routinely/routinely/util/TaskCategory.kt @@ -1,17 +1,34 @@ package com.routinely.routinely.util -import com.routinely.routinely.R -import kotlinx.parcelize.Parcelize +enum class TaskCategory(val id: Int) { + CAREER(1), + FINANCE(2), + STUDIES(3), + HEALTH(4), + LEISURE(5), + PRODUCTIVITY(6), + SEVERAL(7); -sealed class TaskCategory(stringId: Int, apiString: String) : TaskFields(stringId, apiString) { - @Parcelize - data object Career : TaskCategory(R.string.category_career_text, "career") - @Parcelize - data object Personal : TaskCategory(R.string.category_personal_text, "personal") - @Parcelize - data object Health : TaskCategory(R.string.category_health_text, "health") - @Parcelize - data object Finances : TaskCategory(R.string.category_finances_text, "finance") - @Parcelize - data object Studies : TaskCategory(R.string.category_studies_text, "study") -} \ No newline at end of file + val apiName: String + get() = name.lowercase().replaceFirstChar { it.uppercase() } + + companion object { + fun fromString(value: String): TaskCategory { + return try { + valueOf(value.uppercase()) + } catch (e: IllegalArgumentException) { + SEVERAL + } + } + + fun fromId(id: Int): TaskCategory { + return entries.find { it.id == id } ?: SEVERAL + } + + fun toTaskFieldsList(): List { + return entries.map { category -> + object : TaskFields(category.id, category.apiName) {} + } + } + } +} diff --git a/app/src/main/java/com/routinely/routinely/util/TaskCateogry.kt b/app/src/main/java/com/routinely/routinely/util/TaskCateogry.kt new file mode 100644 index 0000000..e1ff84c --- /dev/null +++ b/app/src/main/java/com/routinely/routinely/util/TaskCateogry.kt @@ -0,0 +1,11 @@ +package com.routinely.routinely.util + +enum class TaskCategoryNew { + Career, + Finances, + Studies, + Health, + Leisure, + Productivity, + Several; +} \ No newline at end of file diff --git a/app/src/main/java/com/routinely/routinely/util/TaskFields.kt b/app/src/main/java/com/routinely/routinely/util/TaskFields.kt index 20d8fd2..e616278 100644 --- a/app/src/main/java/com/routinely/routinely/util/TaskFields.kt +++ b/app/src/main/java/com/routinely/routinely/util/TaskFields.kt @@ -1,14 +1,16 @@ package com.routinely.routinely.util import android.os.Parcelable +import kotlinx.parcelize.Parcelize -sealed class TaskFields(var stringId: Int, val apiString: String) : Parcelable { +@Parcelize +open class TaskFields(val stringId: Int, val apiString: String) : Parcelable { companion object { /** * Return all stringId from subclass */ inline fun allStringIds(): List { - return T::class.sealedSubclasses.map { it.objectInstance!!.stringId } + return T::class.sealedSubclasses.map { it.objectInstance?.stringId ?: 0 } } /** @@ -17,7 +19,8 @@ sealed class TaskFields(var stringId: Int, val apiString: String) : Parcelable { inline fun getStringIdByApiString(apiString: String): Int? { return T::class.sealedSubclasses .mapNotNull { it.objectInstance } - .find { it.apiString == apiString }?.stringId + .find { it.apiString == apiString } + ?.stringId } /** @@ -26,15 +29,15 @@ sealed class TaskFields(var stringId: Int, val apiString: String) : Parcelable { inline fun getTaskFieldByStringId(stringId: Int): T { return T::class.sealedSubclasses .mapNotNull { it.objectInstance } - .find { it.stringId == stringId }!! + .find { it.stringId == stringId } + ?: throw IllegalArgumentException("No TaskField found with stringId: $stringId") } /** * Get all options of T : TaskFields */ inline fun getAllOptions(): List { - return T::class.sealedSubclasses - .mapNotNull { it.objectInstance } + return T::class.sealedSubclasses.mapNotNull { it.objectInstance } } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/routinely/routinely/util/TaskItem.kt b/app/src/main/java/com/routinely/routinely/util/TaskItem.kt index 6d30c24..8aebe5a 100644 --- a/app/src/main/java/com/routinely/routinely/util/TaskItem.kt +++ b/app/src/main/java/com/routinely/routinely/util/TaskItem.kt @@ -1,27 +1,19 @@ package com.routinely.routinely.util import android.os.Parcelable +import com.routinely.routinely.ui.components.Task import kotlinx.parcelize.Parcelize +import java.time.LocalDate @Parcelize data class TaskItem( - val id: Int, - var name: String, - var date: String, - var hour: String, - var tag: TaskTag, - var priority: TaskPriorities, - var category: TaskCategory, - var description: String -) : Parcelable - -data class TaskItemRemote( val id: Int, val name: String, - val date: String, - val hour: String, - val tag: String, - val priority: String, + val description: String?, val category: String, - val description: String -) \ No newline at end of file + val date: String, + val type: String, + val finallyDate: String?, + val weekDays: List, +) : Parcelable + diff --git a/app/src/main/java/com/routinely/routinely/util/TaskItemRemote.kt b/app/src/main/java/com/routinely/routinely/util/TaskItemRemote.kt new file mode 100644 index 0000000..b39c71c --- /dev/null +++ b/app/src/main/java/com/routinely/routinely/util/TaskItemRemote.kt @@ -0,0 +1,22 @@ +package com.routinely.routinely.util + +import kotlinx.serialization.Serializable + +@Serializable +data class TaskListResponse( + val count: Int, + val tasks: List +) + +@Serializable +data class TaskItemRemote( + val id: Int, + val name: String, + val description: String, + val date: String, + val category: String, + val checked: Boolean, + val finallyDate: String, + val weekDays: List, + val type: String, +) \ No newline at end of file diff --git a/app/src/main/java/com/routinely/routinely/util/TaskMapper.kt b/app/src/main/java/com/routinely/routinely/util/TaskMapper.kt new file mode 100644 index 0000000..c87858c --- /dev/null +++ b/app/src/main/java/com/routinely/routinely/util/TaskMapper.kt @@ -0,0 +1,26 @@ +package com.routinely.routinely.util + +import com.routinely.routinely.ui.components.Task +import java.time.LocalDate +import java.time.format.DateTimeFormatter + +class TaskMapper { + companion object { + private val dateFormatter = DateTimeFormatter.ISO_LOCAL_DATE + + fun fromApi(taskItem: TaskItem): Task? { + return try { + Task.fromApi( + id = taskItem.id, + title = taskItem.name, + description = taskItem.description ?: "", + category = taskItem.category, + date = LocalDate.parse(taskItem.date, dateFormatter), + type = taskItem.type + ) + } catch (e: Exception) { + null + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/routinely/routinely/util/TaskType.kt b/app/src/main/java/com/routinely/routinely/util/TaskType.kt new file mode 100644 index 0000000..fd90fd2 --- /dev/null +++ b/app/src/main/java/com/routinely/routinely/util/TaskType.kt @@ -0,0 +1,18 @@ +package com.routinely.routinely.util + +sealed class TaskType(val value: String) { + data object Task : TaskType("task") + data object Habit : TaskType("habit") + data object Project : TaskType("project") + + companion object { + fun fromString(value: String): TaskType { + return when (value.lowercase()) { + "task" -> Task + "habit" -> Habit + "project" -> Project + else -> Task + } + } + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/baseline_keyboard_arrow_down_24.xml b/app/src/main/res/drawable/baseline_keyboard_arrow_down_24.xml new file mode 100644 index 0000000..128f2f6 --- /dev/null +++ b/app/src/main/res/drawable/baseline_keyboard_arrow_down_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/baseline_keyboard_arrow_left_24.xml b/app/src/main/res/drawable/baseline_keyboard_arrow_left_24.xml new file mode 100644 index 0000000..8a2ea6d --- /dev/null +++ b/app/src/main/res/drawable/baseline_keyboard_arrow_left_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/baseline_keyboard_arrow_right_24.xml b/app/src/main/res/drawable/baseline_keyboard_arrow_right_24.xml new file mode 100644 index 0000000..b857edc --- /dev/null +++ b/app/src/main/res/drawable/baseline_keyboard_arrow_right_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/baseline_keyboard_arrow_up_24.xml b/app/src/main/res/drawable/baseline_keyboard_arrow_up_24.xml new file mode 100644 index 0000000..4b82d8b --- /dev/null +++ b/app/src/main/res/drawable/baseline_keyboard_arrow_up_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 892924c..da7e775 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -5,7 +5,7 @@ Botão Home de Navegação Início - Nova tarefa + Nova atividade Top Bar Prioridade urgente @@ -80,7 +80,7 @@ Conta Projeto Hábito - Atividade + Tarefa bill Categorias Tags @@ -122,4 +122,7 @@ O código de verificação que você inseriu não é válido. Verifique o código e tente novamente. Todas as atividades Concluídas + Lazer + Produtividade + Diversos diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9bac1d3..f3d9ccc 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -82,7 +82,7 @@ Bill Project Habit - Activity + Task bill Categories Tags @@ -124,4 +124,9 @@ Password reset completed successfully The verification code you entered is not valid. Check the code and try again. All activities + Leisure + Productivity + Several + Invalid Type + Invalid category. From bcd96fb63b1c8e5231a6bf42967e58bd14e37c22 Mon Sep 17 00:00:00 2001 From: CrozCamel Date: Mon, 7 Apr 2025 21:30:18 -0300 Subject: [PATCH 2/7] =?UTF-8?q?Altera=C3=A7=C3=B5es=20para=20resolver=20os?= =?UTF-8?q?=20conflitos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../routinely/routinely/home/HomeScreen.kt | 3 +-- .../routinely/navigation/SetupNavGraph.kt | 8 ++------ .../routinely/ui/components/CardTask.kt | 3 +-- .../ui/components/DropdownTaskFilter.kt | 2 +- .../routinely/routinely/ui/components/Task.kt | 20 +------------------ .../routinely/routinely/util/ActivityTag.kt | 2 +- app/src/main/res/values-pt-rBR/strings.xml | 2 ++ 7 files changed, 9 insertions(+), 31 deletions(-) diff --git a/app/src/main/java/com/routinely/routinely/home/HomeScreen.kt b/app/src/main/java/com/routinely/routinely/home/HomeScreen.kt index 2f21dbd..19a7b93 100644 --- a/app/src/main/java/com/routinely/routinely/home/HomeScreen.kt +++ b/app/src/main/java/com/routinely/routinely/home/HomeScreen.kt @@ -1,7 +1,6 @@ package com.routinely.routinely.home import androidx.compose.foundation.layout.Column -import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items @@ -66,7 +65,7 @@ fun HomeScreen( var expanded by remember { mutableStateOf(false) } var showDeleteDialog by rememberSaveable { mutableStateOf(false) } - var temporaryDeleteId by remember { mutableStateOf(null) } + val temporaryDeleteId by remember { mutableStateOf(null) } var selectedActivityTag by remember { mutableIntStateOf(ActivityTag.Task.stringId) } var selectedDate by remember { mutableStateOf(null) } diff --git a/app/src/main/java/com/routinely/routinely/navigation/SetupNavGraph.kt b/app/src/main/java/com/routinely/routinely/navigation/SetupNavGraph.kt index 8e0f9b0..fdf06dd 100644 --- a/app/src/main/java/com/routinely/routinely/navigation/SetupNavGraph.kt +++ b/app/src/main/java/com/routinely/routinely/navigation/SetupNavGraph.kt @@ -1,6 +1,5 @@ package com.routinely.routinely.navigation -import android.util.Log import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable @@ -44,13 +43,10 @@ import com.routinely.routinely.task.AddTaskViewModel import com.routinely.routinely.task.EditTaskScreen import com.routinely.routinely.task.EditTaskViewModel import com.routinely.routinely.ui.components.IndeterminateCircularIndicator -import com.routinely.routinely.ui.components.Task -import com.routinely.routinely.util.ActivityTag import com.routinely.routinely.util.MenuItem import com.routinely.routinely.util.TaskItem import com.routinely.routinely.util.TaskMapper import org.koin.androidx.compose.koinViewModel -import java.time.LocalDate @Composable @@ -339,7 +335,7 @@ fun NavGraphBuilder.homeScreenRoute( navigateToLoginScreen: () -> Unit, navigateToEditScreen: (Int) -> Unit, ) { - composable(route = Screen.HomeScreen.route) { navBackStackEntry -> + composable(route = Screen.HomeScreen.route) { _ -> val viewModel: HomeViewModel = koinViewModel() val getTasksResponse by viewModel.getTasksResponse.collectAsStateWithLifecycle() val deleteTaskResponse by viewModel.deleteTaskResponse.collectAsState() @@ -559,4 +555,4 @@ private fun navigateToScreenOnlyIfResumed( if (navBackStackEntry.lifecycleIsresumed()) { navigateToHomeScreen() } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/routinely/routinely/ui/components/CardTask.kt b/app/src/main/java/com/routinely/routinely/ui/components/CardTask.kt index ec5cef3..64137c4 100644 --- a/app/src/main/java/com/routinely/routinely/ui/components/CardTask.kt +++ b/app/src/main/java/com/routinely/routinely/ui/components/CardTask.kt @@ -225,12 +225,11 @@ fun getCategoryName(category: Int): String { } } - @Preview(showBackground = true) @Composable private fun CardTaskPreview() { CardTask( - title = "Asa sauaygsdyu", + title = "Titulo", description = "Description", isSelected = true, category = 4, diff --git a/app/src/main/java/com/routinely/routinely/ui/components/DropdownTaskFilter.kt b/app/src/main/java/com/routinely/routinely/ui/components/DropdownTaskFilter.kt index b21913d..2fc2bb5 100644 --- a/app/src/main/java/com/routinely/routinely/ui/components/DropdownTaskFilter.kt +++ b/app/src/main/java/com/routinely/routinely/ui/components/DropdownTaskFilter.kt @@ -124,4 +124,4 @@ private fun TaskFilterPreview() { object : TaskFields(ActivityTag.Habit.stringId, "Hábito") {} ) ) -} \ No newline at end of file +} diff --git a/app/src/main/java/com/routinely/routinely/ui/components/Task.kt b/app/src/main/java/com/routinely/routinely/ui/components/Task.kt index 11d826a..29dfd20 100644 --- a/app/src/main/java/com/routinely/routinely/ui/components/Task.kt +++ b/app/src/main/java/com/routinely/routinely/ui/components/Task.kt @@ -1,7 +1,6 @@ package com.routinely.routinely.ui.components import com.routinely.routinely.util.TaskCategory -import com.routinely.routinely.util.TaskItem import com.routinely.routinely.util.TaskType import java.time.LocalDate @@ -55,21 +54,4 @@ data class Task( ) } } - - fun copyWithSelection(isSelected: Boolean): Task { - return copy(isSelected = isSelected) - } - - fun toApiModel(): TaskItem { - return TaskItem( - id = id, - name = title, - description = description, - category = category.name, - date = date.toString(), - type = type.value, - finallyDate = null, - weekDays = emptyList() - ) - } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/routinely/routinely/util/ActivityTag.kt b/app/src/main/java/com/routinely/routinely/util/ActivityTag.kt index b6ae282..482a975 100644 --- a/app/src/main/java/com/routinely/routinely/util/ActivityTag.kt +++ b/app/src/main/java/com/routinely/routinely/util/ActivityTag.kt @@ -21,4 +21,4 @@ sealed class ActivityTag(stringId: Int, apiString: String) : TaskFields(stringId } } } -} \ No newline at end of file +} diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index da7e775..ae05ee2 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -125,4 +125,6 @@ Lazer Produtividade Diversos + Tipo invalido. + Categoria invalida. From e8ad5071100c02df142aebdeec5ee336d9ceeac7 Mon Sep 17 00:00:00 2001 From: CrozCamel Date: Tue, 8 Apr 2025 19:34:07 -0300 Subject: [PATCH 3/7] Tentnaod resolver o conflito na String --- app/src/main/res/values-pt-rBR/strings.xml | 1 - app/src/main/res/values/strings.xml | 1 - 2 files changed, 2 deletions(-) diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index ae05ee2..6552d3a 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -92,7 +92,6 @@ Todas as atividades Editar tarefa Continuar com e-mail e senha - Criar conta Já possui uma conta? Entrar Confirma diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f3d9ccc..00e1a56 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -95,7 +95,6 @@ All activities Edit Task Continue with email and password - Create Account Already have an account? Login Confirm From a169874513fdebf55dccb8ab64c6ae7af2d9c24c Mon Sep 17 00:00:00 2001 From: CrozCamel Date: Sat, 5 Apr 2025 18:40:00 -0300 Subject: [PATCH 4/7] =?UTF-8?q?refatora=C3=A7=C3=A3o=20das=20task?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit modificação das respostas das atividades para a nova versão; --- .../routinely/data/auth/api/AuthApiImpl.kt | 17 +- .../routinely/data/auth/model/TaskRequest.kt | 13 +- .../routinely/data/task/api/TaskApiImpl.kt | 53 +- .../data/task/extensions/taskExtensions.kt | 139 +++-- .../routinely/routinely/home/HomeScreen.kt | 171 +++--- .../routinely/routinely/home/HomeViewModel.kt | 56 -- .../routinely/navigation/SetupNavGraph.kt | 101 ++-- .../routinely/routinely/task/AddTaskScreen.kt | 153 ++++-- .../routinely/task/AddTaskViewModel.kt | 34 +- .../task/DropdownWeeklyFrequencyScreen.kt | 339 ++++++++++++ .../routinely/task/EditTaskScreen.kt | 101 ++-- .../routinely/task/EditTaskViewModel.kt | 33 +- .../routinely/ui/components/CardTask.kt | 245 ++++----- .../ui/components/DescriptionTextField.kt | 9 + .../ui/components/DropdownActivityFilter.kt | 129 +++++ .../ui/components/DropdownRoutinely.kt | 29 +- ...ownTaksFilter.kt => DropdownTaskFilter.kt} | 43 +- .../routinely/routinely/ui/components/Task.kt | 75 ++- .../ui/components/TasksViewerRoutinely.kt | 502 ------------------ .../routinely/routinely/util/ActivityTag.kt | 27 +- .../routinely/routinely/util/TaskCategory.kt | 45 +- .../routinely/routinely/util/TaskCateogry.kt | 11 + .../routinely/routinely/util/TaskFields.kt | 17 +- .../com/routinely/routinely/util/TaskItem.kt | 26 +- .../routinely/util/TaskItemRemote.kt | 22 + .../routinely/routinely/util/TaskMapper.kt | 26 + .../com/routinely/routinely/util/TaskType.kt | 18 + .../baseline_keyboard_arrow_down_24.xml | 5 + .../baseline_keyboard_arrow_left_24.xml | 5 + .../baseline_keyboard_arrow_right_24.xml | 5 + .../baseline_keyboard_arrow_up_24.xml | 5 + app/src/main/res/values-pt-rBR/strings.xml | 11 +- app/src/main/res/values/strings.xml | 9 +- 33 files changed, 1272 insertions(+), 1202 deletions(-) create mode 100644 app/src/main/java/com/routinely/routinely/task/DropdownWeeklyFrequencyScreen.kt create mode 100644 app/src/main/java/com/routinely/routinely/ui/components/DropdownActivityFilter.kt rename app/src/main/java/com/routinely/routinely/ui/components/{DropdownTaksFilter.kt => DropdownTaskFilter.kt} (79%) delete mode 100644 app/src/main/java/com/routinely/routinely/ui/components/TasksViewerRoutinely.kt create mode 100644 app/src/main/java/com/routinely/routinely/util/TaskCateogry.kt create mode 100644 app/src/main/java/com/routinely/routinely/util/TaskItemRemote.kt create mode 100644 app/src/main/java/com/routinely/routinely/util/TaskMapper.kt create mode 100644 app/src/main/java/com/routinely/routinely/util/TaskType.kt create mode 100644 app/src/main/res/drawable/baseline_keyboard_arrow_down_24.xml create mode 100644 app/src/main/res/drawable/baseline_keyboard_arrow_left_24.xml create mode 100644 app/src/main/res/drawable/baseline_keyboard_arrow_right_24.xml create mode 100644 app/src/main/res/drawable/baseline_keyboard_arrow_up_24.xml diff --git a/app/src/main/java/com/routinely/routinely/data/auth/api/AuthApiImpl.kt b/app/src/main/java/com/routinely/routinely/data/auth/api/AuthApiImpl.kt index 4f22736..0974792 100644 --- a/app/src/main/java/com/routinely/routinely/data/auth/api/AuthApiImpl.kt +++ b/app/src/main/java/com/routinely/routinely/data/auth/api/AuthApiImpl.kt @@ -27,6 +27,7 @@ import io.ktor.client.statement.HttpResponse import io.ktor.http.ContentType import io.ktor.http.HttpStatusCode import io.ktor.http.contentType +import timber.log.Timber internal class AuthApiImpl( private val client: HttpClient @@ -51,9 +52,9 @@ internal class AuthApiImpl( contentType(ContentType.Application.Json) }.toSignInResult() } catch(e: ResponseException){ - handleSignInErrorResponse(e.response.status) + handleSignInError(e.response.status) } catch(e: Exception){ - handleSignInErrorResponse(HttpStatusCode(900, e.message ?: "Unknown Exception")) + handleSignInError(HttpStatusCode(900, e.message ?: "Unknown Exception")) } } @@ -104,27 +105,25 @@ internal class AuthApiImpl( } } - private fun handleSignInErrorResponse(httpStatusCode: HttpStatusCode): SignInResult { - println("Error SignIn: ${httpStatusCode.description}") - return SignInResult.DefaultError + private fun handleSignInError(httpStatusCode: HttpStatusCode): SignInResult { + return when (httpStatusCode) { + HttpStatusCode.Unauthorized -> SignInResult.DefaultError + else -> SignInResult.DefaultError + } } private fun handleCreateAccountErrorResponse(httpStatusCode: HttpStatusCode): CreateAccountResult { - println("Error SignIn: ${httpStatusCode.description}") return CreateAccountResult.Error(R.string.api_unexpected_error) } private fun handleForgotPasswordError(httpStatusCode: HttpStatusCode): ForgotPasswordResult { - println("Error: ${httpStatusCode.description}") return ForgotPasswordResult.DefaultError } private fun handleValidateCodeError(httpStatusCode: HttpStatusCode): ValidateCodeResult { - println("Error: ${httpStatusCode.description}") return ValidateCodeResult.DefaultError } private fun handleCreateNewPasswordError(httpStatusCode: HttpStatusCode): CreateNewPasswordResult { - println("Error: ${httpStatusCode.description}") return CreateNewPasswordResult.DefaultError } } diff --git a/app/src/main/java/com/routinely/routinely/data/auth/model/TaskRequest.kt b/app/src/main/java/com/routinely/routinely/data/auth/model/TaskRequest.kt index 8bc95ae..9a839b6 100644 --- a/app/src/main/java/com/routinely/routinely/data/auth/model/TaskRequest.kt +++ b/app/src/main/java/com/routinely/routinely/data/auth/model/TaskRequest.kt @@ -1,11 +1,14 @@ package com.routinely.routinely.data.auth.model +import kotlinx.serialization.Serializable + +@Serializable data class TaskRequest( val name: String, - val date: String, - val hour: String, val description: String, - val priority: String, - val tag: String, - val category: String + val date: String, + val category: String, + val finallyDate: String, + val weekDays: List, + val type: String, ) diff --git a/app/src/main/java/com/routinely/routinely/data/task/api/TaskApiImpl.kt b/app/src/main/java/com/routinely/routinely/data/task/api/TaskApiImpl.kt index 832fdbe..a20fedd 100644 --- a/app/src/main/java/com/routinely/routinely/data/task/api/TaskApiImpl.kt +++ b/app/src/main/java/com/routinely/routinely/data/task/api/TaskApiImpl.kt @@ -32,25 +32,26 @@ internal class TaskApiImpl( ) : TaskApi { override suspend fun addTask(taskRequest: TaskRequest): ApiResponse { return try { - client.post(HttpRoutes.TASK) { + val response = client.post(HttpRoutes.TASK) { setBody(taskRequest) contentType(ContentType.Application.Json) - }.taskToApiResponse() - } catch(e: RedirectResponseException){ - // 3xx - responses + } + response.taskToApiResponse() + } catch (e: RedirectResponseException) { handleErrorApiErrorResponse() - } catch(e: ClientRequestException){ - // 4xx - responses + } catch (e: ClientRequestException) { handleErrorApiErrorResponse() - } catch(e: ServerResponseException){ - // 5xx - responses + } catch (e: ServerResponseException) { handleErrorApiErrorResponse() - } catch(e: Exception){ + } catch (e: Exception) { handleErrorApiErrorResponse() } } - override suspend fun getMonthTasks(month: Int, year: Int): Flow>> = flow { + override suspend fun getMonthTasks( + month: Int, + year: Int, + ): Flow>> = flow { emit(ApiResponseWithData.Loading()) try { emit( @@ -61,7 +62,7 @@ internal class TaskApiImpl( } }.toTaskItemList() ) - } catch (e:Exception){ + } catch (e: Exception) { e.printStackTrace() emit(ApiResponseWithData.Error()) } @@ -74,7 +75,7 @@ internal class TaskApiImpl( appendPathSegments(taskId.toString()) } }.toTaskItem() - } catch (e:Exception){ + } catch (e: Exception) { e.printStackTrace() null } @@ -87,16 +88,16 @@ internal class TaskApiImpl( appendPathSegments(taskId.toString()) } }.excludeToApiResponse() - } catch(e: RedirectResponseException){ - // 3xx - responses + } catch (e: RedirectResponseException) { + handleErrorApiErrorResponse() - } catch(e: ClientRequestException){ - // 4xx - responses + } catch (e: ClientRequestException) { + handleErrorApiErrorResponse() - } catch(e: ServerResponseException){ - // 5xx - responses + } catch (e: ServerResponseException) { + handleErrorApiErrorResponse() - } catch(e: Exception){ + } catch (e: Exception) { handleErrorApiErrorResponse() } } @@ -112,16 +113,16 @@ internal class TaskApiImpl( } val test = response.taskUpdateToApiResponse() return test - } catch(e: RedirectResponseException){ - // 3xx - responses + } catch (e: RedirectResponseException) { + handleErrorApiErrorResponse() - } catch(e: ClientRequestException){ - // 4xx - responses + } catch (e: ClientRequestException) { + handleErrorApiErrorResponse() - } catch(e: ServerResponseException){ - // 5xx - responses + } catch (e: ServerResponseException) { + handleErrorApiErrorResponse() - } catch(e: Exception){ + } catch (e: Exception) { handleErrorApiErrorResponse() } } diff --git a/app/src/main/java/com/routinely/routinely/data/task/extensions/taskExtensions.kt b/app/src/main/java/com/routinely/routinely/data/task/extensions/taskExtensions.kt index d7009dc..9a934dc 100644 --- a/app/src/main/java/com/routinely/routinely/data/task/extensions/taskExtensions.kt +++ b/app/src/main/java/com/routinely/routinely/data/task/extensions/taskExtensions.kt @@ -1,36 +1,59 @@ package com.routinely.routinely.data.task.extensions +import com.routinely.routinely.R import com.routinely.routinely.data.auth.model.ApiResponse import com.routinely.routinely.data.auth.model.ApiResponseWithData -import com.routinely.routinely.util.TaskCategory import com.routinely.routinely.util.TaskItem import com.routinely.routinely.util.TaskItemRemote +import com.routinely.routinely.util.TaskListResponse import com.routinely.routinely.util.TaskPriorities import com.routinely.routinely.util.TaskTag import io.ktor.client.call.body import io.ktor.client.statement.HttpResponse import io.ktor.http.HttpStatusCode import kotlinx.serialization.Serializable +import timber.log.Timber -suspend fun HttpResponse.taskToApiResponse() : ApiResponse { - return when(this.status) { +@Serializable +data class ErrorResponse( + val errors: List, +) + +@Serializable +data class ErrorDetail( + val property: String?, + val message: String?, +) + +suspend fun HttpResponse.taskToApiResponse(): ApiResponse { + return when (this.status) { HttpStatusCode.Created -> { ApiResponse.Success } + else -> { - val test = this.body() - println("Property ${test.errors?.property} -- ${test.errors?.message}") - ApiResponse.DefaultError + try { + val errorResponse = this.body() + if (errorResponse.errors.isNotEmpty()) { + ApiResponse.Error(message = R.string.api_unexpected_error) + } else { + ApiResponse.DefaultError + } + } catch (e: Exception) { + println("Erro ao processar resposta: ${e.message}") + ApiResponse.DefaultError + } } } } -suspend fun HttpResponse.taskUpdateToApiResponse() : ApiResponse { - return when(this.status) { +suspend fun HttpResponse.taskUpdateToApiResponse(): ApiResponse { + return when (this.status) { HttpStatusCode.OK -> { ApiResponse.Success } + else -> { val test = this.body() println("Property ${test.errors?.property} -- ${test.errors?.message}") @@ -39,51 +62,58 @@ suspend fun HttpResponse.taskUpdateToApiResponse() : ApiResponse { } } -suspend fun HttpResponse.toTaskItemList() : ApiResponseWithData> { - return when(this.status) { +suspend fun HttpResponse.toTaskItemList(): ApiResponseWithData> { + return when (this.status) { HttpStatusCode.OK -> { - val remote = this.body?>() ?: return ApiResponseWithData.EmptyData() - if(remote.isEmpty()) return ApiResponseWithData.EmptyData() - - return ApiResponseWithData.Success( - remote.map { - TaskItem( - id = it.id, - name = it.name, - date = it.date, - hour = it.hour, - description = it.description, - tag = mapApiStringToTag(it.tag), - priority = mapApiStringToPriority(it.priority), - category = mapApiStringToCategory(it.category) - ) - } - ) + try { + val response = + this.body() ?: return ApiResponseWithData.EmptyData() + if (response.tasks.isEmpty()) return ApiResponseWithData.EmptyData() + + ApiResponseWithData.Success( + response.tasks.map { + TaskItem( + id = it.id, + name = it.name, + date = it.date, + type = it.type.lowercase(), + category = it.category, + description = it.description, + finallyDate = it.finallyDate, + weekDays = it.weekDays + ) + } + ) + } catch (e: Exception) { + ApiResponseWithData.DefaultError() + } } + else -> { ApiResponseWithData.DefaultError() } } } -suspend fun HttpResponse.toTaskItem() : TaskItem? { - return when(this.status) { +suspend fun HttpResponse.toTaskItem(): TaskItem? { + return when (this.status) { HttpStatusCode.OK -> { val remote = this.body() - if(remote == null) return remote + if (remote == null) return remote remote.let { TaskItem( id = it.id, name = it.name, date = it.date, - hour = it.hour, + type = it.type, + category = it.category, description = it.description, - tag = mapApiStringToTag(it.tag), - priority = mapApiStringToPriority(it.priority), - category = mapApiStringToCategory(it.category) + finallyDate = it.finallyDate, + weekDays = it.weekDays ) } } + else -> { println(this.status) null @@ -93,54 +123,23 @@ suspend fun HttpResponse.toTaskItem() : TaskItem? { @Serializable data class Error( - val errors: Errors? + val errors: Errors?, ) @Serializable data class Errors( val property: String, - val message: String + val message: String, ) -fun HttpResponse.excludeToApiResponse() : ApiResponse { - return when(this.status) { +fun HttpResponse.excludeToApiResponse(): ApiResponse { + return when (this.status) { HttpStatusCode.OK -> { ApiResponse.Success } + else -> { ApiResponse.DefaultError } } } - -fun mapApiStringToCategory(apiString: String): TaskCategory { - return when (apiString) { - "career" -> TaskCategory.Career - "personal" -> TaskCategory.Personal - "health" -> TaskCategory.Health - "finance" -> TaskCategory.Finances - "study" -> TaskCategory.Studies - else -> throw IllegalArgumentException("Unknown category: $apiString") - } -} - -private fun mapApiStringToPriority(apiString: String): TaskPriorities { - return when (apiString) { - "urgent" -> TaskPriorities.Urgent - "high" -> TaskPriorities.High - "medium" -> TaskPriorities.Medium - "low" -> TaskPriorities.Low - else -> throw IllegalArgumentException("Unknown priority: $apiString") - } -} - -private fun mapApiStringToTag(apiString: String): TaskTag { - return when (apiString) { - "application" -> TaskTag.Candidacy - "account" -> TaskTag.Bill - "exercise" -> TaskTag.Exercise - "beauty" -> TaskTag.Beauty - "literature" -> TaskTag.Literature - else -> throw IllegalArgumentException("Unknown tag: $apiString") - } -} \ No newline at end of file diff --git a/app/src/main/java/com/routinely/routinely/home/HomeScreen.kt b/app/src/main/java/com/routinely/routinely/home/HomeScreen.kt index 5cb65b4..fc7c3ad 100644 --- a/app/src/main/java/com/routinely/routinely/home/HomeScreen.kt +++ b/app/src/main/java/com/routinely/routinely/home/HomeScreen.kt @@ -5,9 +5,11 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material3.Scaffold -import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.saveable.Saver @@ -15,28 +17,23 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshots.SnapshotStateMap import androidx.compose.ui.Modifier -import androidx.compose.ui.graphics.Color import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.lifecycle.viewmodel.compose.viewModel import com.kizitonwose.calendar.compose.weekcalendar.rememberWeekCalendarState import com.routinely.routinely.R import com.routinely.routinely.data.auth.model.ApiResponseWithData import com.routinely.routinely.ui.components.BottomAppBarRoutinely import com.routinely.routinely.ui.components.CalendarRoutinely import com.routinely.routinely.ui.components.CardTask -import com.routinely.routinely.ui.components.DropdownTaskFilter +import com.routinely.routinely.ui.components.DropdownActivityFilter import com.routinely.routinely.ui.components.Task import com.routinely.routinely.ui.components.TaskAlertDialog import com.routinely.routinely.ui.components.TopAppBarRoutinely import com.routinely.routinely.util.ActivityTag import com.routinely.routinely.util.BottomNavItems import com.routinely.routinely.util.MenuItem -import com.routinely.routinely.util.TaskFields import com.routinely.routinely.util.TaskItem -import com.routinely.routinely.util.toStringId +import java.time.LocalDate fun snapshotStateMapSaver() = Saver, Any>( save = { map -> @@ -61,23 +58,15 @@ fun HomeScreen( menuItems: List, menuTask: List, onSelectDayChange: (Int, Int, Int) -> Unit, - viewModel: HomeViewModel = viewModel(), getTasksResponse: ApiResponseWithData>, ) { - val tasks by viewModel.tasks.collectAsStateWithLifecycle() - val selectedActivityTag by viewModel.selectedActivityTag.collectAsStateWithLifecycle() - val selectedDate by viewModel.selectedDate.collectAsStateWithLifecycle() - val taskSelections by viewModel.taskSelections.collectAsStateWithLifecycle() - val originalCategories by viewModel.originalCategories.collectAsStateWithLifecycle() - val activityTags = TaskFields.getAllOptions() - val bottomBarItems = listOf(BottomNavItems.NewTask) val weekCalendarState = rememberWeekCalendarState() var expanded by remember { mutableStateOf(false) } var showDeleteDialog by rememberSaveable { mutableStateOf(false) } val temporaryDeleteId by remember { mutableStateOf(null) } - /*var selectedActivityTag by remember { mutableIntStateOf(ActivityTag.AllActivity.stringId) } + var selectedActivityTag by remember { mutableIntStateOf(ActivityTag.Task.stringId) } var selectedDate by remember { mutableStateOf(null) } val taskSelections by rememberSaveable(stateSaver = snapshotStateMapSaver()) { @@ -87,24 +76,39 @@ fun HomeScreen( mutableStateOf(SnapshotStateMap()) } - val tasks = remember { mutableStateListOf().apply { addAll(menuTask) } } - menuTask.forEach { task -> - if (!taskSelections.containsKey(task.id)) { - taskSelections[task.id] = task.isSelected - } - if (!originalCategories.containsKey(task.id)) { - originalCategories[task.id] = task.category + val tasks = remember { mutableStateListOf() } + + LaunchedEffect(getTasksResponse) { + if (getTasksResponse is ApiResponseWithData.Success) { + tasks.clear() + getTasksResponse.data?.let { taskItems -> + tasks.addAll(taskItems.mapNotNull { taskItem -> + try { + Task.fromApi( + id = taskItem.id, + title = taskItem.name, + description = taskItem.description ?: "", + category = taskItem.category, + date = LocalDate.parse(taskItem.date.substring(0, 10)), + type = taskItem.type + ) + } catch (e: Exception) { + null + } + }) + } } - }*/ - /* val filteredTasks = menuTask.filter { task -> - val matchesCategory = when (selectedActivityTag) { - ActivityTag.AllActivity.stringId -> task.category != ActivityTag.Completed.stringId - ActivityTag.Completed.stringId -> task.category == ActivityTag.Completed.stringId - else -> task.category == selectedActivityTag + } + + val filteredTasks = tasks.filter { task -> + val matchesType = when (selectedActivityTag) { + ActivityTag.Task.stringId -> task.type.value == "task" + ActivityTag.Habit.stringId -> task.type.value == "habit" + else -> true } val matchesDate = selectedDate?.let { task.date == it } ?: true - matchesCategory && matchesDate - }*/ + matchesType && matchesDate + } Scaffold( topBar = { @@ -120,7 +124,7 @@ fun HomeScreen( }, bottomBar = { BottomAppBarRoutinely( - bottomBarItems = listOf(BottomNavItems.NewTask), + bottomBarItems = bottomBarItems, onClick = { onNewTaskClicked() }, ) }, @@ -133,77 +137,63 @@ fun HomeScreen( CalendarRoutinely( state = weekCalendarState, onDateSelected = { newDate -> - viewModel.onDateSelected(newDate) + selectedDate = newDate + onSelectDayChange( + newDate.monthValue, + newDate.year, + newDate.dayOfMonth + ) } ) - DropdownTaskFilter( + DropdownActivityFilter( modifier = Modifier.padding(top = 9.dp), - labelRes = selectedActivityTag.toStringId(), - onValueChange = { newLabelRes -> - viewModel.onActivityTagChanged(newLabelRes) + labelRes = R.string.label_tag_dropdown, + onValueChange = { stringId -> + selectedActivityTag = stringId }, - list = activityTags, - option = selectedActivityTag.stringId + list = listOf(ActivityTag.Task, ActivityTag.Habit), + option = selectedActivityTag ) - val filteredTasks = tasks.filter { task -> - val matchesCategory = when (selectedActivityTag) { - else -> task.categoryTask == selectedActivityTag.stringId - } - val matchesDate = selectedDate?.let { task.date == it } ?: true - matchesCategory && matchesDate - } - - if (filteredTasks.isEmpty()) { - Text( - text = "Você ainda não tem atividades para hoje", - modifier = Modifier.padding(top = 32.dp), - fontSize = 14.sp, - color = Color.Gray - ) - } - LazyColumn( modifier = Modifier + ) { items(filteredTasks) { task -> val isSelected = taskSelections[task.id] ?: false - val originalCategory = originalCategories[task.id] ?: task.categoryTask - - val shouldDisplay = when (selectedActivityTag) { - - else -> task.categoryTask == selectedActivityTag.stringId - } - - if (shouldDisplay) { - CardTask( - activityTag = ActivityTag.Task.stringId, - description = task.description, - categoryTask = task.categoryTask, - isSelected = isSelected, - onSelected = { selected -> - viewModel.onTaskSelected(task.id, selected) - } - ) + val taskType = when (task.type.value) { + "task" -> ActivityTag.Task.stringId + "habit" -> ActivityTag.Habit.stringId + else -> ActivityTag.Task.stringId } + CardTask( + title = task.title, + description = task.description, + taskType = taskType, + category = task.category.id, + isSelected = isSelected, + onSelected = { selected -> + taskSelections[task.id] = selected + } + ) } } + } - if (showDeleteDialog) { - TaskAlertDialog( - textRes = R.string.delete_task_confirmation, - onConfirm = { - showDeleteDialog = false - temporaryDeleteId?.let { onDeleteTaskClicked(it) } - }, - onCancel = { - showDeleteDialog = false - }, - onDismissRequest = { - showDeleteDialog = false - } - ) - } + if (showDeleteDialog) { + TaskAlertDialog( + textRes = R.string.delete_task_confirmation, + onConfirm = { + showDeleteDialog = false + temporaryDeleteId?.let { onDeleteTaskClicked(it) } + }, + onCancel = { + showDeleteDialog = false + }, + onDismissRequest = { + showDeleteDialog = false + } + ) } } ) @@ -220,7 +210,6 @@ fun HomeScreenPreview() { menuItems = listOf(), menuTask = listOf(), onSelectDayChange = { _, _, _ -> }, - getTasksResponse = ApiResponseWithData.Default(), - viewModel = viewModel() + getTasksResponse = ApiResponseWithData.Default() ) } diff --git a/app/src/main/java/com/routinely/routinely/home/HomeViewModel.kt b/app/src/main/java/com/routinely/routinely/home/HomeViewModel.kt index 2708b14..ccddaa1 100644 --- a/app/src/main/java/com/routinely/routinely/home/HomeViewModel.kt +++ b/app/src/main/java/com/routinely/routinely/home/HomeViewModel.kt @@ -9,9 +9,7 @@ import com.routinely.routinely.home.data.ExcludeTaskUseCase import com.routinely.routinely.home.data.GetUserTasksFromMonthUseCase import com.routinely.routinely.ui.components.Task import com.routinely.routinely.util.ActivityTag -import com.routinely.routinely.util.TaskCategory import com.routinely.routinely.util.TaskItem -import com.routinely.routinely.util.fromStringId import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow @@ -24,60 +22,35 @@ class HomeViewModel( private val excludeTaskUseCase: ExcludeTaskUseCase, private val logoutUseCase: LogoutUseCase, ) : ViewModel() { - private val _deleteTaskResponse = MutableStateFlow(ApiResponse.Empty) val deleteTaskResponse = _deleteTaskResponse.asStateFlow() - private val _getTasksResponse = MutableStateFlow>>(ApiResponseWithData.Default()) val getTasksResponse: StateFlow>> = _getTasksResponse - - private val _tasks = MutableStateFlow>(emptyList()) - val tasks: StateFlow> get() = _tasks - - private val _selectedActivityTag = MutableStateFlow(ActivityTag.Task) - val selectedActivityTag: StateFlow get() = _selectedActivityTag - - private val _selectedData = MutableStateFlow(null) - val selectedDate: StateFlow = _selectedData.asStateFlow() - - private val _taskSelections = MutableStateFlow>(emptyMap()) - val taskSelections: StateFlow> = _taskSelections.asStateFlow() - - private val _originalCategories = MutableStateFlow>(emptyMap()) - val originalCategories: StateFlow> get() = _originalCategories - var lastMonth = 0 var lastYear = 0 var lastDay = 0 - init { val calendar = Calendar.getInstance() - val year = calendar.get(Calendar.YEAR) val month = calendar.get(Calendar.MONTH) + 1 val day = calendar.get(Calendar.DAY_OF_MONTH) - lastMonth = month lastYear = year lastDay = day - getUserTasks(month, year, day) } - fun logout() { viewModelScope.launch { logoutUseCase() } } - fun getUserTasks(month: Int, year: Int, day: Int, force: Boolean = false) = viewModelScope.launch { if (lastMonth == month && lastYear == year && lastDay == day && !force) return@launch lastMonth = month lastYear = year lastDay = day - try { getUserTasksFromMonthUseCase.invoke(month, year, day, force).collect { _getTasksResponse.value = it @@ -86,39 +59,10 @@ class HomeViewModel( _getTasksResponse.value = ApiResponseWithData.DefaultError() } } - fun excludeTask(task: TaskItem) = viewModelScope.launch { _deleteTaskResponse.value = excludeTaskUseCase(task.id) } - fun onTaskSelected(taskId: Int, isSelected: Boolean) { - val task = _tasks.value.find { it.id == taskId } - task?.let { - _taskSelections.value = _taskSelections.value.toMutableMap().apply { - this[taskId] = isSelected - } - val updatedTasks = _tasks.value.toMutableList().map { task -> - if (task.id == taskId) { - val originalCategory = - _originalCategories.value[taskId] ?: fromStringId(task.categoryTask) - val newCategory = - if (isSelected) ActivityTag.Task.stringId else originalCategory.stringId - task.copy(categoryTask = newCategory) - } else task - } - _tasks.value = updatedTasks - } - } - - fun onDateSelected(date: LocalDate) { - _selectedData.value = date - } - - fun onActivityTagChanged(newTagId: Int) { - val newTag = fromStringId(newTagId) - _selectedActivityTag.value = newTag - } - companion object { private const val TAG = "HomeViewModel" } diff --git a/app/src/main/java/com/routinely/routinely/navigation/SetupNavGraph.kt b/app/src/main/java/com/routinely/routinely/navigation/SetupNavGraph.kt index 0ae3ea2..f438ac0 100644 --- a/app/src/main/java/com/routinely/routinely/navigation/SetupNavGraph.kt +++ b/app/src/main/java/com/routinely/routinely/navigation/SetupNavGraph.kt @@ -1,6 +1,5 @@ package com.routinely.routinely.navigation -import android.content.Intent import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.runtime.Composable @@ -15,15 +14,12 @@ import androidx.compose.ui.res.stringResource import androidx.lifecycle.Lifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.navigation.NavBackStackEntry -import androidx.navigation.NavController import androidx.navigation.NavGraphBuilder import androidx.navigation.NavHostController import androidx.navigation.NavType import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable -import androidx.navigation.compose.rememberNavController import androidx.navigation.navArgument -import androidx.navigation.navDeepLink import com.routinely.routinely.R import com.routinely.routinely.changepassword.CreateNewPasswordScreen import com.routinely.routinely.changepassword.CreateNewPasswordViewModel @@ -31,7 +27,6 @@ import com.routinely.routinely.changepassword.ForgotPasswordScreen import com.routinely.routinely.changepassword.ForgotPasswordViewModel import com.routinely.routinely.changepassword.VerificationCodeScreen import com.routinely.routinely.changepassword.VerificationCodeViewModel -import com.routinely.routinely.data.auth.HttpRoutes import com.routinely.routinely.data.auth.model.ApiResponse import com.routinely.routinely.data.auth.model.CreateNewPasswordRequest import com.routinely.routinely.data.auth.model.ForgotPasswordRequest @@ -48,12 +43,10 @@ import com.routinely.routinely.task.AddTaskViewModel import com.routinely.routinely.task.EditTaskScreen import com.routinely.routinely.task.EditTaskViewModel import com.routinely.routinely.ui.components.IndeterminateCircularIndicator -import com.routinely.routinely.ui.components.Task -import com.routinely.routinely.util.ActivityTag import com.routinely.routinely.util.MenuItem import com.routinely.routinely.util.TaskItem +import com.routinely.routinely.util.TaskMapper import org.koin.androidx.compose.koinViewModel -import java.time.LocalDate @Composable @@ -164,12 +157,7 @@ fun NavGraphBuilder.loginRoute( navigateToCreateAccountScreen: () -> Unit, navigateToForgotPasswordScreen: () -> Unit, ) { - composable( - route = Screen.Login.route, - deepLinks = listOf(navDeepLink { - uriPattern = "https://routinely-api-next.vercel.app" - }) - ) { navBackStackEntry -> + composable(route = Screen.Login.route) { navBackStackEntry -> val viewModel: LoginViewModel = koinViewModel() val signInResult by viewModel.signInResult.collectAsState() LoginScreen( @@ -247,7 +235,7 @@ fun NavGraphBuilder.createAccountRoute( } fun NavGraphBuilder.newPasswordRoute( - navigateToLoginScreen: () -> Unit + navigateToLoginScreen: () -> Unit, ) { composable( route = Screen.NewPasswordScreen.route, @@ -268,10 +256,10 @@ fun NavGraphBuilder.newPasswordRoute( password = password, accountId = accountId, code = code - ),confirmPassword + ), confirmPassword ) }, - passwordStateValidation = {password -> + passwordStateValidation = { password -> viewModel.passwordState(password) }, navigateToLoginScreen = { @@ -345,79 +333,58 @@ fun NavGraphBuilder.homeScreenRoute( onNotificationClicked: () -> Unit, onNewTaskClicked: () -> Unit, navigateToLoginScreen: () -> Unit, - navigateToEditScreen: (taskId: Int) -> Unit, + navigateToEditScreen: (Int) -> Unit, ) { composable(route = Screen.HomeScreen.route) { navBackStackEntry -> val viewModel: HomeViewModel = koinViewModel() + val getTasksResponse by viewModel.getTasksResponse.collectAsStateWithLifecycle() + val deleteTaskResponse by viewModel.deleteTaskResponse.collectAsState() + val menuItems = listOf( - MenuItem( - text = stringResource(R.string.menu_configuration), - onItemClick = { } - ), - MenuItem( - text = stringResource(R.string.menu_goal), - onItemClick = { } - ), - MenuItem( - text = stringResource(R.string.menu_notification), - onItemClick = { } - ), MenuItem( text = stringResource(R.string.menu_logout), onItemClick = { viewModel.logout() - navigateToScreenOnlyIfResumed(navBackStackEntry, navigateToLoginScreen) + navigateToLoginScreen() } - ), - ) - val menuTask = listOf( - Task( - id = 1, - activityTag = ActivityTag.Task.stringId, - description = "1", - categoryTask = ActivityTag.Task.stringId, - date = LocalDate.now() ) ) - val deleteTaskResponse by viewModel.deleteTaskResponse.collectAsStateWithLifecycle() - val getTasksResponse = viewModel.getTasksResponse.collectAsStateWithLifecycle() - - LaunchedEffect(key1 = deleteTaskResponse) { - if (deleteTaskResponse == ApiResponse.Success) { - viewModel.getUserTasks( - month = viewModel.lastMonth, - year = viewModel.lastYear, - day = viewModel.lastDay, - force = true - ) + val menuTask = getTasksResponse.data?.mapNotNull { taskItem -> + try { + TaskMapper.fromApi(taskItem) + } catch (e: Exception) { + null } - } + } ?: emptyList() HomeScreen( - onNotificationClicked = { onNotificationClicked() }, - onNewTaskClicked = { onNewTaskClicked() }, - onEditTaskClicked = { - navigateToEditScreen(it.id) + onNotificationClicked = onNotificationClicked, + onNewTaskClicked = onNewTaskClicked, + onEditTaskClicked = { taskItem -> + navigateToEditScreen(taskItem.id) }, - onDeleteTaskClicked = { - viewModel.excludeTask(it) - viewModel.getUserTasks( - month = viewModel.lastMonth, - year = viewModel.lastYear, - day = viewModel.lastDay, - force = true - ) + onDeleteTaskClicked = { taskItem -> + viewModel.excludeTask(taskItem) }, menuItems = menuItems, - + menuTask = menuTask, onSelectDayChange = { month, year, day -> viewModel.getUserTasks(month, year, day) }, - getTasksResponse = getTasksResponse.value, - menuTask = menuTask, + getTasksResponse = getTasksResponse ) + LaunchedEffect(deleteTaskResponse) { + if (deleteTaskResponse is ApiResponse.Success) { + viewModel.getUserTasks( + viewModel.lastMonth, + viewModel.lastYear, + viewModel.lastDay, + force = true + ) + } + } } } diff --git a/app/src/main/java/com/routinely/routinely/task/AddTaskScreen.kt b/app/src/main/java/com/routinely/routinely/task/AddTaskScreen.kt index baf75c7..201cc54 100644 --- a/app/src/main/java/com/routinely/routinely/task/AddTaskScreen.kt +++ b/app/src/main/java/com/routinely/routinely/task/AddTaskScreen.kt @@ -25,33 +25,33 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle 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 com.routinely.routinely.R -import com.routinely.routinely.data.auth.model.TaskRequest import com.routinely.routinely.data.auth.model.ApiResponse +import com.routinely.routinely.data.auth.model.TaskRequest import com.routinely.routinely.ui.components.AddTaskButton import com.routinely.routinely.ui.components.BottomAppBarRoutinely import com.routinely.routinely.ui.components.DatePickerDialogRoutinely import com.routinely.routinely.ui.components.DescriptionTextField -import com.routinely.routinely.ui.components.DropdownTaskFilter -import com.routinely.routinely.ui.components.DropdownRoutinelyPriorities +import com.routinely.routinely.ui.components.DropdownActivityFilter import com.routinely.routinely.ui.components.IndeterminateCircularIndicator import com.routinely.routinely.ui.components.LabelError import com.routinely.routinely.ui.components.TaskNameTextField import com.routinely.routinely.ui.components.TimePickerDialog import com.routinely.routinely.ui.components.TopAppBarRoutinely import com.routinely.routinely.ui.theme.PurpleRoutinely +import com.routinely.routinely.util.ActivityTag import com.routinely.routinely.util.BottomNavItems import com.routinely.routinely.util.MenuItem import com.routinely.routinely.util.TaskCategory import com.routinely.routinely.util.TaskFields -import com.routinely.routinely.util.TaskPriorities -import com.routinely.routinely.util.TaskTag import com.routinely.routinely.util.validators.DateTimeInputValid import com.routinely.routinely.util.validators.DescriptionInputValid import com.routinely.routinely.util.validators.DropdownInputValid import com.routinely.routinely.util.validators.TaskNameInputValid +import java.util.Locale @OptIn(ExperimentalMaterial3Api::class) @@ -74,14 +74,26 @@ fun AddTaskScreen( var taskDateState by rememberSaveable { mutableStateOf(DateTimeInputValid.Empty) } var taskTime by rememberSaveable { mutableStateOf("") } var taskTimeState by rememberSaveable { mutableStateOf(DateTimeInputValid.Empty) } - var dropdownPriority by rememberSaveable { mutableStateOf(null) } - var dropdownPriorityState by rememberSaveable { mutableStateOf(DropdownInputValid.Empty) } - var dropdownTags by rememberSaveable { mutableStateOf(null) } - var dropdownTagsState by rememberSaveable { mutableStateOf(DropdownInputValid.Empty) } + var selectedWeekDays by rememberSaveable { mutableStateOf>(emptyList()) } + + var dropdownTags by rememberSaveable { mutableStateOf(null) } + var dropdownTagsState by rememberSaveable { + mutableStateOf( + DropdownInputValid.Empty + ) + } var dropdownCategory by rememberSaveable { mutableStateOf(null) } - var dropdownCategoryState by rememberSaveable { mutableStateOf(DropdownInputValid.Empty) } + var dropdownCategoryState by rememberSaveable { + mutableStateOf( + DropdownInputValid.Empty + ) + } var taskDescription by rememberSaveable { mutableStateOf("") } - var taskDescriptionState by rememberSaveable { mutableStateOf(DescriptionInputValid.Empty) } + var taskDescriptionState by rememberSaveable { + mutableStateOf( + DescriptionInputValid.Empty + ) + } var apiErrorMessage by rememberSaveable { mutableIntStateOf(0) } var showApiErrors by rememberSaveable { mutableStateOf(false) } @@ -93,7 +105,7 @@ fun AddTaskScreen( topBar = { TopAppBarRoutinely( onMenuClick = { expanded = true }, - onNotificationClick = { }, + onNotificationClick = { }, showBackButton = true, onBackButtonClicked = { onBackButtonPressed() }, onDismissMenu = { expanded = false }, @@ -164,37 +176,40 @@ fun AddTaskScreen( Column( verticalArrangement = Arrangement.spacedBy(16.dp) ) { - DropdownRoutinelyPriorities( - labelRes = R.string.label_priority_dropdown, - onValueChange = { stringId -> - dropdownPriority = TaskFields.getTaskFieldByStringId(stringId = stringId) - dropdownPriorityState = DropdownInputValid.Valid - }, - list = TaskPriorities.getAllTaskPriorities(), - ) Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(16.dp), ) { - DropdownTaskFilter( + DropdownActivityFilter( labelRes = R.string.label_category_dropdown, onValueChange = { stringId -> - dropdownCategory = TaskFields.getTaskFieldByStringId(stringId = stringId) + dropdownCategory = TaskCategory.fromId(stringId) dropdownCategoryState = DropdownInputValid.Valid }, - list = TaskFields.getAllOptions(), + list = TaskCategory.entries.map { category: TaskCategory -> + object : TaskFields(category.id, category.name) {} + }, modifier = Modifier.weight(1f), + option = dropdownCategory?.id ) - DropdownTaskFilter( + } + + Row( + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(16.dp), + ) { + DropdownActivityFilter( labelRes = R.string.label_tag_dropdown, onValueChange = { stringId -> - dropdownTags = TaskFields.getTaskFieldByStringId(stringId = stringId) + dropdownTags = ActivityTag.fromId(stringId) dropdownTagsState = DropdownInputValid.Valid }, - list = TaskFields.getAllOptions(), + list = listOf(ActivityTag.Task, ActivityTag.Habit), modifier = Modifier.weight(1f), + option = dropdownTags?.stringId ) } + DescriptionTextField( value = taskDescription, onValueChange = { newTaskDescription: String -> @@ -204,60 +219,84 @@ fun AddTaskScreen( labelRes = stringResource(id = R.string.label_task_description), error = taskDescriptionState, ) - if(showApiErrors) { + + DropdownWeeklyFrequencyScreen( + onWeekDaysSelected = { days -> + selectedWeekDays = days + } + ) + + if (showApiErrors) { LabelError(stringResource(apiErrorMessage)) } - AddTaskButton ( + AddTaskButton( { + val formattedDate = if (taskDate.isNotEmpty() && taskTime.isNotEmpty()) { + "$taskDate $taskTime" + } else { + "" + } + + val category = dropdownCategory?.apiName ?: "Several" + val type = dropdownTags?.apiString?.lowercase() ?: "task" + val weekDays = if (selectedWeekDays.isEmpty()) + listOf("Monday") + else + selectedWeekDays.map { it.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.getDefault()) else it.toString() } } + onAddTaskClick( TaskRequest( - name = taskName, - date = taskDate, - priority = dropdownPriority!!.apiString, - description = taskDescription, - hour = taskTime, - tag = dropdownTags!!.apiString, - category = dropdownCategory!!.apiString + name = taskName.trim(), + description = taskDescription.trim(), + date = formattedDate, + category = category, + finallyDate = formattedDate, + weekDays = weekDays, + type = type, ) ) }, areFieldsValid = taskNameState == TaskNameInputValid.Valid && taskDescriptionState == DescriptionInputValid.Valid && taskDateState == DateTimeInputValid.Valid && - dropdownPriorityState == DropdownInputValid.Valid && - dropdownCategoryState == DropdownInputValid.Valid && - dropdownTagsState == DropdownInputValid.Valid && - taskTimeState == DateTimeInputValid.Valid + taskTimeState == DateTimeInputValid.Valid && + dropdownCategory != null && + dropdownTags != null && + selectedWeekDays.isNotEmpty() ) } } }, ) LaunchedEffect(key1 = addTaskResult) { - when(addTaskResult) { + when (addTaskResult) { is ApiResponse.Success -> { showApiErrors = false showLoading = false navigateToHomeScreen() } + is ApiResponse.Error -> { apiErrorMessage = addTaskResult.message showApiErrors = true showLoading = false } + is ApiResponse.DefaultError -> { apiErrorMessage = R.string.api_unexpected_error showApiErrors = true showLoading = false } + is ApiResponse.Loading -> { showLoading = true showApiErrors = false } + else -> Unit } } - if(showLoading) { + if (showLoading) { Box( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center @@ -266,4 +305,34 @@ fun AddTaskScreen( IndeterminateCircularIndicator() } } -} \ No newline at end of file +} + + +@Preview(showBackground = true) +@Composable +fun PreviewAddTaskScreen() { + AddTaskScreen( + onBackButtonPressed = { }, + onHomeButtonPressed = { }, + taskNameStateValidation = { nameTask -> + TaskNameInputValid.Valid + }, + taskDateStateValidation = { dateTask -> + DateTimeInputValid.Valid + }, + taskTimeStateValidation = { timeTask -> + DateTimeInputValid.Valid + }, + taskDescriptionStateValidation = { descriptionTask -> + DescriptionInputValid.Valid + }, + navigateToHomeScreen = { }, + onAddTaskClick = { taskRequest -> + }, + menuItems = listOf( + ), + addTaskResult = ApiResponse.Success + ) +} + + diff --git a/app/src/main/java/com/routinely/routinely/task/AddTaskViewModel.kt b/app/src/main/java/com/routinely/routinely/task/AddTaskViewModel.kt index f19c41a..76f9976 100644 --- a/app/src/main/java/com/routinely/routinely/task/AddTaskViewModel.kt +++ b/app/src/main/java/com/routinely/routinely/task/AddTaskViewModel.kt @@ -3,7 +3,6 @@ package com.routinely.routinely.task import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.routinely.routinely.R -import com.routinely.routinely.core.Session import com.routinely.routinely.core.useCase.LogoutUseCase import com.routinely.routinely.data.auth.model.ApiResponse import com.routinely.routinely.data.auth.model.TaskRequest @@ -23,68 +22,67 @@ class AddTaskViewModel( private val _apiResponse = MutableStateFlow(ApiResponse.Empty) val apiResponse = _apiResponse.asStateFlow() - fun addTask(newTask: TaskRequest) { - val newTaskData = TaskRequest( - name = newTask.name, - date = newTask.date, - hour = newTask.hour, - description = newTask.description, - priority = newTask.priority, - category = newTask.category, - tag = newTask.tag, - ) + companion object { + private const val TAG = "AddTaskViewModel" + } + fun addTask(newTask: TaskRequest) { viewModelScope.launch { _apiResponse.value = ApiResponse.Loading try { - _apiResponse.value = taskApi.addTask(newTaskData) - } catch(e: Exception) { + _apiResponse.value = taskApi.addTask(newTask) + } catch (e: Exception) { _apiResponse.value = ApiResponse.DefaultError } } } - fun taskNameState(taskName: String) : TaskNameInputValid { + fun taskNameState(taskName: String): TaskNameInputValid { return when { taskName.isEmpty() -> { TaskNameInputValid.Error(R.string.empty_field) } + taskName.count { it.isLetter() } > 50 -> { TaskNameInputValid.Error(R.string.task_name_limit) } + else -> { TaskNameInputValid.Valid } } } - fun taskDateState(taskDate: String) : DateTimeInputValid { + fun taskDateState(taskDate: String): DateTimeInputValid { return when { taskDate.isEmpty() -> { DateTimeInputValid.Error(R.string.empty_field) } + else -> { DateTimeInputValid.Valid } } } - fun taskTimeState(taskTime: String) : DateTimeInputValid { + fun taskTimeState(taskTime: String): DateTimeInputValid { return when { taskTime.isEmpty() -> { DateTimeInputValid.Error(R.string.empty_field) } + else -> { DateTimeInputValid.Valid } } } - fun taskDescriptionState(description: String) : DescriptionInputValid { + fun taskDescriptionState(description: String): DescriptionInputValid { return when { description.isEmpty() -> { DescriptionInputValid.Error(R.string.empty_field) } + else -> { DescriptionInputValid.Valid } @@ -96,4 +94,4 @@ class AddTaskViewModel( logoutUseCase() } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/routinely/routinely/task/DropdownWeeklyFrequencyScreen.kt b/app/src/main/java/com/routinely/routinely/task/DropdownWeeklyFrequencyScreen.kt new file mode 100644 index 0000000..144c2da --- /dev/null +++ b/app/src/main/java/com/routinely/routinely/task/DropdownWeeklyFrequencyScreen.kt @@ -0,0 +1,339 @@ +package com.routinely.routinely.task + +import androidx.compose.foundation.BorderStroke +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.aspectRatio +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.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.layout.wrapContentSize +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Icon +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalConfiguration +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.kizitonwose.calendar.compose.HorizontalCalendar +import com.kizitonwose.calendar.compose.rememberCalendarState +import com.kizitonwose.calendar.core.CalendarDay +import com.kizitonwose.calendar.core.firstDayOfWeekFromLocale +import com.routinely.routinely.R +import com.routinely.routinely.ui.components.MonthHeader +import com.routinely.routinely.ui.theme.PurpleRoutinely +import com.routinely.routinely.ui.theme.lightGray +import java.time.LocalDate +import java.time.YearMonth +import java.time.format.DateTimeFormatter +import java.util.Locale + +@Composable +fun DropdownWeeklyFrequencyScreen( + onWeekDaysSelected: (List) -> Unit +) { + val daysOfWeek = listOf("D", "S", "T", "Q", "Q", "S", "S") + var currentDate by rememberSaveable { mutableStateOf(LocalDate.now()) } + var selectedDate by rememberSaveable { mutableStateOf(LocalDate.now()) } + var selectedDays by rememberSaveable { mutableStateOf>(emptySet()) } + val formatter = DateTimeFormatter.ofPattern("MMMM", Locale("pt", "BR")) + val monthName = formatter.format(currentDate) + + var isExpanded by remember { mutableStateOf(true) } + + LaunchedEffect(selectedDays) { + val weekDays = selectedDays.map { index -> + when (index) { + 0 -> "Sunday" + 1 -> "Monday" + 2 -> "Tuesday" + 3 -> "Wednesday" + 4 -> "Thursday" + 5 -> "Friday" + 6 -> "Saturday" + else -> "" + } + }.filter { it.isNotEmpty() } + onWeekDaysSelected(weekDays) + } + + Column( + modifier = Modifier + .fillMaxSize(), + verticalArrangement = Arrangement.spacedBy(15.dp) + ) { + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween + ) { + Text( + modifier = Modifier.padding(horizontal = 5.dp), + text = "Frequência semanal", + fontSize = 16.sp, + fontWeight = FontWeight.W400 + ) + Icon( + painter = painterResource( + id = if (isExpanded) { + R.drawable.baseline_keyboard_arrow_up_24 + } else { + R.drawable.baseline_keyboard_arrow_down_24 + } + ), + contentDescription = "Expandir/Recolher", + modifier = Modifier.clickable { isExpanded = !isExpanded } + ) + } + + if (isExpanded) { + Text( + modifier = Modifier.padding(horizontal = 5.dp), + text = "Dias da semana", + fontSize = 16.sp, + fontWeight = FontWeight.Normal + ) + + Row( + modifier = Modifier + .padding(horizontal = 5.dp) + .fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(16.dp) + ) { + daysOfWeek.forEachIndexed { index, day -> + val isSelected = selectedDays.contains(index) + Box( + modifier = Modifier + .size(24.dp) + .border( + width = 1.dp, + color = if (isSelected) PurpleRoutinely else Color.Gray, + shape = CircleShape + ) + .background( + color = if (isSelected) PurpleRoutinely else Color.Transparent, + shape = CircleShape + ) + .clickable { + selectedDays = if (isSelected) { + selectedDays - index + } else { + selectedDays + index + } + } + .padding(1.dp), + contentAlignment = Alignment.Center + ) { + Text( + text = day, + fontSize = 16.sp, + fontWeight = FontWeight.Medium, + textAlign = TextAlign.Left, + color = if (isSelected) Color.White else Color.Black, + ) + } + } + } + + Text( + modifier = Modifier.padding(horizontal = 5.dp), + text = "Finaliza em:", + fontSize = 16.sp, + fontWeight = FontWeight.Normal + ) + Column( + modifier = Modifier + .background(lightGray) + .border( + border = BorderStroke(1.dp, Color.Blue), + shape = RoundedCornerShape(8.dp) + ) + ) { + Column( + Modifier.padding(10.dp) + ) { + + Row( + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 25.dp), + horizontalArrangement = Arrangement.Absolute.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text( + modifier = Modifier.padding(horizontal = 20.dp), + text = "${monthName.capitalize()} de ${currentDate.year}", + fontWeight = FontWeight.Bold, + fontSize = 16.sp, + color = PurpleRoutinely, + textAlign = TextAlign.Center + ) + Row { + Icon( + painter = painterResource(id = R.drawable.baseline_keyboard_arrow_left_24), + tint = PurpleRoutinely, + contentDescription = "Mês anterior", + modifier = Modifier.clickable { + currentDate = currentDate.minusMonths(1) + } + ) + Spacer(modifier = Modifier.width(12.dp)) + Icon( + painter = painterResource(id = R.drawable.baseline_keyboard_arrow_right_24), + tint = PurpleRoutinely, + contentDescription = "Próximo mês", + modifier = Modifier.clickable { + currentDate = currentDate.plusMonths(1) + } + ) + } + } + Spacer(modifier = Modifier.height(20.dp)) + CalendarGrid( + currentDate = currentDate, + selectedDate = selectedDate, + onDateSelected = { date -> + selectedDate = date + currentDate = date + } + ) + } + } + } + } +} + + +@Composable +fun CalendarGrid( + currentDate: LocalDate, + selectedDate: LocalDate?, + onDateSelected: (LocalDate) -> Unit, +) { + + val currentMonth = YearMonth.from(currentDate) + + val startMonth = remember { currentMonth.minusMonths(100) } + val endMonth = remember { currentMonth.plusMonths(100) } + val firstDayOfWeek = remember { firstDayOfWeekFromLocale() } + + val state = rememberCalendarState( + startMonth = startMonth, + endMonth = endMonth, + firstVisibleMonth = currentMonth, + firstDayOfWeek = firstDayOfWeek, + ) + + LaunchedEffect(currentMonth) { + state.scrollToMonth(currentMonth) + } + + HorizontalCalendar( + state = state, + dayContent = { day -> + val isSelected = selectedDate?.isEqual(day.date) == true + Day( + day = day, + isSelected = isSelected, + onClick = { onDateSelected(day.date) } + ) + }, + monthHeader = { month -> + Box( + modifier = Modifier + .background( + color = lightGray + ) + .fillMaxWidth(0.85f)) { + + val daysOfWeek = month.weekDays.first().map { it.date.dayOfWeek } + MonthHeader(daysOfWeek = daysOfWeek) + } + + }, + monthBody = { _, content -> + Box( + modifier = Modifier + .background( + color = lightGray + ) + .fillMaxWidth(0.85f) + ) { + content() + } + }, + monthContainer = { _, container -> + val configuration = LocalConfiguration.current + val screenWidth = configuration.smallestScreenWidthDp.dp + + Box( + modifier = Modifier + .width(screenWidth) + .padding(0.dp) + .clip(shape = RoundedCornerShape(8.dp)) + + ) { + container() + } + } + ) +} + +@Composable +fun Day( + day: CalendarDay, + isSelected: Boolean, + onClick: () -> Unit, +) { + Box( + modifier = Modifier + .aspectRatio(1f) + .background( + color = if (isSelected) PurpleRoutinely else Color.Transparent, + shape = CircleShape + ) + .clickable { onClick() } + .wrapContentSize(Alignment.Center) + .border( + width = 1.dp, + color = if (isSelected) PurpleRoutinely else Color.Transparent, + ), + contentAlignment = Alignment.Center + ) { + + Text( + text = day.date.dayOfMonth.toString(), + color = if (isSelected) Color.White else Color.Black, + textAlign = TextAlign.Center + ) + } +} + +@Preview(showBackground = true) +@Composable +fun PreviewWeeklyFrequencyScreen() { + DropdownWeeklyFrequencyScreen { _ -> } +} diff --git a/app/src/main/java/com/routinely/routinely/task/EditTaskScreen.kt b/app/src/main/java/com/routinely/routinely/task/EditTaskScreen.kt index 82da5e5..4b33dd2 100644 --- a/app/src/main/java/com/routinely/routinely/task/EditTaskScreen.kt +++ b/app/src/main/java/com/routinely/routinely/task/EditTaskScreen.kt @@ -29,6 +29,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle 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 com.routinely.routinely.R @@ -38,8 +39,7 @@ import com.routinely.routinely.ui.components.BottomAppBarRoutinely import com.routinely.routinely.ui.components.ConfirmTaskAlertDialog import com.routinely.routinely.ui.components.DatePickerDialogRoutinely import com.routinely.routinely.ui.components.DescriptionTextField -import com.routinely.routinely.ui.components.DropdownRoutinelyPriorities -import com.routinely.routinely.ui.components.DropdownTaskFilter +import com.routinely.routinely.ui.components.DropdownActivityFilter import com.routinely.routinely.ui.components.IndeterminateCircularIndicator import com.routinely.routinely.ui.components.RoutinelyTaskButton import com.routinely.routinely.ui.components.TaskAlertDialog @@ -51,10 +51,8 @@ import com.routinely.routinely.ui.theme.RedRoutinely import com.routinely.routinely.util.BottomNavItems import com.routinely.routinely.util.MenuItem import com.routinely.routinely.util.TaskCategory -import com.routinely.routinely.util.TaskFields +import com.routinely.routinely.util.TaskCategoryNew import com.routinely.routinely.util.TaskItem -import com.routinely.routinely.util.TaskPriorities -import com.routinely.routinely.util.TaskTag import com.routinely.routinely.util.validators.DateTimeInputValid import com.routinely.routinely.util.validators.DescriptionInputValid import com.routinely.routinely.util.validators.DropdownInputValid @@ -94,14 +92,13 @@ fun EditTaskScreen( var taskTime by rememberSaveable { mutableStateOf("") } var taskTimeState by rememberSaveable { mutableStateOf(DateTimeInputValid.Empty) } - var dropdownPriority by rememberSaveable { mutableStateOf(initialTask.priority) } var dropdownPriorityState by rememberSaveable { mutableStateOf( DropdownInputValid.Empty ) } - var dropdownTags by rememberSaveable { mutableStateOf(initialTask.tag) } + val dropdownTags by rememberSaveable { mutableStateOf(initialTask.type) } var dropdownTagsState by rememberSaveable { mutableStateOf( DropdownInputValid.Empty @@ -115,7 +112,7 @@ fun EditTaskScreen( ) } - var taskDescription by rememberSaveable { mutableStateOf(initialTask.description) } + var taskDescription by rememberSaveable { mutableStateOf(initialTask.description ?: "") } var taskDescriptionState by rememberSaveable { mutableStateOf( DescriptionInputValid.Empty @@ -129,13 +126,14 @@ fun EditTaskScreen( var hasChanges by remember { mutableStateOf(false) } var taskId by remember { mutableStateOf(0) } - val hourFormatter = DateTimeFormatter.ISO_DATE_TIME - initialTask.let { + taskNameState = taskNameStateValidation(it.name) taskDateState = taskDateStateValidation(it.date) - val dateTime = LocalDateTime.parse(it.hour, hourFormatter) + val dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm") + val dateTime = LocalDateTime.parse(initialTask.date, dateTimeFormatter) + val hour = dateTime.hour.toString() var minute = dateTime.minute.toString() if(minute.length == 1) { @@ -150,7 +148,7 @@ fun EditTaskScreen( dropdownCategoryState = DropdownInputValid.Valid - taskDescriptionState = taskDescriptionStateValidation(it.description) + taskDescriptionState = taskDescriptionStateValidation(it.description ?: "") taskId = it.id } @@ -237,46 +235,23 @@ fun EditTaskScreen( verticalArrangement = Arrangement.spacedBy(16.dp), ) { - DropdownRoutinelyPriorities( - labelRes = R.string.label_priority_dropdown, - onValueChange = { stringId -> - dropdownPriority = - TaskFields.getTaskFieldByStringId(stringId = stringId) - dropdownPriorityState = DropdownInputValid.Valid - hasChanges = true - }, - list = TaskPriorities.getAllTaskPriorities(), - option = dropdownPriority.stringId - ) Row( verticalAlignment = Alignment.CenterVertically, horizontalArrangement = Arrangement.spacedBy(16.dp), modifier = Modifier.fillMaxWidth() ) { - DropdownTaskFilter( + DropdownActivityFilter( labelRes = R.string.label_category_dropdown, onValueChange = { stringId -> - dropdownCategory = - TaskFields.getTaskFieldByStringId(stringId = stringId) + dropdownCategory = TaskCategory.fromId(stringId).toString() dropdownCategoryState = DropdownInputValid.Valid hasChanges = true }, - list = TaskFields.getAllOptions(), - modifier = Modifier.weight(1f), - option = dropdownCategory.stringId - ) - DropdownTaskFilter( - labelRes = R.string.label_tag_dropdown, - onValueChange = { stringId -> - dropdownTags = - TaskFields.getTaskFieldByStringId(stringId = stringId) - dropdownTagsState = DropdownInputValid.Valid - hasChanges = true - }, - list = TaskFields.getAllOptions(), + list = TaskCategory.toTaskFieldsList(), modifier = Modifier.weight(1f), - option = dropdownTags.stringId + option = TaskCategory.fromString(dropdownCategory).id ) + } DescriptionTextField( value = taskDescription, @@ -334,12 +309,12 @@ fun EditTaskScreen( taskId, TaskRequest( name = taskName, - date = taskDate, - priority = dropdownPriority.apiString, description = taskDescription, - hour = taskTime, - tag = dropdownTags.apiString, - category = dropdownCategory.apiString + date = "$taskDate $taskTime", + category = dropdownCategory, + finallyDate = "$taskDate $taskTime", + weekDays = listOf("Monday"), + type = dropdownTags, ) ) }, @@ -450,3 +425,39 @@ fun EditTaskScreen( } } } + + +@Preview(showBackground = true, showSystemUi = true) +@Composable +fun EditTaskScreenPreview() { + val initialTask = TaskItem( + id = 1, + name = "Tarefa de Exemplo", + date = "2025-04-01 14:30", + type = "habit", + category = TaskCategoryNew.Career.toString(), + description = "Descrição da tarefa", + finallyDate = "2024-04-01 15:30", + weekDays = listOf("Monday", "Wednesday") + ) + + EditTaskScreen( + onBackButtonPressed = { }, + onNotificationClicked = { }, + onHomeButtonPressed = { }, + taskNameStateValidation = { TaskNameInputValid.Valid }, + taskDateStateValidation = { DateTimeInputValid.Valid }, + taskTimeStateValidation = { DateTimeInputValid.Valid }, + taskDescriptionStateValidation = { DescriptionInputValid.Valid }, + menuItems = listOf( + MenuItem("Opção 1", { /* Nada a fazer */ }), + MenuItem("Opção 2", { /* Nada a fazer */ }) + ), + editTaskResult = ApiResponse.Empty, + initialTask = initialTask, + onSaveChanges = { _, _ -> /* Nada a fazer */ }, + onDeleteTask = { /* Nada a fazer */ }, + onDuplicateTask = { true } + ) +} + diff --git a/app/src/main/java/com/routinely/routinely/task/EditTaskViewModel.kt b/app/src/main/java/com/routinely/routinely/task/EditTaskViewModel.kt index bfa4e8d..ab788e7 100644 --- a/app/src/main/java/com/routinely/routinely/task/EditTaskViewModel.kt +++ b/app/src/main/java/com/routinely/routinely/task/EditTaskViewModel.kt @@ -1,13 +1,11 @@ package com.routinely.routinely.task -import android.util.Log import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.routinely.routinely.R +import com.routinely.routinely.core.useCase.LogoutUseCase import com.routinely.routinely.data.auth.model.ApiResponse import com.routinely.routinely.data.auth.model.TaskRequest -import com.routinely.routinely.core.Session -import com.routinely.routinely.core.useCase.LogoutUseCase import com.routinely.routinely.data.task.api.TaskApi import com.routinely.routinely.task.data.GetTaskByIdUseCase import com.routinely.routinely.util.TaskItem @@ -35,12 +33,12 @@ class EditTaskViewModel( fun saveTask(taskId: Int, newTask: TaskRequest) { val newTaskData = TaskRequest( name = newTask.name, - date = newTask.date, - hour = newTask.hour, description = newTask.description, - priority = newTask.priority, + date = newTask.date, category = newTask.category, - tag = newTask.tag, + finallyDate = newTask.finallyDate, + weekDays = newTask.weekDays, + type = newTask.type, ) viewModelScope.launch { @@ -67,18 +65,8 @@ class EditTaskViewModel( fun duplicateTask(): Boolean { if(task!!.name.contains("(5)")) return false - val hourFormatter = DateTimeFormatter.ISO_DATE_TIME - - val dateTime = LocalDateTime.parse(task!!.hour, hourFormatter) - val hour = dateTime.hour.toString() - var minute = dateTime.minute.toString() - if(minute.length == 1) { - minute = "0$minute" - } - val name = duplicateItem(task!!.name) - viewModelScope.launch { _apiResponse.value = ApiResponse.Loading try { @@ -86,11 +74,11 @@ class EditTaskViewModel( TaskRequest( name = name, date = task!!.date, - hour = "${hour}:${minute}", - description = task!!.description, - priority = task!!.priority.apiString, - category = task!!.category.apiString, - tag = task!!.tag.apiString, + description = task!!.description ?: "Sem descrição", + category = task!!.category, + finallyDate = task!!.finallyDate ?: "Sem final de data", + weekDays = task!!.weekDays, + type = task!!.type, ) ) } catch (e: Exception) { @@ -100,7 +88,6 @@ class EditTaskViewModel( return true } - fun getTaskById(taskId: Int): TaskItem { return runBlocking { task = getTaskByIdUseCase(taskId) diff --git a/app/src/main/java/com/routinely/routinely/ui/components/CardTask.kt b/app/src/main/java/com/routinely/routinely/ui/components/CardTask.kt index cf31859..ec5cef3 100644 --- a/app/src/main/java/com/routinely/routinely/ui/components/CardTask.kt +++ b/app/src/main/java/com/routinely/routinely/ui/components/CardTask.kt @@ -6,12 +6,10 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row -import androidx.compose.foundation.layout.fillMaxHeight +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.layout.width -import androidx.compose.foundation.layout.wrapContentWidth +import androidx.compose.foundation.layout.size import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Edit @@ -19,7 +17,6 @@ import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.Icon import androidx.compose.material3.IconToggleButton -import androidx.compose.material3.MaterialTheme import androidx.compose.material3.RadioButtonColors import androidx.compose.material3.RadioButtonDefaults import androidx.compose.material3.Text @@ -30,100 +27,97 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.text.style.TextDecoration import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.routinely.routinely.R import com.routinely.routinely.ui.theme.PurpleRoutinely -import com.routinely.routinely.ui.theme.cardHabit -import com.routinely.routinely.ui.theme.cardProject -import com.routinely.routinely.ui.theme.cardTask import com.routinely.routinely.ui.theme.categoryColor import com.routinely.routinely.util.ActivityTag -import com.routinely.routinely.util.TaskCategory +import com.routinely.routinely.util.TaskCategoryNew @Composable fun CardTask( - activityTag: Int, + title: String, description: String, - categoryTask: Int, + taskType: Int, + category: Int, isSelected: Boolean, onSelected: (Boolean) -> Unit, - modifier: Modifier = Modifier + modifier: Modifier = Modifier, ) { + val (emoji, cardBackgroundColor, border) = getCategoryAttributes(taskType) + val taskTypeText = when (taskType) { + ActivityTag.Task.stringId -> "Atividade" + ActivityTag.Habit.stringId -> "Hábitos" + else -> "Atividade" + } - val (defaultEmoji, defaultBackgroundColor, defaultBorderColor) = getActivityAttributes( - activityTag - ) - val (selectedEmoji, selectedBackgroundColor, selectedBorderColor) = getSelectedActivityAttributes( - activityTag - ) - - val (defaultActivity, defaultBackgroundActivityColor) = getActivityAttributes( activityTag ) - val (emojiActivity, backgroundActivityColor) = getSelectedActivityAttributes( activityTag ) - - - val emoji = if (isSelected) emojiActivity else defaultActivity - val cardBackgroundColor = if (isSelected) backgroundActivityColor else defaultBackgroundActivityColor - val borderColor = if (isSelected) selectedBorderColor else defaultBorderColor - + val categoryText = when (category) { + 1 -> TaskCategoryNew.Career.name + 2 -> TaskCategoryNew.Finances.name + 3 -> TaskCategoryNew.Studies.name + 4 -> TaskCategoryNew.Health.name + 5 -> TaskCategoryNew.Leisure.name + 6 -> TaskCategoryNew.Productivity.name + else -> TaskCategoryNew.Several.name + } Card( modifier = modifier - .padding(vertical = 12.dp) - .height(124.dp), - shape = RoundedCornerShape(8.dp), - border = BorderStroke(1.dp, borderColor), + .fillMaxWidth(), + shape = RoundedCornerShape(12.dp), + border = BorderStroke(1.dp, border), colors = CardDefaults.cardColors(cardBackgroundColor) ) { Column( - modifier = modifier - .padding(horizontal = 8.dp) - .fillMaxHeight(), - verticalArrangement = Arrangement.SpaceAround - + modifier = Modifier + .padding(8.dp) + .fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(8.dp), ) { Row( - horizontalArrangement = Arrangement.Start, - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.fillMaxWidth() + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(4.dp), + verticalAlignment = Alignment.CenterVertically ) { Text( text = emoji, - fontSize = 14.sp, - modifier = Modifier.padding(end = 2.dp) + fontSize = 16.sp ) Text( - text = getActivityName(activityTag), + text = taskTypeText, fontSize = 14.sp, color = categoryColor, - fontWeight = FontWeight.Bold + fontWeight = FontWeight.Medium ) } Row( - horizontalArrangement = Arrangement.SpaceEvenly, - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.fillMaxWidth() - ) { - Text( - text = description, - modifier = Modifier - .weight(1f) - .padding(start = 2.dp) - .weight(0.5f), - maxLines = 2, - style = MaterialTheme.typography.bodyLarge.copy( - textDecoration = if (isSelected) TextDecoration.LineThrough else TextDecoration.None + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.Bottom + + ) { + Column( + modifier = Modifier.weight(1f) + .fillMaxWidth() + ) { + Text( + text = "00:00", + fontSize = 14.sp, + color = Color.Black ) - ) + Text( + text = title, + fontSize = 14.sp, + color = Color.Black, + fontWeight = FontWeight.Normal, + maxLines = 2 + ) + } CustomRadioButton( - modifier = modifier.padding(start = 10.dp), selected = isSelected, - onSelected = { - onSelected(it) - }, + onSelected = onSelected, selectedIcon = painterResource(id = R.drawable.baseline_check_circle_outline_24), unselectedIcon = painterResource(id = R.drawable.baseline_radio_button_unchecked_24), colors = RadioButtonDefaults.colors( @@ -131,37 +125,35 @@ fun CardTask( selectedColor = categoryColor, disabledSelectedColor = categoryColor, disabledUnselectedColor = categoryColor - ) + ), ) } + Row( + modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically, - modifier = Modifier.fillMaxWidth() + verticalAlignment = Alignment.CenterVertically ) { Box( modifier = Modifier - .wrapContentWidth() .background( color = categoryColor, shape = RoundedCornerShape(4.dp) ) - .padding(horizontal = 6.dp, vertical = 4.dp) - + .padding(horizontal = 12.dp, vertical = 4.dp) ) { Text( - text = getCategoryName(categoryTask), - textAlign = TextAlign.Center, + text = categoryText, fontSize = 12.sp, - fontWeight = FontWeight(400), color = Color.White, + fontWeight = FontWeight.Normal ) } Icon( imageVector = Icons.Default.Edit, contentDescription = null, tint = PurpleRoutinely, - modifier = Modifier + modifier = Modifier.size(24.dp) ) } } @@ -175,20 +167,21 @@ fun CustomRadioButton( modifier: Modifier = Modifier, selectedIcon: Painter, unselectedIcon: Painter, - colors: RadioButtonColors = RadioButtonDefaults.colors() + colors: RadioButtonColors = RadioButtonDefaults.colors(), ) { IconToggleButton( checked = selected, onCheckedChange = { onSelected(!selected) }, - modifier = modifier - .width(24.dp) - .height(24.dp) - ) { + modifier = modifier.size(24.dp) + + ) { Icon( painter = if (selected) selectedIcon else unselectedIcon, contentDescription = null, - tint = if (selected) colors.selectedColor else colors.unselectedColor + tint = if (selected) colors.selectedColor else colors.unselectedColor, + modifier = modifier.fillMaxSize() ) + } } @@ -196,96 +189,39 @@ data class CardCategory( val icon: String, val cardColor: Color, val cardBorder: Color, - val checked: Int + val originalCategoryName: String, ) +fun getCategoryAttributes(taskType: Int): CardCategory { + val defaultBorderColor = Color.Gray -fun darkenColor(color: Color, factor: Float = 0.8f): Color { - return Color( - red = (color.red * factor).coerceIn(0f, 1f), - green = (color.green * factor).coerceIn(0f, 1f), - blue = (color.blue * factor).coerceIn(0f, 1f), - alpha = color.alpha - ) -} - -fun getSelectedActivityAttributes(activityTag: Int): CardCategory { - val selectedBackgroundColor = when (activityTag) { - ActivityTag.Task.stringId -> darkenColor(cardTask, 0.6f) // Darker color for selected state - ActivityTag.Habit.stringId -> darkenColor(cardHabit, 0.6f) - ActivityTag.Project.stringId -> darkenColor(cardProject, 0.6f) - else -> Color.LightGray + val backgroundColor = when (taskType) { + ActivityTag.Task.stringId -> Color(0xFFD1EAFF) + ActivityTag.Habit.stringId -> Color(0xF1E0DFFF) + else -> Color.Transparent } - val selectedBorderColor = when (activityTag) { - ActivityTag.Task.stringId -> Color(0xFF002D5E) // Darker border for selected state - ActivityTag.Habit.stringId -> Color(0xFF303080) - ActivityTag.Project.stringId -> Color(0xFF505000) - else -> Color.Gray - } - - val selectedIcon = when (activityTag) { - ActivityTag.Task.stringId -> "✅" - ActivityTag.Habit.stringId -> "🎯" - ActivityTag.Project.stringId -> "🚀" - else -> "❓" - } - - return CardCategory( - selectedIcon, - selectedBackgroundColor, - selectedBorderColor, - activityTag - ) -} - -fun getActivityAttributes(activityTag: Int, isSelected: Boolean = false): CardCategory { - val defaultBackgroundColor = when (activityTag) { - ActivityTag.Task.stringId -> cardTask - ActivityTag.Habit.stringId -> cardHabit - ActivityTag.Project.stringId -> cardProject - else -> Color.LightGray - } - - val defaultBorderColor = when (activityTag) { + val borderColor = when (taskType) { ActivityTag.Task.stringId -> Color(0xFF115D9E) ActivityTag.Habit.stringId -> Color(0xFF5450BC) - ActivityTag.Project.stringId -> Color(0xFF747400) - else -> Color.Gray + else -> defaultBorderColor } - val backgroundColor = if (isSelected) darkenColor(defaultBackgroundColor, 0.6f) else defaultBackgroundColor - val borderColor = if (isSelected) darkenColor(defaultBorderColor, 0.6f) else defaultBorderColor - - val icon = when (activityTag) { + val icon = when (taskType) { ActivityTag.Task.stringId -> "📋" ActivityTag.Habit.stringId -> "📌" - ActivityTag.Project.stringId -> "🚀" else -> "❓" } - val activityName = getActivityName(activityTag) - - return CardCategory(icon, backgroundColor, borderColor, activityTag) -} - -fun getActivityName(activity: Int): String { - return when (activity) { - ActivityTag.Task.stringId -> "Task" - ActivityTag.Habit.stringId -> "Habit" - ActivityTag.Project.stringId -> "Project" - else -> "Unknown" - } + val originalCategoryName = getCategoryName(taskType) + return CardCategory(icon, backgroundColor, borderColor, originalCategoryName) } fun getCategoryName(category: Int): String { return when (category) { - TaskCategory.Personal.stringId -> "Personal" - TaskCategory.Career.stringId -> "Career" - TaskCategory.Health.stringId -> "Health" - TaskCategory.Studies.stringId -> "Studies" - TaskCategory.Finances.stringId -> "Finances" - else -> "Unknown" + ActivityTag.Task.stringId -> "Tarefa" + ActivityTag.Habit.stringId -> "Hábito" + else -> "Desconhecido" } } @@ -294,10 +230,11 @@ fun getCategoryName(category: Int): String { @Composable private fun CardTaskPreview() { CardTask( - activityTag = ActivityTag.Task.stringId, + title = "Asa sauaygsdyu", description = "Description", - isSelected = false, + isSelected = true, + category = 4, onSelected = {}, - categoryTask = TaskCategory.Career.stringId + taskType = ActivityTag.Habit.stringId ) } diff --git a/app/src/main/java/com/routinely/routinely/ui/components/DescriptionTextField.kt b/app/src/main/java/com/routinely/routinely/ui/components/DescriptionTextField.kt index 4767ec7..05bb476 100644 --- a/app/src/main/java/com/routinely/routinely/ui/components/DescriptionTextField.kt +++ b/app/src/main/java/com/routinely/routinely/ui/components/DescriptionTextField.kt @@ -13,6 +13,8 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.sp import com.routinely.routinely.ui.theme.Gray80 import com.routinely.routinely.ui.theme.GrayRoutinely import com.routinely.routinely.ui.theme.PurpleRoutinely @@ -34,6 +36,7 @@ fun DescriptionTextField( label = { Text( text = labelRes, + fontSize = 16.sp, style = TextStyle(color = Color.Black) ) }, @@ -69,4 +72,10 @@ fun DescriptionTextField( modifier = Modifier .fillMaxWidth() ) +} + +@Preview (showBackground = true) +@Composable +private fun DescriptionTextFieldPreview() { + DescriptionTextField(value = "Description", onValueChange = {}, labelRes = "", error = DescriptionInputValid.Valid) } \ No newline at end of file diff --git a/app/src/main/java/com/routinely/routinely/ui/components/DropdownActivityFilter.kt b/app/src/main/java/com/routinely/routinely/ui/components/DropdownActivityFilter.kt new file mode 100644 index 0000000..d844d1c --- /dev/null +++ b/app/src/main/java/com/routinely/routinely/ui/components/DropdownActivityFilter.kt @@ -0,0 +1,129 @@ +package com.routinely.routinely.ui.components + +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.DropdownMenuItem +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExposedDropdownMenuBox +import androidx.compose.material3.ExposedDropdownMenuDefaults +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.OutlinedTextFieldDefaults +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.routinely.routinely.ui.theme.GrayRoutinely +import com.routinely.routinely.ui.theme.PurpleRoutinely +import com.routinely.routinely.ui.theme.lightGray +import com.routinely.routinely.util.ActivityTag +import com.routinely.routinely.util.TaskFields + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +fun DropdownActivityFilter( + labelRes: Int, + onValueChange: (Int) -> Unit, + list: List, + modifier: Modifier = Modifier, + option: Int? = null +) { + var expanded by remember { mutableStateOf(false) } + + val mapStringToId: Map = list.associate { tag -> + tag.apiString to tag.stringId + } + + var selectedOptionText by remember { mutableStateOf("") } + + selectedOptionText = when { + option != null && option > 0 -> list.find { it.stringId == option }?.apiString ?: "" + else -> stringResource(labelRes) + } + + ExposedDropdownMenuBox( + modifier = modifier + .fillMaxWidth(), + expanded = expanded, + onExpandedChange = { expanded = !expanded }, + ) { + OutlinedTextField( + modifier = modifier + .menuAnchor() + .fillMaxWidth() + .clip(RoundedCornerShape(8.dp)), + readOnly = true, + value = selectedOptionText, + onValueChange = { }, + label = { + Text( + text = stringResource(labelRes), + style = TextStyle(color = PurpleRoutinely), + fontSize = 16.sp, + ) + }, + trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) }, + colors = OutlinedTextFieldDefaults.colors( + unfocusedContainerColor = lightGray, + focusedContainerColor = lightGray, + focusedTextColor = Color.Blue, + unfocusedTextColor = Color.Blue, + focusedBorderColor = GrayRoutinely, + unfocusedBorderColor = GrayRoutinely + ), + textStyle = TextStyle(color = Color.Black) + ) + + ExposedDropdownMenu( + modifier = modifier + .fillMaxWidth(), + expanded = expanded, + onDismissRequest = { expanded = false }, + ) { + mapStringToId.forEach { (string, id) -> + DropdownMenuItem( + text = { + Text( + string, + color = Color.DarkGray + ) + }, + onClick = { + selectedOptionText = string + onValueChange(id) + expanded = false + }, + contentPadding = ExposedDropdownMenuDefaults.ItemContentPadding, + enabled = true + ) + } + } + } +} + +@Preview(showBackground = true) +@Composable +private fun ActivityFilterPreview() { + var selectedActivity by remember { mutableIntStateOf(ActivityTag.Task.stringId) } + DropdownActivityFilter( + labelRes = ActivityTag.Task.stringId, + onValueChange = { newActivity -> + selectedActivity = newActivity + }, + list = listOf( + object : TaskFields(ActivityTag.Task.stringId, "Tarefa") {}, + object : TaskFields(ActivityTag.Habit.stringId, "Hábito") {} + ) + ) +} \ No newline at end of file diff --git a/app/src/main/java/com/routinely/routinely/ui/components/DropdownRoutinely.kt b/app/src/main/java/com/routinely/routinely/ui/components/DropdownRoutinely.kt index 4f4f462..7497f95 100644 --- a/app/src/main/java/com/routinely/routinely/ui/components/DropdownRoutinely.kt +++ b/app/src/main/java/com/routinely/routinely/ui/components/DropdownRoutinely.kt @@ -1,5 +1,6 @@ package com.routinely.routinely.ui.components +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.material3.DropdownMenuItem import androidx.compose.material3.ExperimentalMaterial3Api @@ -28,6 +29,9 @@ import com.routinely.routinely.ui.theme.PurpleRoutinely import com.routinely.routinely.util.TaskFields import com.routinely.routinely.util.TaskPriorities import com.routinely.routinely.util.TaskTag +import com.routinely.routinely.util.TaskCategory +import com.routinely.routinely.util.ActivityTag +import com.routinely.routinely.util.TaskCategoryNew @OptIn(ExperimentalMaterial3Api::class) @Composable @@ -214,11 +218,32 @@ fun DropdownRoutinelyPriorities( @Preview @Composable private fun DropdownRoutinelyPreview() { - DropdownRoutinely(labelRes = R.string.label_tag_dropdown, onValueChange = {}, list = TaskFields.getAllOptions()) + Column { + DropdownRoutinely( + labelRes = R.string.label_tag_dropdown, + onValueChange = {}, + list = listOf(ActivityTag.Task, ActivityTag.Habit), + option = ActivityTag.Task.stringId + ) + + DropdownRoutinely( + labelRes = R.string.label_category_dropdown, + onValueChange = {}, + list = TaskCategory.toTaskFieldsList(), + option = 2 + ) + } } @Preview @Composable private fun DropdownRoutinelyWithColorPreview() { - DropdownRoutinelyPriorities(labelRes = R.string.label_priority_dropdown, onValueChange = {}, list = TaskPriorities.getAllTaskPriorities()) + Column { + DropdownRoutinelyPriorities( + labelRes = R.string.label_priority_dropdown, + onValueChange = {}, + list = TaskPriorities.getAllTaskPriorities(), + option = TaskPriorities.High.stringId + ) + } } \ No newline at end of file diff --git a/app/src/main/java/com/routinely/routinely/ui/components/DropdownTaksFilter.kt b/app/src/main/java/com/routinely/routinely/ui/components/DropdownTaskFilter.kt similarity index 79% rename from app/src/main/java/com/routinely/routinely/ui/components/DropdownTaksFilter.kt rename to app/src/main/java/com/routinely/routinely/ui/components/DropdownTaskFilter.kt index 0740edf..f8f9c42 100644 --- a/app/src/main/java/com/routinely/routinely/ui/components/DropdownTaksFilter.kt +++ b/app/src/main/java/com/routinely/routinely/ui/components/DropdownTaskFilter.kt @@ -1,6 +1,5 @@ package com.routinely.routinely.ui.components - import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.DropdownMenuItem @@ -19,8 +18,6 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color -import androidx.compose.ui.platform.LocalContext -import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -30,32 +27,29 @@ import com.routinely.routinely.ui.theme.PurpleRoutinely import com.routinely.routinely.ui.theme.lightGray import com.routinely.routinely.util.ActivityTag import com.routinely.routinely.util.TaskFields -import com.routinely.routinely.util.TaskTag @OptIn(ExperimentalMaterial3Api::class) @Composable fun DropdownTaskFilter( - labelRes: Int, + label: String, onValueChange: (Int) -> Unit, list: List, modifier: Modifier = Modifier, option: Int? = null ) { var expanded by remember { mutableStateOf(false) } - val context = LocalContext.current val mapStringToId: Map = list.associate { tag -> - context.getString(tag.stringId) to tag.stringId + tag.apiString to tag.stringId } - val labelResAsString = stringResource(id = labelRes) - var selectedOptionText by remember { mutableStateOf(labelResAsString) } + var selectedOptionText by remember { mutableStateOf("") } - option?.let { - selectedOptionText = stringResource(id = it) + selectedOptionText = when { + option != null && option > 0 -> list.find { it.stringId == option }?.apiString ?: "" + else -> label } - ExposedDropdownMenuBox( modifier = modifier .fillMaxWidth(), @@ -72,7 +66,7 @@ fun DropdownTaskFilter( onValueChange = { }, label = { Text( - text = labelResAsString, + text = label, style = TextStyle(color = PurpleRoutinely), fontSize = 16.sp, ) @@ -95,20 +89,7 @@ fun DropdownTaskFilter( expanded = expanded, onDismissRequest = { expanded = false }, ) { - DropdownMenuItem( - text = { - Text( - labelResAsString, - color = Color.Blue - ) - }, - onClick = { }, - contentPadding = ExposedDropdownMenuDefaults.ItemContentPadding, - enabled = false, - ) - mapStringToId.forEach { (string, id) -> - DropdownMenuItem( text = { Text( @@ -129,17 +110,19 @@ fun DropdownTaskFilter( } } - @Preview(showBackground = true) @Composable -private fun TaskFilterRoutinelyPreview() { +private fun TaskFilterPreview() { var selectedTasktag by remember { mutableIntStateOf(ActivityTag.Task.stringId) } DropdownTaskFilter( - labelRes = selectedTasktag, + label = "Tarefa", onValueChange = { newTask -> selectedTasktag = newTask }, - list = TaskFields.getAllOptions() + list = listOf( + object : TaskFields(ActivityTag.Task.stringId, "Tarefa") {}, + object : TaskFields(ActivityTag.Habit.stringId, "Hábito") {} + ) ) } \ No newline at end of file diff --git a/app/src/main/java/com/routinely/routinely/ui/components/Task.kt b/app/src/main/java/com/routinely/routinely/ui/components/Task.kt index 00b83a1..08e2400 100644 --- a/app/src/main/java/com/routinely/routinely/ui/components/Task.kt +++ b/app/src/main/java/com/routinely/routinely/ui/components/Task.kt @@ -1,12 +1,75 @@ package com.routinely.routinely.ui.components +import com.routinely.routinely.util.TaskCategory +import com.routinely.routinely.util.TaskItem +import com.routinely.routinely.util.TaskType import java.time.LocalDate -data class Task ( +data class Task( val id: Int, - val activityTag: Int, + val title: String, val description: String, - var categoryTask: Int, - var isSelected: Boolean = false, - val date: LocalDate -) + val category: TaskCategory, + val isSelected: Boolean, + val date: LocalDate, + val type: TaskType, +) { + companion object { + private fun create( + id: Int, + title: String, + description: String, + category: TaskCategory, + date: LocalDate, + type: TaskType = TaskType.Task, + isSelected: Boolean = false, + ): Task { + return Task( + id = id, + title = title, + description = description, + category = category, + date = date, + type = type, + isSelected = isSelected + ) + } + + fun fromApi( + id: Int, + title: String, + description: String, + category: String, + date: LocalDate, + type: String, + isSelected: Boolean = false, + ): Task { + return create( + id = id, + title = title, + description = description, + category = TaskCategory.fromString(category), + date = date, + type = TaskType.fromString(type), + isSelected = isSelected + ) + } + } + + fun copyWithSelection(isSelected: Boolean): Task { + return copy(isSelected = isSelected) + } + + fun toApiModel(): TaskItem { + return TaskItem( + id = id, + name = title, + description = description, + category = category.name, + date = date.toString(), + type = type.value, + finallyDate = null, + weekDays = emptyList() + ) + } +} diff --git a/app/src/main/java/com/routinely/routinely/ui/components/TasksViewerRoutinely.kt b/app/src/main/java/com/routinely/routinely/ui/components/TasksViewerRoutinely.kt deleted file mode 100644 index 4fb124a..0000000 --- a/app/src/main/java/com/routinely/routinely/ui/components/TasksViewerRoutinely.kt +++ /dev/null @@ -1,502 +0,0 @@ -package com.routinely.routinely.ui.components - -import androidx.compose.foundation.ExperimentalFoundationApi -import androidx.compose.foundation.MarqueeAnimationMode -import androidx.compose.foundation.background -import androidx.compose.foundation.basicMarquee -import androidx.compose.foundation.border -import androidx.compose.foundation.clickable -import androidx.compose.foundation.focusable -import androidx.compose.foundation.interaction.MutableInteractionSource -import androidx.compose.foundation.layout.Arrangement -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.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.layout.width -import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.Checkbox -import androidx.compose.material3.Icon -import androidx.compose.material3.IconButton -import androidx.compose.material3.Text -import androidx.compose.runtime.Composable -import androidx.compose.runtime.MutableState -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.ui.Alignment -import androidx.compose.ui.Modifier -import androidx.compose.ui.focus.FocusManager -import androidx.compose.ui.focus.FocusRequester -import androidx.compose.ui.focus.focusRequester -import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.vector.ImageVector -import androidx.compose.ui.platform.LocalFocusManager -import androidx.compose.ui.res.stringResource -import androidx.compose.ui.res.vectorResource -import androidx.compose.ui.text.font.FontWeight -import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.text.style.TextDecoration -import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.tooling.preview.Preview -import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.sp -import com.routinely.routinely.R -import com.routinely.routinely.data.auth.model.ApiResponseWithData -import com.routinely.routinely.ui.theme.PurpleRoutinely -import com.routinely.routinely.ui.theme.SecondaryYellowRoutinely -import com.routinely.routinely.ui.theme.textGrayColor -import com.routinely.routinely.util.TaskCategory -import com.routinely.routinely.util.TaskItem -import com.routinely.routinely.util.TaskPriorities -import com.routinely.routinely.util.TaskTag - -@Composable -fun TasksViewerRoutinely( - getTasksResponse: ApiResponseWithData>, - listOfConcludedTaskItems: List, - onEditButtonClicked: (taskItem: TaskItem) -> Unit, - onDeleteButtonClicked: (taskItem: TaskItem) -> Unit, -) { - - Column( - modifier = Modifier - .fillMaxWidth() - .padding(top = 12.dp, bottom = 24.dp, start = 12.dp, end = 12.dp) - .border( - width = 1.dp, - color = PurpleRoutinely, - shape = RoundedCornerShape(10.dp) - ), - ) { - Text( - text = stringResource(R.string.label_tasks_viewer), - fontSize = 16.sp, - fontWeight = FontWeight.SemiBold, - color = PurpleRoutinely, - modifier = Modifier - .padding(top = 16.dp, bottom = 6.dp, end = 16.dp, start = 16.dp) - ) - Column( - Modifier - .fillMaxWidth() - .padding(bottom = 12.dp), - ) { - when (getTasksResponse) { - is ApiResponseWithData.Success -> { - val data = getTasksResponse.data!! - for (item in data) { - TaskItem(item, onEditButtonClicked, onDeleteButtonClicked) - } - } - - is ApiResponseWithData.Error -> { - EmptyDataLayout() - } - - is ApiResponseWithData.EmptyData -> { - EmptyDataLayout() - } - - is ApiResponseWithData.Loading -> { - Box( - modifier = Modifier.fillMaxSize(), - contentAlignment = Alignment.Center - - ) { - IndeterminateCircularIndicator() - } - } - - else -> { - EmptyDataLayout() - } - } - } - - - Spacer( - modifier = Modifier - .border(1.dp, PurpleRoutinely) - .fillMaxWidth() - .height(1.dp) - ) - - Text( - text = stringResource(R.string.label_completed_tasks), - fontSize = 16.sp, - fontWeight = FontWeight.SemiBold, - color = PurpleRoutinely, - modifier = Modifier - .padding(top = 12.dp, bottom = 6.dp, end = 16.dp, start = 16.dp) - ) - Column( - Modifier - .fillMaxWidth() - .padding(bottom = 12.dp), - ) { - if(listOfConcludedTaskItems.isEmpty()) { - Column( - modifier = Modifier.padding( - horizontal = 16.dp - ) - ) { - Text( - text = stringResource(id = R.string.no_tasks_completed), - fontSize = 16.sp, - color = textGrayColor, - ) - } - } else { - for (item in listOfConcludedTaskItems) { - ConcludedTaskItem(item) - } - } - } - } -} - -@Composable -private fun EmptyDataLayout() { - Column( - modifier = Modifier.padding( - horizontal = 16.dp - ) - ) { - Text( - text = stringResource(id = R.string.no_tasks_available), - fontSize = 16.sp, - color = textGrayColor, - ) - } -} - -@Composable -private fun TaskItem( - taskItem: TaskItem, - onEditButtonClicked: (taskItem: TaskItem) -> Unit, - onDeleteButtonClicked: (taskItem: TaskItem) -> Unit -) { - val focusRequester = remember { FocusRequester() } - val focusManager = LocalFocusManager.current - val isFocused = remember { mutableStateOf(false) } - val checkedState = remember { mutableStateOf(false) } - val interactionSource = remember { MutableInteractionSource() } - - Row( - Modifier - .fillMaxWidth() - .clickable( - interactionSource = interactionSource, - indication = null, - ) { - if (!isFocused.value) { - focusRequester.requestFocus() - } else { - focusManager.clearFocus() - } - isFocused.value = !isFocused.value - }, - - ) { - CheckButtonTasks(checkedState, focusManager, focusRequester) - - TextWithMarquee(taskItem.name, focusRequester) - - Row( - verticalAlignment = Alignment.CenterVertically - ){ - CategoryItem(taskItem.category) - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.Center, - modifier = Modifier.fillMaxWidth() - ) { - IconButton( - onClick = { }, - enabled = false, - ) { - Icon( - imageVector = ImageVector.vectorResource(id = taskItem.priority.icon), - contentDescription = stringResource(id = taskItem.priority.contentDescription), - tint = Color.Unspecified, - ) - } - - ActionsForTask( - onEditButtonClicked = { - onEditButtonClicked(taskItem) - }, - onDeleteButtonClicked = { - onDeleteButtonClicked(taskItem) - } - ) - } - } - } -} - -@Composable -private fun ConcludedTaskItem( - taskItem: TaskItem -) { - val focusRequester = remember { FocusRequester() } - val focusManager = LocalFocusManager.current - val isFocused = remember { mutableStateOf(false) } - val interactionSource = remember { MutableInteractionSource() } - - Row( - Modifier - .fillMaxWidth() - .clickable( - interactionSource = interactionSource, - indication = null, - ) { - if (!isFocused.value) { - focusRequester.requestFocus() - } else { - focusManager.clearFocus() - } - isFocused.value = !isFocused.value - }, - - ) { - CheckButtonConcludedTask(focusManager, focusRequester) - - TextConcludedWithMarquee(taskItem.name, focusRequester) - - Row( - verticalAlignment = Alignment.CenterVertically - ) { - CategoryItem(category = taskItem.category) - - Row( - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.Start, - modifier = Modifier.fillMaxWidth() - ) { - IconButton( - onClick = { }, - enabled = false, - ) { - Icon( - imageVector = ImageVector.vectorResource(id = taskItem.priority.icon), - contentDescription = stringResource(id = taskItem.priority.contentDescription), - tint = Color.Unspecified, - ) - } - } - } - } -} - -@Composable -private fun CategoryItem(category: TaskCategory) { - Column( - modifier = Modifier - .padding(start = 12.dp, end = 6.dp) - .background(SecondaryYellowRoutinely, shape = RoundedCornerShape(20)) - .width(70.dp), - horizontalAlignment = Alignment.CenterHorizontally - ) { - Text( - text = stringResource(category.stringId), - fontSize = 12.sp, - modifier = Modifier - .padding(horizontal = 6.dp, vertical = 8.dp), - textAlign = TextAlign.Center - ) - } -} - -@Composable -private fun CategoryTask(category: TaskCategory) { - -} - -@Composable -private fun ActionsForTask( - onEditButtonClicked: () -> Unit, - onDeleteButtonClicked: () -> Unit -) { - IconButton( - onClick = { onEditButtonClicked() } - ) { - Icon( - imageVector = ImageVector.vectorResource(id = R.drawable.ic_edit), - contentDescription = stringResource(id = R.string.desc_high_priority), - tint = Color.Unspecified - ) - } - - IconButton( - onClick = { onDeleteButtonClicked() } - ) { - Icon( - imageVector = ImageVector.vectorResource(id = R.drawable.ic_trash), - contentDescription = stringResource(id = R.string.desc_high_priority), - tint = Color.Unspecified - ) - } -} - -@Composable -private fun RowScope.CheckButtonTasks( - checkedState: MutableState, - focusManager: FocusManager, - focusRequester: FocusRequester -) { - Checkbox( - checked = checkedState.value, - /** - * Is this correctly? I don't know, - * but at the moment I'm clearing the focus here to stop basicMarquee from text... - * when user click on CheckBox as well - */ - onCheckedChange = { - focusManager.clearFocus() - checkedState.value = it - }, - modifier = Modifier - .align(Alignment.CenterVertically) - .focusable() - .focusRequester(focusRequester), - ) -} - -@Composable -private fun RowScope.CheckButtonConcludedTask( - focusManager: FocusManager, - focusRequester: FocusRequester -) { - Checkbox( - checked = true, - onCheckedChange = { - focusManager.clearFocus() - }, - modifier = Modifier - .align(Alignment.CenterVertically) - .focusable() - .focusRequester(focusRequester), - enabled = false - ) -} - -@Composable -@OptIn(ExperimentalFoundationApi::class) -private fun RowScope.TextWithMarquee(text: String, focusRequester: FocusRequester) { - Text( - text = text, - /** - * basicMarquee will start once the user click on the text - * It will also stop if the user click again. - * If the user clicks on any component other than this line, it will not interrupt basicMarquee... - * I can't make the background takes the focus - */ - modifier = Modifier - .fillMaxWidth(0.33f) - .align(Alignment.CenterVertically) - .basicMarquee( - animationMode = MarqueeAnimationMode.WhileFocused, - velocity = 40.dp, - delayMillis = 1500 - ) - .focusRequester(focusRequester = focusRequester) - .focusable(), - fontSize = 14.sp, - overflow = TextOverflow.Ellipsis, - maxLines = 1, - ) -} - -@Composable -@OptIn(ExperimentalFoundationApi::class) -private fun RowScope.TextConcludedWithMarquee(text: String, focusRequester: FocusRequester) { - Text( - text = text, - modifier = Modifier - .fillMaxWidth(0.33f) - .align(Alignment.CenterVertically) - .basicMarquee( - animationMode = MarqueeAnimationMode.WhileFocused, - velocity = 40.dp, - delayMillis = 1500 - ) - .focusRequester(focusRequester = focusRequester) - .focusable(), - fontSize = 14.sp, - overflow = TextOverflow.Ellipsis, - maxLines = 1, - textDecoration = TextDecoration.LineThrough, - color = Color.Gray - ) -} -@Composable -@Preview (showBackground = true) -fun PreviewTasksViewerRoutinely() { - val mockTasks = listOf( - TaskItem( - id = 1, - name = "Task 12", - date = "2024-05-11", - hour = "10:00", - tag = TaskTag.Candidacy, - priority = TaskPriorities.High, - category = TaskCategory.Career, - description = "Description of Task 1" - ), - TaskItem( - id = 2, - name = "Task 2", - date = "2024-05-12", - hour = "14:00", - tag = TaskTag.Bill, - priority = TaskPriorities.Medium, - category = TaskCategory.Personal, - description = "Description of Task 2" - ), - TaskItem( - id = 3, - name = "Task 3", - date = "2024-05-13", - hour = "16:00", - tag = TaskTag.Exercise, - priority = TaskPriorities.Low, - category = TaskCategory.Studies, - description = "Description of Task 3" - ) - ) - - val mockCompletedTasks = listOf( - TaskItem( - id = 4, - name = "Completed Task 1", - date = "2024-05-10", - hour = "12:00", - tag = TaskTag.Beauty, - priority = TaskPriorities.High, - category = TaskCategory.Health, - description = "Description of Completed Task 1" - ), - TaskItem( - id = 5, - name = "Completed Task 2", - date = "2024-05-09", - hour = "09:00", - tag = TaskTag.Literature, - priority = TaskPriorities.Medium, - category = TaskCategory.Finances, - description = "Description of Completed Task 2" - ) - ) - TasksViewerRoutinely( - getTasksResponse = ApiResponseWithData.Success(mockTasks), - listOfConcludedTaskItems = mockCompletedTasks, - onEditButtonClicked = { taskItem -> /* Handle edit button click */ }, - onDeleteButtonClicked = { taskItem -> /* Handle delete button click */ } - ) -} diff --git a/app/src/main/java/com/routinely/routinely/util/ActivityTag.kt b/app/src/main/java/com/routinely/routinely/util/ActivityTag.kt index 28e9b02..b6ae282 100644 --- a/app/src/main/java/com/routinely/routinely/util/ActivityTag.kt +++ b/app/src/main/java/com/routinely/routinely/util/ActivityTag.kt @@ -1,23 +1,24 @@ package com.routinely.routinely.util +import android.os.Parcelable import com.routinely.routinely.R import kotlinx.parcelize.Parcelize sealed class ActivityTag(stringId: Int, apiString: String) : TaskFields(stringId, apiString) { @Parcelize - data object Task : ActivityTag(R.string.text_tag_task, "Task") - @Parcelize - data object Habit : ActivityTag(R.string.text_tag_habit, "Habit") - @Parcelize - data object Project : ActivityTag(R.string.text_tag_project, "Project") + data object Task : ActivityTag(R.string.text_tag_task, "Tarefa"), Parcelable -} - -fun ActivityTag.toStringId(): Int { - return this.stringId -} + @Parcelize + data object Habit : ActivityTag(R.string.text_tag_habit, "Hábito"), Parcelable -fun fromStringId(stringId: Int): ActivityTag { - return TaskFields.getTaskFieldByStringId(stringId) -} + companion object { + fun fromId(id: Int): ActivityTag? { + return when (id) { + Task.stringId -> Task + Habit.stringId -> Habit + else -> null + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/routinely/routinely/util/TaskCategory.kt b/app/src/main/java/com/routinely/routinely/util/TaskCategory.kt index 9f054fe..b4ad6ba 100644 --- a/app/src/main/java/com/routinely/routinely/util/TaskCategory.kt +++ b/app/src/main/java/com/routinely/routinely/util/TaskCategory.kt @@ -1,17 +1,34 @@ package com.routinely.routinely.util -import com.routinely.routinely.R -import kotlinx.parcelize.Parcelize +enum class TaskCategory(val id: Int) { + CAREER(1), + FINANCE(2), + STUDIES(3), + HEALTH(4), + LEISURE(5), + PRODUCTIVITY(6), + SEVERAL(7); -sealed class TaskCategory(stringId: Int, apiString: String) : TaskFields(stringId, apiString) { - @Parcelize - data object Career : TaskCategory(R.string.category_career_text, "career") - @Parcelize - data object Personal : TaskCategory(R.string.category_personal_text, "personal") - @Parcelize - data object Health : TaskCategory(R.string.category_health_text, "health") - @Parcelize - data object Finances : TaskCategory(R.string.category_finances_text, "finance") - @Parcelize - data object Studies : TaskCategory(R.string.category_studies_text, "study") -} \ No newline at end of file + val apiName: String + get() = name.lowercase().replaceFirstChar { it.uppercase() } + + companion object { + fun fromString(value: String): TaskCategory { + return try { + valueOf(value.uppercase()) + } catch (e: IllegalArgumentException) { + SEVERAL + } + } + + fun fromId(id: Int): TaskCategory { + return entries.find { it.id == id } ?: SEVERAL + } + + fun toTaskFieldsList(): List { + return entries.map { category -> + object : TaskFields(category.id, category.apiName) {} + } + } + } +} diff --git a/app/src/main/java/com/routinely/routinely/util/TaskCateogry.kt b/app/src/main/java/com/routinely/routinely/util/TaskCateogry.kt new file mode 100644 index 0000000..e1ff84c --- /dev/null +++ b/app/src/main/java/com/routinely/routinely/util/TaskCateogry.kt @@ -0,0 +1,11 @@ +package com.routinely.routinely.util + +enum class TaskCategoryNew { + Career, + Finances, + Studies, + Health, + Leisure, + Productivity, + Several; +} \ No newline at end of file diff --git a/app/src/main/java/com/routinely/routinely/util/TaskFields.kt b/app/src/main/java/com/routinely/routinely/util/TaskFields.kt index 20d8fd2..e616278 100644 --- a/app/src/main/java/com/routinely/routinely/util/TaskFields.kt +++ b/app/src/main/java/com/routinely/routinely/util/TaskFields.kt @@ -1,14 +1,16 @@ package com.routinely.routinely.util import android.os.Parcelable +import kotlinx.parcelize.Parcelize -sealed class TaskFields(var stringId: Int, val apiString: String) : Parcelable { +@Parcelize +open class TaskFields(val stringId: Int, val apiString: String) : Parcelable { companion object { /** * Return all stringId from subclass */ inline fun allStringIds(): List { - return T::class.sealedSubclasses.map { it.objectInstance!!.stringId } + return T::class.sealedSubclasses.map { it.objectInstance?.stringId ?: 0 } } /** @@ -17,7 +19,8 @@ sealed class TaskFields(var stringId: Int, val apiString: String) : Parcelable { inline fun getStringIdByApiString(apiString: String): Int? { return T::class.sealedSubclasses .mapNotNull { it.objectInstance } - .find { it.apiString == apiString }?.stringId + .find { it.apiString == apiString } + ?.stringId } /** @@ -26,15 +29,15 @@ sealed class TaskFields(var stringId: Int, val apiString: String) : Parcelable { inline fun getTaskFieldByStringId(stringId: Int): T { return T::class.sealedSubclasses .mapNotNull { it.objectInstance } - .find { it.stringId == stringId }!! + .find { it.stringId == stringId } + ?: throw IllegalArgumentException("No TaskField found with stringId: $stringId") } /** * Get all options of T : TaskFields */ inline fun getAllOptions(): List { - return T::class.sealedSubclasses - .mapNotNull { it.objectInstance } + return T::class.sealedSubclasses.mapNotNull { it.objectInstance } } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/routinely/routinely/util/TaskItem.kt b/app/src/main/java/com/routinely/routinely/util/TaskItem.kt index 6d30c24..8aebe5a 100644 --- a/app/src/main/java/com/routinely/routinely/util/TaskItem.kt +++ b/app/src/main/java/com/routinely/routinely/util/TaskItem.kt @@ -1,27 +1,19 @@ package com.routinely.routinely.util import android.os.Parcelable +import com.routinely.routinely.ui.components.Task import kotlinx.parcelize.Parcelize +import java.time.LocalDate @Parcelize data class TaskItem( - val id: Int, - var name: String, - var date: String, - var hour: String, - var tag: TaskTag, - var priority: TaskPriorities, - var category: TaskCategory, - var description: String -) : Parcelable - -data class TaskItemRemote( val id: Int, val name: String, - val date: String, - val hour: String, - val tag: String, - val priority: String, + val description: String?, val category: String, - val description: String -) \ No newline at end of file + val date: String, + val type: String, + val finallyDate: String?, + val weekDays: List, +) : Parcelable + diff --git a/app/src/main/java/com/routinely/routinely/util/TaskItemRemote.kt b/app/src/main/java/com/routinely/routinely/util/TaskItemRemote.kt new file mode 100644 index 0000000..b39c71c --- /dev/null +++ b/app/src/main/java/com/routinely/routinely/util/TaskItemRemote.kt @@ -0,0 +1,22 @@ +package com.routinely.routinely.util + +import kotlinx.serialization.Serializable + +@Serializable +data class TaskListResponse( + val count: Int, + val tasks: List +) + +@Serializable +data class TaskItemRemote( + val id: Int, + val name: String, + val description: String, + val date: String, + val category: String, + val checked: Boolean, + val finallyDate: String, + val weekDays: List, + val type: String, +) \ No newline at end of file diff --git a/app/src/main/java/com/routinely/routinely/util/TaskMapper.kt b/app/src/main/java/com/routinely/routinely/util/TaskMapper.kt new file mode 100644 index 0000000..c87858c --- /dev/null +++ b/app/src/main/java/com/routinely/routinely/util/TaskMapper.kt @@ -0,0 +1,26 @@ +package com.routinely.routinely.util + +import com.routinely.routinely.ui.components.Task +import java.time.LocalDate +import java.time.format.DateTimeFormatter + +class TaskMapper { + companion object { + private val dateFormatter = DateTimeFormatter.ISO_LOCAL_DATE + + fun fromApi(taskItem: TaskItem): Task? { + return try { + Task.fromApi( + id = taskItem.id, + title = taskItem.name, + description = taskItem.description ?: "", + category = taskItem.category, + date = LocalDate.parse(taskItem.date, dateFormatter), + type = taskItem.type + ) + } catch (e: Exception) { + null + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/routinely/routinely/util/TaskType.kt b/app/src/main/java/com/routinely/routinely/util/TaskType.kt new file mode 100644 index 0000000..fd90fd2 --- /dev/null +++ b/app/src/main/java/com/routinely/routinely/util/TaskType.kt @@ -0,0 +1,18 @@ +package com.routinely.routinely.util + +sealed class TaskType(val value: String) { + data object Task : TaskType("task") + data object Habit : TaskType("habit") + data object Project : TaskType("project") + + companion object { + fun fromString(value: String): TaskType { + return when (value.lowercase()) { + "task" -> Task + "habit" -> Habit + "project" -> Project + else -> Task + } + } + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/baseline_keyboard_arrow_down_24.xml b/app/src/main/res/drawable/baseline_keyboard_arrow_down_24.xml new file mode 100644 index 0000000..128f2f6 --- /dev/null +++ b/app/src/main/res/drawable/baseline_keyboard_arrow_down_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/baseline_keyboard_arrow_left_24.xml b/app/src/main/res/drawable/baseline_keyboard_arrow_left_24.xml new file mode 100644 index 0000000..8a2ea6d --- /dev/null +++ b/app/src/main/res/drawable/baseline_keyboard_arrow_left_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/baseline_keyboard_arrow_right_24.xml b/app/src/main/res/drawable/baseline_keyboard_arrow_right_24.xml new file mode 100644 index 0000000..b857edc --- /dev/null +++ b/app/src/main/res/drawable/baseline_keyboard_arrow_right_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/baseline_keyboard_arrow_up_24.xml b/app/src/main/res/drawable/baseline_keyboard_arrow_up_24.xml new file mode 100644 index 0000000..4b82d8b --- /dev/null +++ b/app/src/main/res/drawable/baseline_keyboard_arrow_up_24.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index c6bb05c..a9aaf65 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -5,7 +5,7 @@ Botão Home de Navegação Início - Nova tarefa + Nova atividade Top Bar Prioridade urgente @@ -80,7 +80,7 @@ Conta Projeto Hábito - Atividade + Tarefa bill Categorias Tags @@ -122,5 +122,10 @@ O código de verificação que você inseriu não é válido. Verifique o código e tente novamente. Todas as atividades Concluídas - Tarefa + Lazer + Produtividade + Diversos + Tipo invalido + Tipo de categoria + Atividade diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e691f1e..12baf61 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -5,7 +5,6 @@ Notification Button Menu Button Home Navigation Button - Home New Task Top Bar @@ -82,8 +81,8 @@ Bill Project Habit - Activity Task + Activity bill Categories Tags @@ -125,5 +124,11 @@ Password reset completed successfully The verification code you entered is not valid. Check the code and try again. All activities + Leisure + Productivity + Several + Invalid Type + Invalid category. [{ \"include\": \"https://routinely-web-next.vercel.app/.well-known/assetlinks.json\" }] + From 7706a84afbaf18b160c700133c6b0c3a0826abeb Mon Sep 17 00:00:00 2001 From: CrozCamel Date: Mon, 7 Apr 2025 21:30:18 -0300 Subject: [PATCH 5/7] =?UTF-8?q?Altera=C3=A7=C3=B5es=20para=20resolver=20os?= =?UTF-8?q?=20conflitos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../routinely/navigation/SetupNavGraph.kt | 4 ++-- .../routinely/ui/components/CardTask.kt | 3 +-- .../ui/components/DropdownTaskFilter.kt | 3 +-- .../routinely/routinely/ui/components/Task.kt | 23 +++---------------- .../routinely/routinely/util/ActivityTag.kt | 2 +- app/src/main/res/values-pt-rBR/strings.xml | 5 ++-- 6 files changed, 10 insertions(+), 30 deletions(-) diff --git a/app/src/main/java/com/routinely/routinely/navigation/SetupNavGraph.kt b/app/src/main/java/com/routinely/routinely/navigation/SetupNavGraph.kt index f438ac0..fdf06dd 100644 --- a/app/src/main/java/com/routinely/routinely/navigation/SetupNavGraph.kt +++ b/app/src/main/java/com/routinely/routinely/navigation/SetupNavGraph.kt @@ -335,7 +335,7 @@ fun NavGraphBuilder.homeScreenRoute( navigateToLoginScreen: () -> Unit, navigateToEditScreen: (Int) -> Unit, ) { - composable(route = Screen.HomeScreen.route) { navBackStackEntry -> + composable(route = Screen.HomeScreen.route) { _ -> val viewModel: HomeViewModel = koinViewModel() val getTasksResponse by viewModel.getTasksResponse.collectAsStateWithLifecycle() val deleteTaskResponse by viewModel.deleteTaskResponse.collectAsState() @@ -555,4 +555,4 @@ private fun navigateToScreenOnlyIfResumed( if (navBackStackEntry.lifecycleIsresumed()) { navigateToHomeScreen() } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/routinely/routinely/ui/components/CardTask.kt b/app/src/main/java/com/routinely/routinely/ui/components/CardTask.kt index ec5cef3..64137c4 100644 --- a/app/src/main/java/com/routinely/routinely/ui/components/CardTask.kt +++ b/app/src/main/java/com/routinely/routinely/ui/components/CardTask.kt @@ -225,12 +225,11 @@ fun getCategoryName(category: Int): String { } } - @Preview(showBackground = true) @Composable private fun CardTaskPreview() { CardTask( - title = "Asa sauaygsdyu", + title = "Titulo", description = "Description", isSelected = true, category = 4, diff --git a/app/src/main/java/com/routinely/routinely/ui/components/DropdownTaskFilter.kt b/app/src/main/java/com/routinely/routinely/ui/components/DropdownTaskFilter.kt index f8f9c42..2fc2bb5 100644 --- a/app/src/main/java/com/routinely/routinely/ui/components/DropdownTaskFilter.kt +++ b/app/src/main/java/com/routinely/routinely/ui/components/DropdownTaskFilter.kt @@ -124,5 +124,4 @@ private fun TaskFilterPreview() { object : TaskFields(ActivityTag.Habit.stringId, "Hábito") {} ) ) - -} \ No newline at end of file +} diff --git a/app/src/main/java/com/routinely/routinely/ui/components/Task.kt b/app/src/main/java/com/routinely/routinely/ui/components/Task.kt index 08e2400..107cbc4 100644 --- a/app/src/main/java/com/routinely/routinely/ui/components/Task.kt +++ b/app/src/main/java/com/routinely/routinely/ui/components/Task.kt @@ -12,7 +12,7 @@ data class Task( val category: TaskCategory, val isSelected: Boolean, val date: LocalDate, - val type: TaskType, + val type: TaskType ) { companion object { private fun create( @@ -22,7 +22,7 @@ data class Task( category: TaskCategory, date: LocalDate, type: TaskType = TaskType.Task, - isSelected: Boolean = false, + isSelected: Boolean = false ): Task { return Task( id = id, @@ -42,7 +42,7 @@ data class Task( category: String, date: LocalDate, type: String, - isSelected: Boolean = false, + isSelected: Boolean = false ): Task { return create( id = id, @@ -55,21 +55,4 @@ data class Task( ) } } - - fun copyWithSelection(isSelected: Boolean): Task { - return copy(isSelected = isSelected) - } - - fun toApiModel(): TaskItem { - return TaskItem( - id = id, - name = title, - description = description, - category = category.name, - date = date.toString(), - type = type.value, - finallyDate = null, - weekDays = emptyList() - ) - } } diff --git a/app/src/main/java/com/routinely/routinely/util/ActivityTag.kt b/app/src/main/java/com/routinely/routinely/util/ActivityTag.kt index b6ae282..482a975 100644 --- a/app/src/main/java/com/routinely/routinely/util/ActivityTag.kt +++ b/app/src/main/java/com/routinely/routinely/util/ActivityTag.kt @@ -21,4 +21,4 @@ sealed class ActivityTag(stringId: Int, apiString: String) : TaskFields(stringId } } } -} \ No newline at end of file +} diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index a9aaf65..ae05ee2 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -125,7 +125,6 @@ Lazer Produtividade Diversos - Tipo invalido - Tipo de categoria - Atividade + Tipo invalido. + Categoria invalida. From b6df4cdf9601d79c259ae5d0f2e02e48c1cf90c7 Mon Sep 17 00:00:00 2001 From: CrozCamel Date: Tue, 8 Apr 2025 19:34:07 -0300 Subject: [PATCH 6/7] Tentnaod resolver o conflito na String --- app/src/main/res/values-pt-rBR/strings.xml | 1 - app/src/main/res/values/strings.xml | 1 - 2 files changed, 2 deletions(-) diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index ae05ee2..6552d3a 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -92,7 +92,6 @@ Todas as atividades Editar tarefa Continuar com e-mail e senha - Criar conta Já possui uma conta? Entrar Confirma diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 12baf61..96e7201 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -95,7 +95,6 @@ All activities Edit Task Continue with email and password - Create Account Already have an account? Login Confirm From c2e0f84bcc076585c3071b3aa761f50619c4e77f Mon Sep 17 00:00:00 2001 From: CrozCamel Date: Sat, 12 Apr 2025 17:42:45 -0300 Subject: [PATCH 7/7] resolvendo conflitos --- .../routinely/routinely/ui/components/CreateAccountButton.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/com/routinely/routinely/ui/components/CreateAccountButton.kt b/app/src/main/java/com/routinely/routinely/ui/components/CreateAccountButton.kt index 9a08e7e..351c9f4 100644 --- a/app/src/main/java/com/routinely/routinely/ui/components/CreateAccountButton.kt +++ b/app/src/main/java/com/routinely/routinely/ui/components/CreateAccountButton.kt @@ -26,7 +26,7 @@ fun CreateAccountButton( shape = MaterialTheme.shapes.small, ) { Text( - text = stringResource(id = R.string.create_account_button), color = Color.White + text = stringResource(id = R.string.title_create_account), color = Color.White ) } } \ No newline at end of file