Skip to content

Instantly share code, notes, and snippets.

@ithustle
Created January 3, 2024 15:31
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ithustle/9a37b68be806de206090e258199870ec to your computer and use it in GitHub Desktop.
Save ithustle/9a37b68be806de206090e258199870ec to your computer and use it in GitHub Desktop.
ToDo - Live 1
class Colors {
companion object {
val arrayColors = listOf(
Color(0xFF910101),
Color(0xFF0D0191),
Color(0xFF5D8238),
Color(0xFF000000),
Color(0xFFB47D3C),
Color(0xFF6D0D75),
Color(0xFF9C9C9C)
)
}
}
@Composable
fun DetailScreen(
viewModel: DetailViewModel,
onCreateTask: () -> Unit = {}
) {
val keyboardController = LocalSoftwareKeyboardController.current
val state by viewModel.uiState.collectAsState()
val categories = listOf(
"Negócios",
"Pessoal",
"Casa",
"Finanças",
"Saúde",
"Educação",
"Viagens",
"Projectos"
)
Scaffold(
floatingActionButton = {
FloatingActionButton(
onClick = onCreateTask,
shape = RoundedCornerShape(100.dp),
containerColor = MaterialTheme.colorScheme.primary
) {
Icon(
Icons.Default.Done,
contentDescription = "Add"
)
}
}
) { paddingValues ->
Column(
modifier = Modifier
.padding(paddingValues)
.fillMaxSize()
.background(color = MaterialTheme.colorScheme.background)
) {
Spacer(modifier = Modifier.size(30.dp))
Text(
text = "Nova Tarefa",
fontSize = 32.sp,
fontWeight = FontWeight.SemiBold,
modifier = Modifier
.padding(horizontal = 16.dp)
)
Spacer(modifier = Modifier.size(30.dp))
Text(
text = "Cor",
color = Color(0xFF676767),
modifier = Modifier
.padding(vertical = 8.dp, horizontal = 16.dp)
)
TextField(
value = state.descriptionTask,
onValueChange = { viewModel.updateDescription(it) },
label = {
Text(text = "O que estás a planear?")
},
modifier = Modifier
.fillMaxWidth()
.height(150.dp)
.padding(horizontal = 16.dp),
textStyle = TextStyle(color = Color.Black),
maxLines = 10
)
Spacer(modifier = Modifier.size(16.dp))
Text(
text = "Cor",
color = arrayColors[state.colorTask],
modifier = Modifier
.padding(vertical = 8.dp, horizontal = 16.dp)
)
Row(
horizontalArrangement = Arrangement.SpaceAround,
modifier = Modifier
.padding(horizontal = 16.dp)
.fillMaxWidth()
) {
arrayColors.forEachIndexed { index, color ->
TaskColorPicker(
color = color,
selected = state.colorTask
) { _ ->
viewModel.onColorTask(index)
keyboardController?.hide()
}
Spacer(modifier = Modifier.size(12.dp))
}
}
Spacer(modifier = Modifier.size(16.dp))
Text(
text = "Categorias",
color = Color(0xFF676767),
modifier = Modifier
.padding(vertical = 8.dp, horizontal = 16.dp)
)
LazyVerticalGrid(
columns = GridCells.Adaptive(minSize = 128.dp),
modifier = Modifier
.padding(horizontal = 16.dp)
) {
items(categories) { category ->
Column(
modifier = Modifier
.border(width = 0.5.dp, color = Color.LightGray)
.height(height = 72.dp)
.background(color = if (state.categoryTask == category) arrayColors[state.colorTask] else Color.White)
.clickable {
viewModel.onCategoryTask(category)
keyboardController?.hide()
},
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = category,
color = if (state.categoryTask == category) Color.White else Color.Black
)
}
}
}
}
}
}
@Preview
@Composable
fun DetailScreenPrev() {
DetailScreen(viewModel = DetailViewModel(context = LocalContext.current))
}
// Criar somente quando precisar dele
data class DetailScreenUiState(
val descriptionTask: String = "",
val colorTask: Int = 0,
val categoryTask: String = "",
val stateTask: Boolean = false
)
class CreateTaskViewModel(context: Context): ViewModel() {
private var dao: TaskRepository = TaskRepository(context = context)
private val _uiState: MutableStateFlow<DetailScreenUiState> = MutableStateFlow(DetailScreenUiState())
var uiState: StateFlow<DetailScreenUiState> = _uiState.asStateFlow()
fun updateDescription(description: String) {
_uiState.value = _uiState.value.copy(descriptionTask = description)
}
fun onColorTask(color: Int) {
_uiState.value = _uiState.value.copy(colorTask = color)
}
fun onCategoryTask(category: String) {
_uiState.value = _uiState.value.copy(categoryTask = category)
}
suspend fun handleSaveTask() {
val task = Task(
descriptionTask = _uiState.value.descriptionTask,
colorTask = _uiState.value.colorTask,
categoryTask = _uiState.value.categoryTask,
stateTask = _uiState.value.stateTask
)
dao.saveTask(task)
}
}
@Database(entities = [Task::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
abstract fun TaskDao(): TaskDao
}
@Dao
interface TaskDao {
@Insert
suspend fun saveTask(task: Task)
@Query("SELECT * FROM task")
fun getAllTasks(): Flow<List<Task>>
@Query("UPDATE task SET stateTask = :stateTask WHERE uid = :uid")
suspend fun updateTaskState(uid: UUID, stateTask: Boolean)
}
// O Scaffold deverá ser o último a ser adicionado
@Composable
fun DashboardScreen(
viewModel: DashboardModelView,
onNavigateToCreateTask: () -> Unit = {},
onTaskDone: (UUID, Boolean) -> Unit = { _, _ -> }
) {
val isPreview = LocalInspectionMode.current
val state by viewModel.uiState.collectAsState()
val listOfTasks = if (isPreview) MockData.allTasks() else state.allTasks
Column(
modifier = Modifier
.fillMaxSize()
.background(color = MaterialTheme.colorScheme.background)
) {
Scaffold(
floatingActionButton = {
FloatingActionButton(
onClick = onNavigateToCreateTask,
shape = RoundedCornerShape(100.dp),
containerColor = MaterialTheme.colorScheme.primary
) {
Icon(
Icons.Default.Add,
contentDescription = "Add"
)
}
}
) { paddingValues ->
Column(
modifier = Modifier
.padding(paddingValues)
.scrollable(state = rememberScrollState(), orientation = Orientation.Vertical),
) {
Spacer(modifier = Modifier.size(30.dp))
Text(
text = "ToDo - Minhas Cenas",
fontSize = 32.sp,
fontWeight = FontWeight.SemiBold,
modifier = Modifier
.padding(horizontal = 16.dp)
)
// Quando chegares aqui, adiciona apenas o composable sem a condição e depois de tudo
// feito, cria a extensão - Está no final do gist
if (listOfTasks.isNotEmpty()) {
if (listOfTasks.allTasksNotDone().isNotEmpty()) {
TaskCardList(
sectionTitle = "Minhas Tarefas",
listOfTasks = listOfTasks.allTasksNotDone(),
onTaskDone = onTaskDone
)
}
if (listOfTasks.allTasksDone().isNotEmpty()) {
TaskCardList(
sectionTitle = "Finalizadas",
listOfTasks = listOfTasks.allTasksDone(),
onTaskDone = onTaskDone
)
}
} else {
Column(
modifier = Modifier
.fillMaxSize(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally
) {
Text(
text = "Nenhuma tarefa criada",
fontSize = 28.sp,
color = Color.Gray,
fontWeight = FontWeight.SemiBold,
modifier = Modifier
.padding(horizontal = 16.dp)
)
}
}
}
}
}
}
@Preview
@Composable
fun DashboardScreenPreview() {
DashboardScreen(viewModel = DashboardModelView(context = LocalContext.current))
}
data class DashboardUiScreen(
val allTasks: List<Task> = emptyList()
)
class DashboardModelView(context: Context): ViewModel() {
private var dao: TaskRepository = TaskRepository(context = context)
private val _uiState: MutableStateFlow<DashboardUiScreen> = MutableStateFlow(
DashboardUiScreen()
)
var uiState: StateFlow<DashboardUiScreen> = _uiState.asStateFlow()
init {
viewModelScope.launch {
dao.getAllTasks().collect { listTasks ->
_uiState.update {
it.copy(allTasks = listTasks)
}
}
}
}
suspend fun markTaskAsDone(id: UUID, state: Boolean) {
dao.updateTask(id, state)
}
}
class MockData {
companion object {
fun allTasks(): List<Task> {
return listOf(
Task(
descriptionTask = "Minha tarefa 1",
categoryTask = "Negócios",
colorTask = 2,
stateTask = false
),
Task(
descriptionTask = "Minha tarefa 2",
categoryTask = "Pessoal",
colorTask = 3,
stateTask = true
),
Task(
descriptionTask = "Minha tarefa 3",
categoryTask = "Negócios",
colorTask = 2,
stateTask = false
)
)
}
fun task(): Task {
return Task(
descriptionTask = "Minha tarefa 1",
categoryTask = "Negócios",
colorTask = 2,
stateTask = true
)
}
}
}
// Criar o package dao e adicionar os tres files
// Instalar as dependências do Room e ktx, navigation
// E não completar essa tela ainda
fun NavHostApp(
modifier: Modifier = Modifier,
navController: NavHostController = rememberNavController(),
startDestination: String = "dashboard"
) {
NavHost(
modifier = modifier,
navController = navController,
startDestination = startDestination
) {
composable("dashboard") {
val context: Context = LocalContext.current
val viewModel = DashboardModelView(context = context)
val coroutineScope = rememberCoroutineScope()
DashboardScreen(
viewModel = viewModel,
onNavigateToCreateTask = {
navController.navigate("details")
},
onTaskDone = { id, state ->
coroutineScope.launch {
viewModel.markTaskAsDone(id, state)
}
}
)
}
composable("details") {
val context: Context = LocalContext.current
val viewModel = DetailViewModel(context = context)
val coroutineScope = rememberCoroutineScope()
CreateTaskScreen(
viewModel = viewModel
) {
coroutineScope.launch {
viewModel.handleSaveTask()
Toast.makeText(context, "Tarefa registada com sucesso.", Toast.LENGTH_SHORT)
navController.popBackStack()
}
}
}
}
}
@Entity
data class Task(
@PrimaryKey val uid: UUID = UUID.randomUUID(),
var descriptionTask: String,
var categoryTask: String,
var colorTask: Int,
var stateTask: Boolean
)
@Composable
fun TaskCard(
task: Task,
modifier: Modifier
) {
Row(
modifier = modifier
.shadow(
elevation = 16.dp,
spotColor = Color(0x1A000000),
ambientColor = Color(0x1A000000)
)
.fillMaxWidth()
.background(color = Color.White, shape = RoundedCornerShape(size = 10.dp))
.padding(all = 16.dp),
verticalAlignment = Alignment.CenterVertically
) {
if (task.stateTask) {
Box(
modifier = Modifier
.size(32.dp)
.background(
color = Colors.arrayColors[task.colorTask],
shape = RoundedCornerShape(size = 24.dp)
)
)
} else {
Box(
modifier = Modifier
.size(32.dp)
.border(
width = 1.dp,
color = MaterialTheme.colorScheme.primary,
shape = RoundedCornerShape(size = 24.dp)
)
)
}
Spacer(modifier = Modifier.size(10.dp))
Column {
Text(
text = task.descriptionTask,
fontSize = 16.sp,
textDecoration = if (task.stateTask) TextDecoration.LineThrough else TextDecoration.None,
fontWeight = FontWeight.SemiBold,
color = Color(0xFF676767)
)
Text(
text = task.categoryTask,
fontSize = 13.sp,
fontWeight = FontWeight.SemiBold,
color = Color.Gray
)
}
}
}
@Preview
@Composable
fun TaskCardPrev() {
TaskCard(
task = MockData.task(),
modifier = Modifier
)
}
@Composable
fun TaskCardList(
sectionTitle: String,
listOfTasks: List<Task>,
onTaskDone: (UUID, Boolean) -> Unit
) {
Spacer(modifier = Modifier.size(30.dp))
Text(
text = sectionTitle.uppercase(),
color = Color(0xFF676767),
modifier = Modifier
.padding(vertical = 8.dp, horizontal = 16.dp)
)
LazyColumn {
items(listOfTasks) { task ->
TaskCard(
task = task,
modifier = Modifier
.padding(vertical = 8.dp, horizontal = 16.dp)
.clickable {
onTaskDone(task.uid, !task.stateTask)
}
)
}
}
}
@Preview
@Composable
fun TaskCardListPrev() {
TaskCardList(
sectionTitle = "Minhas Tarefas",
listOfTasks = MockData.allTasks().allTasksNotDone(),
onTaskDone = { _, _ -> }
)
}
@Composable
fun TaskColorPicker(
color: Color,
selected: Int,
onSelected: (Color) -> Unit = {}
) {
Box(
contentAlignment = Alignment.Center,
modifier = Modifier
.size(32.dp)
.background(
color = color,
shape = RoundedCornerShape(size = 32.dp)
)
.clickable {
onSelected(color)
}
) {
if (Colors.arrayColors[selected] == color) {
Icon(
Icons.Default.Check,
contentDescription = "Check",
tint = Color.White
)
}
}
}
@Preview
@Composable
fun TaskColorPickerPrev() {
TaskColorPicker(color = Color.Blue, selected = 1)
}
// extensão
fun List<Task>.allTasksDone(): List<Task> {
return this.filter { it.stateTask }
}
fun List<Task>.allTasksNotDone(): List<Task> {
return this.filter { !it.stateTask }
}
class TaskRepository(val context: Context) {
private val db: AppDatabase by lazy {
Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java, "taskdb"
).build()
}
private val td = db.TaskDao()
suspend fun saveTask(task: Task) {
td.saveTask(task)
}
suspend fun updateTask(id: UUID, state: Boolean) {
td.updateTaskState(uid = id, stateTask = state)
}
fun getAllTasks(): Flow<List<Task>> {
return td.getAllTasks()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment