From 1f91dd0b9283c3cd8f37c66cc308aa1cacb1d65c Mon Sep 17 00:00:00 2001
From: Iftakhar Husan <AC5636@student.jamk.fi>
Date: Sat, 6 Jan 2024 20:33:40 +0200
Subject: [PATCH] Add siwpe gesture to edit and delete shopping item

---
 E13RoomShoppingList/.idea/gradle.xml          |   1 +
 E13RoomShoppingList/.idea/misc.xml            |   1 -
 .../e13roomshoppinglist/ShoppingListScreen.kt | 185 ++++++++++++++----
 3 files changed, 149 insertions(+), 38 deletions(-)

diff --git a/E13RoomShoppingList/.idea/gradle.xml b/E13RoomShoppingList/.idea/gradle.xml
index 32522c1..0897082 100644
--- a/E13RoomShoppingList/.idea/gradle.xml
+++ b/E13RoomShoppingList/.idea/gradle.xml
@@ -1,5 +1,6 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <project version="4">
+  <component name="GradleMigrationSettings" migrationVersion="1" />
   <component name="GradleSettings">
     <option name="linkedExternalProjectsSettings">
       <GradleProjectSettings>
diff --git a/E13RoomShoppingList/.idea/misc.xml b/E13RoomShoppingList/.idea/misc.xml
index 0ad17cb..8978d23 100644
--- a/E13RoomShoppingList/.idea/misc.xml
+++ b/E13RoomShoppingList/.idea/misc.xml
@@ -1,4 +1,3 @@
-<?xml version="1.0" encoding="UTF-8"?>
 <project version="4">
   <component name="ExternalStorageConfigurationManager" enabled="true" />
   <component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
diff --git a/E13RoomShoppingList/app/src/main/java/com/example/e13roomshoppinglist/ShoppingListScreen.kt b/E13RoomShoppingList/app/src/main/java/com/example/e13roomshoppinglist/ShoppingListScreen.kt
index da2ae25..43b113a 100644
--- a/E13RoomShoppingList/app/src/main/java/com/example/e13roomshoppinglist/ShoppingListScreen.kt
+++ b/E13RoomShoppingList/app/src/main/java/com/example/e13roomshoppinglist/ShoppingListScreen.kt
@@ -1,9 +1,14 @@
 package com.example.e13roomshoppinglist
 
 
+import androidx.compose.animation.animateColorAsState
+import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
 import androidx.compose.foundation.clickable
 import androidx.compose.foundation.horizontalScroll
 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.fillMaxSize
@@ -14,31 +19,46 @@ import androidx.compose.foundation.layout.width
 import androidx.compose.foundation.lazy.LazyColumn
 import androidx.compose.foundation.lazy.items
 import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.foundation.text.KeyboardOptions
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Add
 import androidx.compose.material.icons.filled.Delete
+import androidx.compose.material.icons.filled.Done
 import androidx.compose.material.icons.filled.Edit
+import androidx.compose.material.icons.outlined.Delete
+import androidx.compose.material.icons.outlined.Edit
 import androidx.compose.material3.AlertDialog
 import androidx.compose.material3.Button
+import androidx.compose.material3.DismissDirection
+import androidx.compose.material3.DismissState
+import androidx.compose.material3.DismissValue
 import androidx.compose.material3.Divider
+import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.FloatingActionButton
 import androidx.compose.material3.Icon
 import androidx.compose.material3.IconButton
 import androidx.compose.material3.RadioButton
 import androidx.compose.material3.Scaffold
+import androidx.compose.material3.SwipeToDismiss
 import androidx.compose.material3.Text
 import androidx.compose.material3.TextField
+import androidx.compose.material3.rememberDismissState
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.draw.scale
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.text.font.FontWeight
 import androidx.compose.ui.text.input.KeyboardType
+import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
 
 
+@OptIn(ExperimentalMaterial3Api::class)
 @Composable
 fun ShoppingListScreen(
     state: ShoppingListState,
@@ -50,7 +70,10 @@ fun ShoppingListScreen(
             FloatingActionButton(onClick = {
                 onEvent(ShoppingListEvent.OpenAddingNewShoppingListItemDialog)
             }) {
-                Icon(imageVector = Icons.Default.Add, contentDescription = "Add new shopping list item")
+                Icon(
+                    imageVector = Icons.Default.Add,
+                    contentDescription = "Add new shopping list item"
+                )
             }
         },
         modifier = Modifier.padding(16.dp)
@@ -84,45 +107,86 @@ fun ShoppingListScreen(
                     }
                 }
             }
