Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

belindas-closet-android_3_175_add-functionality-delete-button #195

Merged
merged 3 commits into from
Nov 27, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ object HttpRoutes {
const val PRODUCTS = "$BASE_URL/products"
const val PRODUCT = "$BASE_URL/products/{id}"
const val ARCHIVE = "$BASE_URL/products/archive"
const val DELETE = "$BASE_URL/products/remove"
const val LOGIN = "$BASE_URL/auth/login"
const val FORGOT_PASSWORD = "$BASE_URL/auth/forgot-password"
const val SIGNUP = "$BASE_URL/auth/signup"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.example.belindas_closet.data.network.auth

import com.example.belindas_closet.MainActivity
import com.example.belindas_closet.data.network.dto.auth_dto.DeleteRequest
import com.example.belindas_closet.data.network.dto.auth_dto.DeleteResponse
import io.ktor.client.HttpClient
import io.ktor.client.engine.android.Android
import io.ktor.client.plugins.contentnegotiation.ContentNegotiation
import io.ktor.client.plugins.logging.DEFAULT
import io.ktor.client.plugins.logging.LogLevel
import io.ktor.client.plugins.logging.Logger
import io.ktor.client.plugins.logging.Logging
import io.ktor.serialization.kotlinx.json.json
import kotlinx.serialization.json.Json

interface DeleteService {
suspend fun delete(deleteRequest: DeleteRequest) : DeleteResponse?

companion object {
fun create() : DeleteService {
return DeleteServiceImpl(
client = HttpClient(Android) {
install(ContentNegotiation) {
json(Json {
prettyPrint = true
isLenient = true
ignoreUnknownKeys = true
})
}
install (Logging) {
level = LogLevel.ALL
logger = Logger.DEFAULT
}
},
getToken = suspend {
MainActivity.getPref().getString("token", "") ?: ""
}
)
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.example.belindas_closet.data.network.auth

import com.example.belindas_closet.data.network.HttpRoutes
import com.example.belindas_closet.data.network.dto.auth_dto.DeleteRequest
import com.example.belindas_closet.data.network.dto.auth_dto.DeleteResponse
import io.ktor.client.HttpClient
import io.ktor.client.call.body
import io.ktor.client.plugins.ClientRequestException
import io.ktor.client.plugins.RedirectResponseException
import io.ktor.client.plugins.ServerResponseException
import io.ktor.client.request.delete
import io.ktor.client.request.header
import io.ktor.client.request.url
import io.ktor.http.ContentType
import io.ktor.http.HttpHeaders
import io.ktor.util.InternalAPI
import kotlinx.serialization.json.Json

class DeleteServiceImpl (
private val client: HttpClient,
private val getToken: suspend () -> String
) : DeleteService {
@OptIn(InternalAPI::class)
override suspend fun delete(deleteRequest: DeleteRequest): DeleteResponse? {
return try {
val token = getToken()
val response = client.delete {
url("${HttpRoutes.DELETE}/${deleteRequest.id}")
header(HttpHeaders.ContentType, ContentType.Application.Json.toString())
header(HttpHeaders.Authorization, "Bearer $token")
body = Json.encodeToString(DeleteRequest.serializer(), deleteRequest)
}
response.body()
} catch (e: RedirectResponseException) {
println("Error: ${e.response.status.description}")
null
} catch (e: ClientRequestException) {
println("Error: ${e.response.status.description}")
null
} catch (e: ServerResponseException) {
println("Error: ${e.response.status.description}")
null
} catch (e: Exception) {
println("Error: ${e.message}")
null
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.example.belindas_closet.data.network.dto.auth_dto

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class DeleteRequest(
@SerialName("id")
val id: String,

@SerialName("role")
val role: Role
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.example.belindas_closet.data.network.dto.auth_dto

import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable

@Serializable
data class DeleteResponse(
@SerialName("isHidden")
val isHidden: Boolean = false
)
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,9 @@ import com.example.belindas_closet.R
import com.example.belindas_closet.Routes
import com.example.belindas_closet.data.Datasource
import com.example.belindas_closet.data.network.auth.ArchiveService
import com.example.belindas_closet.data.network.auth.DeleteService
import com.example.belindas_closet.data.network.dto.auth_dto.ArchiveRequest
import com.example.belindas_closet.data.network.dto.auth_dto.DeleteRequest
import com.example.belindas_closet.data.network.dto.auth_dto.Role
import com.example.belindas_closet.model.Product
import com.example.belindas_closet.model.ProductSizes
Expand Down Expand Up @@ -186,13 +188,17 @@ fun UpdateIndividualProductCard(product: Product, navController: NavController)
}
if (isDelete) {
ConfirmationDialogIndividual(onConfirm = {
val hidden = MainActivity.getPref().getStringSet("hidden", mutableSetOf(product.productType.name))
hidden?.add(product.productType.name)
val editor = MainActivity.getPref().edit()
editor.putStringSet("hidden", hidden)
editor.apply()
navController.navigate(Routes.ProductDetail.route)
// TODO: Delete the product from the database
coroutineScope.launch {
val isDeleteSuccessful = delete(product.id, navController, current)
if (isDeleteSuccessful) {
val hidden = MainActivity.getPref().getStringSet("hidden", mutableSetOf(product.id))
hidden?.add(product.id)
val editor = MainActivity.getPref().edit()
editor.putStringSet("hidden", hidden)
editor.apply()
navController.navigate(Routes.ProductDetail.route)
}
}
// Remove the product from the database
isDelete = false
}, onDismiss = {
Expand Down Expand Up @@ -366,6 +372,7 @@ fun ConfirmationArchiveDialogIndividual(
}
}
}

@Composable
fun ConfirmSaveDialogIndividual(
onConfirm: () -> Unit,
Expand Down Expand Up @@ -457,6 +464,38 @@ fun ConfirmCancelDialogIndividual(
}
}

suspend fun delete(productId: String, navController: NavController, current: Context): Boolean {
return try {
val userRole = MainActivity.getPref().getString("userRole", Role.USER.name)?.let {
Role.valueOf(it) } ?: Role.USER
val deleteRequest = DeleteRequest(
id = productId,
role = Role.ADMIN
)
val deleteResponse = DeleteService.create().delete(deleteRequest)
if (userRole != Role.ADMIN) {
Toast.makeText(current, R.string.unauthorized_toast, Toast.LENGTH_SHORT).show()
false
} else if (productId.count() != 24) {
Toast.makeText(current, R.string.invalid_id, Toast.LENGTH_SHORT).show()
false
} else if (deleteResponse != null) {
MainActivity.getPref().edit().putBoolean("isHidden", deleteResponse.isHidden).apply()
navController.navigate(Routes.ProductDetail.route)
Toast.makeText(current, R.string.delete_successful_toast, Toast.LENGTH_SHORT).show()
true
} else {
Toast.makeText(current, R.string.delete_failed_toast, Toast.LENGTH_SHORT).show()
false
}
} catch (e: HttpException) {
e.printStackTrace()
println("Error: ${e.message}")
Toast.makeText(current, "Delete failed. Error: ${e.message}", Toast.LENGTH_SHORT).show()
false
}
}

suspend fun archive(productId: String, navController: NavController, current: Context) {
return try {
val archiveRequest = ArchiveRequest(
Expand All @@ -465,7 +504,7 @@ suspend fun archive(productId: String, navController: NavController, current: Co
)
val archiveResponse = ArchiveService.create().archive(archiveRequest)
if (productId.count() != 24) {
Toast.makeText(current, R.string.archive_invalid_id, Toast.LENGTH_SHORT).show()
Toast.makeText(current, R.string.invalid_id, Toast.LENGTH_SHORT).show()
} else if (archiveResponse != null) {
MainActivity.getPref().edit().putBoolean("isSold", archiveResponse.isSold).apply()
navController.navigate(Routes.ProductDetail.route)
Expand All @@ -478,4 +517,5 @@ suspend fun archive(productId: String, navController: NavController, current: Co
println("Error: ${e.message}")
Toast.makeText(current, "Archive failed. Error: ${e.message}", Toast.LENGTH_SHORT).show()
}
}
}

22 changes: 21 additions & 1 deletion app/src/main/java/com/example/belindas_closet/screen/Login.kt
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,10 @@ import com.example.belindas_closet.R
import com.example.belindas_closet.Routes
import com.example.belindas_closet.data.network.auth.LoginService
import com.example.belindas_closet.data.network.dto.auth_dto.LoginRequest
import com.example.belindas_closet.data.network.dto.auth_dto.Role
import kotlinx.coroutines.launch
import org.json.JSONObject
import java.util.Locale

@OptIn(ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class)
@Composable
Expand Down Expand Up @@ -307,6 +309,11 @@ suspend fun loginWithValidCredentials(email: String, password: String, navContro
if (loginResponse != null) {
val token = loginResponse.token
saveToken(token)

//Extract and store user role
val userRole = getUserRole(token)
MainActivity.getPref().edit().putString("userRole", userRole.name).apply()

MainActivity.getPref().edit().putString("token", loginResponse.token).apply()
navController.navigate(Routes.AdminView.route)
Toast.makeText(
Expand Down Expand Up @@ -344,6 +351,19 @@ fun getName(token: String): String? {
}
}

fun getUserRole(token: String): Role {
return try {
val payload = token.split(".")[1]
val decodedPayload = String(Base64.decode(payload, Base64.DEFAULT))
val jsonObject = JSONObject(decodedPayload)
val roleString = jsonObject.getString("role")
Role.valueOf(roleString.uppercase(Locale.ROOT))
} catch (e: Exception) {
e.printStackTrace()
Role.USER
}
}

fun saveToken(token: String) {
MainActivity.getPref().edit().putString("token", token).apply()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ fun ProductDetailList(products: List<Product>, navController: NavController) {
) {
items(products
.filter { it.productType.type == MainActivity.getProductType() }
.filter { !hidden!!.contains(it.productType.name) }) { product ->
.filter { !hidden!!.contains(it.id) }) { product ->
ProductDetailCard(product = product, navController = navController)
}
}
Expand Down
6 changes: 5 additions & 1 deletion app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,14 @@
<string name="update_save_confirm_text">Are you sure you want to save this product?</string>
<string name="update_cancel_confirm_text">Are you sure you want to cancel this edit?</string>
<string name="update_page_title">Update Product</string>
<string name="delete_successful_toast">Deletion successful!</string>
<string name="archive_successful_toast">Archival successful!</string>
<string name="archive_invalid_id">Invalid ID!</string>
<string name="unauthorized_toast">Unauthorized!</string>
<string name="invalid_id">Invalid ID!</string>
<string name="delete_failed_toast">Deletion failed, please try again!</string>
<string name="archive_failed_toast">Archival failed, please try again!</string>


<!-- Forgot Password Screen Strings -->
<string name="forgot_password">Forgot Password?</string>
<string name="forgot_password_button">Submit</string>
Expand Down