-            Divider(thickness = 2.dp, color = Color.LightGray, modifier = Modifier.padding(bottom = 8.dp))
+            Divider(
+                thickness = 2.dp,
+                color = Color.LightGray,
+                modifier = Modifier.padding(bottom = 8.dp)
+            )
 
             LazyColumn(
                 contentPadding = padding,
                 modifier = Modifier.fillMaxSize(),
                 verticalArrangement = Arrangement.spacedBy(16.dp)
             ) {
-                items(state.shoppingListItems) { shoppingItem ->
-                    Row(modifier = Modifier.fillMaxWidth()) {
-                        Column(modifier = Modifier.weight(1f)) {
-                            Text(
-                                text = "${shoppingItem.name} - 🧮 ${shoppingItem.count}",
-                                fontSize = 18.sp
-                            )
-                            Text(text = "💶 ${shoppingItem.price}", fontSize = 16.sp)
-                        }
+                items(state.shoppingListItems, key = {shoppingListItem -> if (shoppingListItem.id!==null) shoppingListItem.id else 0}) { shoppingItem ->
+                    val dismissState = rememberDismissState(
+                        confirmValueChange = {
+                            when (it) {
+                                DismissValue.DismissedToEnd -> {
+                                    onEvent(ShoppingListEvent.DeleteShoppingListItem(shoppingItem))
+                                    onShowToast("Deleted ${shoppingItem.name} shopping list item via swipe gesture")
+                                    true
+                                }
+                                DismissValue.DismissedToStart -> {
+                                    onEvent(
+                                        ShoppingListEvent.OpenEditingShoppingListItem(
+                                            shoppingItem
+                                        )
+                                    )
+                                    onShowToast("Opened ${shoppingItem.name} shopping list item edit dialog via swipe gesture")
+                                    false
+                                }
+                                else -> {
+                                    onShowToast("Swipe gesture aborted")
+                                    false
+                                }
+                            }
+                        })
+                    SwipeToDismiss(
+                        state = dismissState,
+                        background = { SwipeBackground(dismissState = dismissState) },
+                        dismissContent = {
+                            Row(
+                                modifier = Modifier
+                                    .fillMaxWidth()
+                                    .clip(RoundedCornerShape(10.dp))
+                                    .background(Color.LightGray)
+                                    .padding(horizontal = 8.dp, vertical = 4.dp)
+                            ) {
+                                Column(modifier = Modifier.weight(1f)) {
+                                    Text(
+                                        text = "${shoppingItem.name} - 🧮 ${shoppingItem.count}",
+                                        fontSize = 18.sp
+                                    )
+                                    Text(text = "💶 ${shoppingItem.price}", fontSize = 16.sp)
+                                }
 
-                        IconButton(onClick = {
-                            onEvent(ShoppingListEvent.DeleteShoppingListItem(shoppingItem))
-                                .also {
-                                    onShowToast("Deleted shopping list item ${shoppingItem.name}")
+                                IconButton(onClick = {
+                                    onEvent(ShoppingListEvent.DeleteShoppingListItem(shoppingItem))
+                                        .also {
+                                            onShowToast("Deleted shopping list item ${shoppingItem.name}")
+                                        }
+                                }) {
+                                    Icon(
+                                        imageVector = Icons.Default.Delete,
+                                        contentDescription = "Delete Shopping List Item"
+                                    )
                                 }
-                        }) {
-                            Icon(
-                                imageVector = Icons.Default.Delete,
-                                contentDescription = "Delete Shopping List Item"
-                            )
-                        }
 
-                        IconButton(onClick = {
-                            onEvent(ShoppingListEvent.OpenEditingShoppingListItem(shoppingItem))
-                        }) {
-                            Icon(
-                                imageVector = Icons.Default.Edit,
-                                contentDescription = "Edit Shopping List Item"
-                            )
-                        }
-                    }
-                    Divider(thickness = 1.dp, color = Color.LightGray)
+                                IconButton(onClick = {
+                                    onEvent(
+                                        ShoppingListEvent.OpenEditingShoppingListItem(
+                                            shoppingItem
+                                        )
+                                    )
+                                }) {
+                                    Icon(
+                                        imageVector = Icons.Default.Edit,
+                                        contentDescription = "Edit Shopping List Item"
+                                    )
+                                }
+                            }
+                        })
                 }
                 item {
                     Row(
@@ -131,7 +195,11 @@ fun ShoppingListScreen(
                             .height(70.dp),
                         verticalAlignment = Alignment.CenterVertically
                     ) {
-                        Text(text = "Empty item to fill up room so that the last item's edit and delete button does not go under FAB.", fontSize = 12.sp, color = Color.Gray)
+                        Text(
+                            text = "Empty item to fill up room so that the last item's edit and delete button does not go under FAB.",
+                            fontSize = 12.sp,
+                            color = Color.Gray
+                        )
                     }
                 }
             }
@@ -200,15 +268,15 @@ fun AddShoppingListItemDialog(
         },
         confirmButton = {
             Button(onClick = {
-                if (state.newShoppingListItem.name.length<3){
+                if (state.newShoppingListItem.name.length < 3) {
                     onShowToast("Item Name is too short!")
                     return@Button
                 }
-                if (state.newShoppingListItem.count.toIntOrNull()===null){
+                if (state.newShoppingListItem.count.toIntOrNull() === null) {
                     onShowToast("Invalid value for Count! Failed to convert value to Int.")
                     return@Button
                 }
-                if (state.newShoppingListItem.price.toFloatOrNull()===null){
+                if (state.newShoppingListItem.price.toFloatOrNull() === null) {
                     onShowToast("Invalid value for Price! Failed to convert value to Float.")
                     return@Button
                 }
@@ -216,7 +284,11 @@ fun AddShoppingListItemDialog(
                 if (state.isAddingNewShoppingListItem) {
                     onEvent(ShoppingListEvent.InsertNewShoppingListItemToDb).also { onShowToast("Added new item to shopping list!") }
                 } else if (state.isEditingShoppingListItem) {
-                    onEvent(ShoppingListEvent.UpdateCurrentlyEditingShoppingListItem).also { onShowToast("Updated shopping list item ${state.currentlyEditingShoppingListItem?.name}!") }
+                    onEvent(ShoppingListEvent.UpdateCurrentlyEditingShoppingListItem).also {
+                        onShowToast(
+                            "Updated shopping list item ${state.currentlyEditingShoppingListItem?.name}!"
+                        )
+                    }
                 }
             }) {
                 Text(text = if (state.isEditingShoppingListItem) "Update" else "Insert")
@@ -234,4 +306,43 @@ fun AddShoppingListItemDialog(
             }
         }
     )
-}
\ No newline at end of file
+}
+
+@Composable
+@OptIn(ExperimentalMaterial3Api::class)
+fun SwipeBackground(dismissState: DismissState) {
+    val direction = dismissState.dismissDirection ?: return
+
+    val color by animateColorAsState(
+        when (dismissState.targetValue) {
+            DismissValue.Default -> Color.Transparent
+            DismissValue.DismissedToEnd -> Color.Red
+            DismissValue.DismissedToStart -> Color.Green
+        }, label = ""
+    )
+    val alignment = when (direction) {
+        DismissDirection.StartToEnd -> Alignment.CenterStart
+        DismissDirection.EndToStart -> Alignment.CenterEnd
+    }
+    val icon = when (direction) {
+        DismissDirection.StartToEnd -> Icons.Filled.Delete
+        DismissDirection.EndToStart -> Icons.Filled.Edit
+    }
+    val scale by animateFloatAsState(
+        if (dismissState.targetValue == DismissValue.Default) 0.8f else 1.25f, label = ""
+    )
+
+    Box(
+        Modifier
+            .fillMaxSize()
+            .background(color)
+            .padding(horizontal = 20.dp),
+        contentAlignment = alignment
+    ) {
+        Icon(
+            icon,
+            contentDescription = "Delete",
+            modifier = Modifier.scale(scale)
+        )
+    }
+}
-- 
GitLab