diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml
index 8d4e827..bbc02b5 100644
--- a/.idea/deploymentTargetDropDown.xml
+++ b/.idea/deploymentTargetDropDown.xml
@@ -12,6 +12,6 @@
-
+
\ No newline at end of file
diff --git a/.idea/discord.xml b/.idea/discord.xml
new file mode 100644
index 0000000..30bab2a
--- /dev/null
+++ b/.idea/discord.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/build.gradle b/app/build.gradle
index f2b037a..c67caad 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -3,6 +3,7 @@ plugins {
id 'org.jetbrains.kotlin.android'
id 'org.jetbrains.kotlin.kapt'
id 'dagger.hilt.android.plugin'
+ id 'com.google.android.libraries.mapsplatform.secrets-gradle-plugin'
}
android {
@@ -55,13 +56,20 @@ dependencies {
implementation 'androidx.activity:activity-compose:1.7.1'
implementation platform('androidx.compose:compose-bom:2022.10.00')
implementation 'androidx.compose.ui:ui'
+ implementation "androidx.compose.ui:ui-util"
implementation 'androidx.compose.ui:ui-graphics'
implementation 'androidx.compose.ui:ui-tooling-preview'
implementation 'androidx.compose.material3:material3'
+ implementation 'androidx.compose.material:material:1.4.3'
// compose
implementation 'androidx.navigation:navigation-compose:2.5.3'
implementation 'androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1'
+ implementation 'androidx.compose.foundation:foundation:1.4.3'
+ implementation 'com.google.accompanist:accompanist-navigation-animation:0.30.0'
+ implementation 'androidx.compose.animation:animation-graphics:1.4.3'
+ implementation 'androidx.compose.animation:animation:1.4.3'
+ implementation 'androidx.compose.animation:animation-core:1.4.3'
//lifecycle
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1'
@@ -85,6 +93,7 @@ dependencies {
// Dagger - Hilt
implementation 'com.google.dagger:hilt-android:2.45'
+ implementation 'com.google.android.gms:play-services-location:21.0.1'
kapt 'com.google.dagger:hilt-android-compiler:2.45'
kapt 'androidx.hilt:hilt-compiler:1.0.0'
implementation "androidx.hilt:hilt-navigation-compose:1.0.0"
@@ -96,6 +105,25 @@ dependencies {
implementation 'com.squareup.moshi:moshi:1.14.0'
implementation 'com.squareup.moshi:moshi-kotlin:1.14.0'
+ // Pager
+ implementation "com.google.accompanist:accompanist-pager:0.23.1"
+
+ // Maps
+ implementation 'com.google.maps.android:maps-compose:2.10.0'
+ implementation 'com.google.android.gms:play-services-maps:18.1.0'
+ implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-play-services:1.5.2'
+ implementation 'com.google.android.gms:play-services-location:21.0.1'
+
+ // CameraX
+ implementation "androidx.camera:camera-camera2:1.2.3"
+ implementation "androidx.camera:camera-lifecycle:1.2.3"
+ implementation "androidx.camera:camera-view:1.3.0-alpha07"
+
+ // Room
+ implementation "androidx.room:room-ktx:2.5.1"
+ kapt "androidx.room:room-compiler:2.5.1"
+ implementation "androidx.room:room-paging:2.5.1"
+
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.5'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index a8a7842..3194540 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -10,7 +10,12 @@
+
+
+
+
@@ -33,6 +39,10 @@
+
+
+ AnimatedNavHost(
+ navController = navController,
+ startDestination = Splash.route,
+ modifier = Modifier
+ .padding(innerPadding),
+
+ ) {
+
+ composable(
+ route = Splash.route,
+ enterTransition = Splash.enterTransition,
+ exitTransition = Splash.exitTransition
+ ) {
+ SplashScreen(navController = navController)
+ }
+
+ composable(
+ route = OnBoarding.route,
+ enterTransition = {
+ slideIntoContainer(AnimatedContentScope.SlideDirection.Left, animationSpec = tween(700))
+ },
+ exitTransition = OnBoarding.exitTransition
+ ) {
+ OnBoardingScreen(navController = navController)
+ }
+
+ composable(SignIn.route) {
+ SignInScreen(navController = navController)
+ }
+
+ composable(SignUp.route) {
+ SignUpScreen(navController = navController)
+ }
+
+ composable(
+ route = Main.route + "/{page}",
+ enterTransition = Main.enterTransition,
+ exitTransition = Main.exitTransition,
+ arguments = listOf(
+ navArgument("page") {
+ type = NavType.IntType
+ defaultValue = 0
+ }
+ )
+ ) { navBackStackEntry ->
+ val page = navBackStackEntry.arguments?.getInt("page")
+ if (page != null) {
+ MainScreen(navController = navController, page = page)
+ }
+ }
+
+ composable(
+ route = Home.route,
+ enterTransition = Home.enterTransition,
+ exitTransition = Home.exitTransition
+ ) {
+ HomeScreen(navController = navController)
+ }
+
+ composable(
+ route = Article.route,
+ enterTransition = Article.enterTransition,
+ exitTransition = Article.exitTransition
+ ) {
+ ArticleScreen(navController = navController)
+ }
+
+ composable(
+ route = SingleArticle.route + "/{idArticle}",
+ arguments = listOf(
+ navArgument("idArticle") {
+ type = NavType.IntType
+ defaultValue = 1
+ }
+ )
+ ) { navBackStackEntry ->
+ val idArticle = navBackStackEntry.arguments?.getInt("idArticle")
+
+ if(idArticle != null) {
+ SingleArticleScreen(idArticle = idArticle, navController = navController)
+ }
+ }
+
+ composable(Forum.route) {
+ ForumScreen(navController = navController)
+ }
+
+ composable(
+ route = SingleForum.route + "/{forumId}",
+ arguments = listOf(
+ navArgument("forumId") {
+ type = NavType.IntType
+ defaultValue = 1
+ }
+ )
+ ) { navBackStackEntry ->
+ val forumId = navBackStackEntry.arguments?.getInt("forumId")
+ if (forumId != null) {
+ ForumSingleScreen(navController = navController, forumId = forumId)
+ }
+ }
+
+ composable(
+ route = CreateForum.route
+ ) {
+ ForumCreateScreen(navController = navController)
+ }
+
+ composable(Profile.route) {
+ ProfileUserScreen(navController = navController)
+ }
+
+ composable(
+ route = Setting.route,
+ enterTransition = Setting.enterTransition,
+ exitTransition = Setting.exitTransition
+ ) {
+ SettingScreen(navController = navController)
+ }
+
+ composable(Catalog.route) {
+ CatalogScreen(navController = navController)
+ }
+
+ composable(
+ route = SingleCatalog.route + "/{componentJson}",
+ arguments = listOf(
+ navArgument("componentJson") {
+ type = NavType.StringType
+ defaultValue = "U fucked up"
+ }
+ )
+ ) { navBackStackEntry ->
+ val componentJson = navBackStackEntry.arguments?.getString("componentJson")
+
+ if (componentJson != null) {
+ CatalogSingleComponentScreen(componentJson = componentJson, navController = navController)
+ }
+ }
+
+ composable(
+ route = Camera.route,
+ enterTransition = Camera.enterTransition,
+ exitTransition = Camera.exitTransition
+ ) {
+ CameraScreen(navController = navController)
+ }
+
+ composable(
+ route = DetectionResult.route + "/{uri}/{result}",
+ arguments = listOf(
+ navArgument("uri") {
+ type = NavType.StringType
+ defaultValue = "Image to sent doesn't exist"
+ },
+ navArgument("result") {
+ type = NavType.StringType
+ defaultValue = "Nothing to predict"
+ }
+ )
+ ) { navBackStackEntry ->
+ val uri = navBackStackEntry.arguments?.getString("uri")
+ val result = navBackStackEntry.arguments?.getString("result")
+
+ if (uri != null && result != null) {
+ DetectionResultScreen(stringUri = uri, detectionResult = result, navController = navController)
+ }
+
+ }
+
+ composable(Maps.route) {
+ MapsScreen(navController = navController)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/capstone/techwasmark02/data/local/database/FavoriteArticleDatabase.kt b/app/src/main/java/com/capstone/techwasmark02/data/local/database/FavoriteArticleDatabase.kt
new file mode 100644
index 0000000..a50a4f4
--- /dev/null
+++ b/app/src/main/java/com/capstone/techwasmark02/data/local/database/FavoriteArticleDatabase.kt
@@ -0,0 +1,14 @@
+package com.capstone.techwasmark02.data.local.database
+
+import androidx.room.Database
+import androidx.room.RoomDatabase
+import com.capstone.techwasmark02.data.local.database.dao.FavoriteArticleEntityDao
+import com.capstone.techwasmark02.data.local.database.entity.FavoriteArticleEntity
+
+@Database(
+ entities = [FavoriteArticleEntity::class],
+ version = 1
+)
+abstract class FavoriteArticleDatabase: RoomDatabase() {
+ abstract val favoriteArticleDao: FavoriteArticleEntityDao
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/capstone/techwasmark02/data/local/database/dao/FavoriteArticleEntityDao.kt b/app/src/main/java/com/capstone/techwasmark02/data/local/database/dao/FavoriteArticleEntityDao.kt
new file mode 100644
index 0000000..4ec782d
--- /dev/null
+++ b/app/src/main/java/com/capstone/techwasmark02/data/local/database/dao/FavoriteArticleEntityDao.kt
@@ -0,0 +1,28 @@
+package com.capstone.techwasmark02.data.local.database.dao
+
+import androidx.room.Dao
+import androidx.room.Delete
+import androidx.room.Query
+import androidx.room.Upsert
+import com.capstone.techwasmark02.data.local.database.entity.FavoriteArticleEntity
+import kotlinx.coroutines.flow.Flow
+
+
+@Dao
+interface FavoriteArticleEntityDao {
+
+ @Upsert
+ suspend fun upsertFavoriteArticle(favoriteArticle: FavoriteArticleEntity)
+
+ @Delete
+ suspend fun deleteFavoriteArticle(
+ favoriteArticle: FavoriteArticleEntity
+ )
+
+ @Query("SELECT * FROM fav_article_entity")
+ fun getFavoriteArticles(): Flow>
+
+ @Query("SELECT * FROM fav_article_entity WHERE id = :id")
+ fun getFavoriteArticleById(id: Int): Flow
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/capstone/techwasmark02/data/local/database/entity/FavoriteArticleEntity.kt b/app/src/main/java/com/capstone/techwasmark02/data/local/database/entity/FavoriteArticleEntity.kt
new file mode 100644
index 0000000..daa2776
--- /dev/null
+++ b/app/src/main/java/com/capstone/techwasmark02/data/local/database/entity/FavoriteArticleEntity.kt
@@ -0,0 +1,14 @@
+package com.capstone.techwasmark02.data.local.database.entity
+
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+
+@Entity( tableName = "fav_article_entity" )
+data class FavoriteArticleEntity(
+ @PrimaryKey
+ val id: Int,
+ val name: String,
+ val desc: String,
+ val articleImageURL: String,
+ val componentId: Int
+)
diff --git a/app/src/main/java/com/capstone/techwasmark02/data/mappers/Mappers.kt b/app/src/main/java/com/capstone/techwasmark02/data/mappers/Mappers.kt
index 3f292d3..307683f 100644
--- a/app/src/main/java/com/capstone/techwasmark02/data/mappers/Mappers.kt
+++ b/app/src/main/java/com/capstone/techwasmark02/data/mappers/Mappers.kt
@@ -1,5 +1,7 @@
package com.capstone.techwasmark02.data.mappers
+import com.capstone.techwasmark02.data.local.database.entity.FavoriteArticleEntity
+import com.capstone.techwasmark02.data.model.FavoriteArticle
import com.capstone.techwasmark02.data.model.UserSession
import com.capstone.techwasmark02.data.remote.response.LoginResult
@@ -8,4 +10,24 @@ fun LoginResult.toUserSession(): UserSession {
userLoginToken = token,
userNameId = userId
)
-}
\ No newline at end of file
+}
+
+fun FavoriteArticleEntity.toFavoriteArticle(): FavoriteArticle {
+ return FavoriteArticle(
+ id = id,
+ name = name,
+ desc = desc,
+ imageURL = articleImageURL,
+ compId = componentId
+ )
+}
+
+fun FavoriteArticle.toFavoriteArticleEntity(): FavoriteArticleEntity {
+ return FavoriteArticleEntity(
+ id = id,
+ name = name,
+ componentId = compId,
+ articleImageURL = imageURL,
+ desc = desc
+ )
+}
diff --git a/app/src/main/java/com/capstone/techwasmark02/data/model/FavoriteArticle.kt b/app/src/main/java/com/capstone/techwasmark02/data/model/FavoriteArticle.kt
new file mode 100644
index 0000000..3ab5a23
--- /dev/null
+++ b/app/src/main/java/com/capstone/techwasmark02/data/model/FavoriteArticle.kt
@@ -0,0 +1,9 @@
+package com.capstone.techwasmark02.data.model
+
+data class FavoriteArticle(
+ val id: Int,
+ val name: String,
+ val imageURL: String,
+ val compId: Int,
+ val desc: String
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/capstone/techwasmark02/data/model/ForumCommentInfo.kt b/app/src/main/java/com/capstone/techwasmark02/data/model/ForumCommentInfo.kt
new file mode 100644
index 0000000..b6704a9
--- /dev/null
+++ b/app/src/main/java/com/capstone/techwasmark02/data/model/ForumCommentInfo.kt
@@ -0,0 +1,6 @@
+package com.capstone.techwasmark02.data.model
+
+data class ForumCommentInfo(
+ val comment: String,
+ val forumID: Int
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/capstone/techwasmark02/data/model/ForumToCreateInfo.kt b/app/src/main/java/com/capstone/techwasmark02/data/model/ForumToCreateInfo.kt
new file mode 100644
index 0000000..e7f209a
--- /dev/null
+++ b/app/src/main/java/com/capstone/techwasmark02/data/model/ForumToCreateInfo.kt
@@ -0,0 +1,9 @@
+package com.capstone.techwasmark02.data.model
+
+data class ForumToCreateInfo(
+ val category: String,
+ val content: String,
+ val imageUrl: String,
+ val location: String,
+ val title: String
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/capstone/techwasmark02/data/remote/apiService/TechwasArticleApi.kt b/app/src/main/java/com/capstone/techwasmark02/data/remote/apiService/TechwasArticleApi.kt
new file mode 100644
index 0000000..047540e
--- /dev/null
+++ b/app/src/main/java/com/capstone/techwasmark02/data/remote/apiService/TechwasArticleApi.kt
@@ -0,0 +1,35 @@
+package com.capstone.techwasmark02.data.remote.apiService
+
+import com.capstone.techwasmark02.data.remote.response.AllArticleResultResponse
+import com.capstone.techwasmark02.data.remote.response.ArticleResultResponse
+import com.capstone.techwasmark02.data.remote.response.SingleArticleResponse
+import retrofit2.http.GET
+import retrofit2.http.Header
+import retrofit2.http.Path
+import retrofit2.http.Query
+
+interface TechwasArticleApi {
+
+ @GET("/allArticle")
+ suspend fun getAllArticle(): AllArticleResultResponse
+
+ @GET("/article/{id}")
+ suspend fun getArticleByComponentId(
+ @Header("Authorization") token: String,
+ @Query("compid") compid: Int
+ ): ArticleResultResponse
+
+ @GET("/article/id/{id}")
+ suspend fun getArticleById(
+ @Path("id") id: Int
+ ): SingleArticleResponse
+
+ @GET("/article/name/{name}")
+ suspend fun getArticleByName(
+ @Path("name") name: String
+ ): ArticleResultResponse
+
+ companion object {
+ const val BASE_URL = "https://backend-api-56g32wdmqa-uc.a.run.app/"
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/capstone/techwasmark02/data/remote/apiService/TechwasComponentApi.kt b/app/src/main/java/com/capstone/techwasmark02/data/remote/apiService/TechwasComponentApi.kt
new file mode 100644
index 0000000..ba3dc1e
--- /dev/null
+++ b/app/src/main/java/com/capstone/techwasmark02/data/remote/apiService/TechwasComponentApi.kt
@@ -0,0 +1,31 @@
+package com.capstone.techwasmark02.data.remote.apiService
+
+import com.capstone.techwasmark02.data.remote.response.ComponentResponse
+import com.capstone.techwasmark02.data.remote.response.ComponentsResponse
+import com.capstone.techwasmark02.data.remote.response.UsableComponentsResponse
+import retrofit2.http.GET
+import retrofit2.http.Header
+import retrofit2.http.Path
+
+interface TechwasComponentApi {
+
+ @GET("components/")
+ suspend fun fetchComponents(
+ @Header("Authorization") token: String,
+ ) : ComponentsResponse
+
+ @GET("components/{id}")
+ suspend fun fetchComponentById(
+ @Header("Authorization") token: String,
+ @Path("id") id: Int
+ ) : ComponentResponse
+
+ @GET("smallparts/bycompid/{compid}")
+ suspend fun fetchUsableComponents(
+ @Path("compid") compid: Int
+ ) : UsableComponentsResponse
+
+ companion object {
+ const val BASE_URL = "https://backend-api-56g32wdmqa-uc.a.run.app/"
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/capstone/techwasmark02/data/remote/apiService/TechwasForumApi.kt b/app/src/main/java/com/capstone/techwasmark02/data/remote/apiService/TechwasForumApi.kt
new file mode 100644
index 0000000..707dc36
--- /dev/null
+++ b/app/src/main/java/com/capstone/techwasmark02/data/remote/apiService/TechwasForumApi.kt
@@ -0,0 +1,63 @@
+package com.capstone.techwasmark02.data.remote.apiService
+
+import com.capstone.techwasmark02.data.model.ForumCommentInfo
+import com.capstone.techwasmark02.data.model.ForumToCreateInfo
+import com.capstone.techwasmark02.data.remote.response.CreateForumResponse
+import com.capstone.techwasmark02.data.remote.response.ForumCommentResponse
+import com.capstone.techwasmark02.data.remote.response.ForumResponse
+import com.capstone.techwasmark02.data.remote.response.ImageUrlResponse
+import com.capstone.techwasmark02.data.remote.response.PostForumCommentResponse
+import okhttp3.MultipartBody
+import retrofit2.http.Body
+import retrofit2.http.GET
+import retrofit2.http.Header
+import retrofit2.http.Multipart
+import retrofit2.http.POST
+import retrofit2.http.Part
+import retrofit2.http.Path
+
+interface TechwasForumApi {
+
+ @GET("/forum/getall")
+ suspend fun fetchAllForum(): ForumResponse
+
+ @GET("/forum/id/{id}")
+ suspend fun fetchForumById(
+ @Header("Authorization") token: String,
+ @Path("id") id: Int
+ ): ForumResponse
+
+ @GET("/forum/category/{category}")
+ suspend fun fetchForumByCategory(
+ @Path("category") category: String
+ ): ForumResponse
+
+ @POST("/forum/post")
+ suspend fun createNewForum(
+ @Header("Authorization") token: String,
+ @Body forumToCreateInfo: ForumToCreateInfo
+ ): CreateForumResponse
+
+ @GET("/comments/byforumid/{forumid}")
+ suspend fun fetchForumComment(
+ @Path("forumid") forumid: Int
+ ): ForumCommentResponse
+
+ @POST("/comments/post")
+ suspend fun postForumComment(
+ @Header("Authorization") token: String,
+ @Body forumCommentInfo: ForumCommentInfo
+ ): PostForumCommentResponse
+
+
+ @Multipart
+ @POST("/forum/upimagepost")
+ suspend fun uploadAndGetImage(
+ @Header("Authorization") token: String,
+ @Part imageFile: MultipartBody.Part
+ ): ImageUrlResponse
+
+ companion object {
+ const val BASE_URL = "https://backend-api-56g32wdmqa-uc.a.run.app/"
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/capstone/techwasmark02/data/remote/apiService/TechwasPredictionApi.kt b/app/src/main/java/com/capstone/techwasmark02/data/remote/apiService/TechwasPredictionApi.kt
index e74f63b..4d139fc 100644
--- a/app/src/main/java/com/capstone/techwasmark02/data/remote/apiService/TechwasPredictionApi.kt
+++ b/app/src/main/java/com/capstone/techwasmark02/data/remote/apiService/TechwasPredictionApi.kt
@@ -1,5 +1,7 @@
package com.capstone.techwasmark02.data.remote.apiService
+
+import com.capstone.techwasmark02.data.remote.response.DetectionsResultResponse
import okhttp3.MultipartBody
import retrofit2.http.Multipart
import retrofit2.http.POST
@@ -11,9 +13,10 @@ interface TechwasPredictionApi {
@POST("predict/")
suspend fun predict(
@Part imageFile: MultipartBody.Part
- ) : String
+ ) : DetectionsResultResponse
companion object {
- const val BASE_URL = "https://e-waste-model-deployment-1gb-fwd5gpydiq-uc.a.run.app/"
+ const val BASE_URL = "http://35.222.88.99/"
+// const val BASE_URL = "https://e-waste-model-deployment-1gb-fwd5gpydiq-uc.a.run.app/"
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/capstone/techwasmark02/data/remote/apiService/TechwasUserApi.kt b/app/src/main/java/com/capstone/techwasmark02/data/remote/apiService/TechwasUserApi.kt
index cc4f41d..0e9767e 100644
--- a/app/src/main/java/com/capstone/techwasmark02/data/remote/apiService/TechwasUserApi.kt
+++ b/app/src/main/java/com/capstone/techwasmark02/data/remote/apiService/TechwasUserApi.kt
@@ -3,6 +3,7 @@ package com.capstone.techwasmark02.data.remote.apiService
import com.capstone.techwasmark02.data.model.UserLoginInfo
import com.capstone.techwasmark02.data.model.UserRegisterInfo
import com.capstone.techwasmark02.data.remote.response.UserLoginResponse
+import com.capstone.techwasmark02.data.remote.response.UserRegisterResponse
import retrofit2.http.Body
import retrofit2.http.POST
@@ -16,12 +17,10 @@ interface TechwasUserApi {
@POST("user/signup")
suspend fun register(
@Body userRegisterInfo: UserRegisterInfo
- ) : String
-
-
+ ) : UserRegisterResponse
companion object {
- const val BASE_URL = "https://the-prophecy-fwd5gpydiq-uc.a.run.app/"
+ const val BASE_URL = "https://backend-api-56g32wdmqa-uc.a.run.app/"
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/capstone/techwasmark02/data/remote/response/ArticleResultResponse.kt b/app/src/main/java/com/capstone/techwasmark02/data/remote/response/ArticleResultResponse.kt
new file mode 100644
index 0000000..b7a4b03
--- /dev/null
+++ b/app/src/main/java/com/capstone/techwasmark02/data/remote/response/ArticleResultResponse.kt
@@ -0,0 +1,29 @@
+package com.capstone.techwasmark02.data.remote.response
+
+import com.google.gson.annotations.SerializedName
+
+data class ArticleResultResponse(
+ val articleList: List,
+ val error: String?,
+ val message: String?,
+)
+
+data class AllArticleResultResponse(
+ val componentList: List,
+ val error: String?,
+ val message: String?,
+)
+
+data class ArticleList(
+ @SerializedName("componentId")
+ val componentId: Int?,
+ @SerializedName("articleImageURL")
+ val articleImageURL: String?,
+ @SerializedName("name")
+ val name: String?,
+ @SerializedName("id")
+ val id: Int?,
+ @SerializedName("desc")
+ val desc: String?,
+)
+
diff --git a/app/src/main/java/com/capstone/techwasmark02/data/remote/response/ComponentResponse.kt b/app/src/main/java/com/capstone/techwasmark02/data/remote/response/ComponentResponse.kt
new file mode 100644
index 0000000..6363750
--- /dev/null
+++ b/app/src/main/java/com/capstone/techwasmark02/data/remote/response/ComponentResponse.kt
@@ -0,0 +1,28 @@
+package com.capstone.techwasmark02.data.remote.response
+
+data class ComponentResponse(
+ val componentList: ComponentList,
+ val error: String,
+ val message: String
+)
+
+data class ComponentList(
+ val desc: String,
+ val example: String,
+ val id: Int,
+ val name: String
+)
+
+data class UsableComponentsResponse(
+ val error: String,
+ val message: String,
+ val smallParts: List
+)
+
+data class SmallPart(
+ val compID: Int,
+ val description: String,
+ val id: Int,
+ val imageURL: String,
+ val name: String
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/capstone/techwasmark02/data/remote/response/ComponentsResponse.kt b/app/src/main/java/com/capstone/techwasmark02/data/remote/response/ComponentsResponse.kt
new file mode 100644
index 0000000..0833b79
--- /dev/null
+++ b/app/src/main/java/com/capstone/techwasmark02/data/remote/response/ComponentsResponse.kt
@@ -0,0 +1,14 @@
+package com.capstone.techwasmark02.data.remote.response
+
+data class ComponentsResponse(
+ val components: List,
+ val error: String,
+ val message: String
+)
+
+data class Component(
+ val desc: String,
+ val id: Int,
+ val imageExample: String,
+ val name: String
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/capstone/techwasmark02/data/remote/response/DetectionResultResponse.kt b/app/src/main/java/com/capstone/techwasmark02/data/remote/response/DetectionResultResponse.kt
new file mode 100644
index 0000000..c8a9ea4
--- /dev/null
+++ b/app/src/main/java/com/capstone/techwasmark02/data/remote/response/DetectionResultResponse.kt
@@ -0,0 +1,21 @@
+package com.capstone.techwasmark02.data.remote.response
+
+import com.google.gson.annotations.SerializedName
+
+data class DetectionsResultResponse(
+ val Image_Url: String,
+ val predictions: List,
+ val time_taken: String
+)
+
+data class Prediction(
+ @SerializedName("Components ID")
+ val componentId: Int,
+ @SerializedName("Components Name")
+ val componentName: String,
+ @SerializedName("Components Value")
+ val componentValue: Double
+)
+
+
+
diff --git a/app/src/main/java/com/capstone/techwasmark02/data/remote/response/ForumResponse.kt b/app/src/main/java/com/capstone/techwasmark02/data/remote/response/ForumResponse.kt
new file mode 100644
index 0000000..74fcf28
--- /dev/null
+++ b/app/src/main/java/com/capstone/techwasmark02/data/remote/response/ForumResponse.kt
@@ -0,0 +1,60 @@
+package com.capstone.techwasmark02.data.remote.response
+
+import com.google.gson.annotations.SerializedName
+
+data class ForumResponse(
+ val error: String,
+ val forum: List,
+ val message: String
+)
+
+data class Forum(
+ @SerializedName("Postedby")
+ val postedBy: String,
+ val category: String,
+ val content: String,
+ val id: Int,
+ val imageURL: String,
+ val likes: Any,
+ val location: String,
+ val title: String
+)
+
+data class ForumCommentResponse(
+ val article: List,
+ val error: String,
+ val message: String
+)
+
+data class Article(
+ val comment: String,
+ val forumID: Int,
+ val id: Int,
+ val replyFrom: Int,
+ val userID: String,
+ val username: String
+)
+
+data class PostForumCommentResponse(
+ val commentInfo: CommentInfo,
+ val error: String,
+ val message: String
+)
+
+data class CommentInfo(
+ val Poster: String,
+ val PosterID: Int,
+ val comment: String,
+ val forumID: Int
+)
+
+data class CreateForumResponse(
+ val error: String,
+ val message: String
+)
+
+data class ImageUrlResponse(
+ val error: String,
+ val imgURL: String,
+ val message: String
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/capstone/techwasmark02/data/remote/response/SingleArticleResponse.kt b/app/src/main/java/com/capstone/techwasmark02/data/remote/response/SingleArticleResponse.kt
new file mode 100644
index 0000000..2ec2174
--- /dev/null
+++ b/app/src/main/java/com/capstone/techwasmark02/data/remote/response/SingleArticleResponse.kt
@@ -0,0 +1,36 @@
+package com.capstone.techwasmark02.data.remote.response
+
+import com.google.gson.annotations.SerializedName
+
+data class SingleArticleResponse(
+
+ @field:SerializedName("error")
+ val error: String? = null,
+
+ @field:SerializedName("message")
+ val message: String? = null,
+
+ @field:SerializedName("article")
+ val article: List? = null
+)
+
+data class ArticleItem(
+
+ @field:SerializedName("componentID")
+ val componentID: Int? = null,
+
+ @field:SerializedName("articleImageURL")
+ val articleImageURL: String? = null,
+
+ @field:SerializedName("name")
+ val name: String? = null,
+
+ @field:SerializedName("description")
+ val description: String? = null,
+
+ @field:SerializedName("id")
+ val id: Int? = null,
+
+ @field:SerializedName("componentName")
+ val componentName: String? = null
+)
diff --git a/app/src/main/java/com/capstone/techwasmark02/data/remote/response/UserRegisterResponse.kt b/app/src/main/java/com/capstone/techwasmark02/data/remote/response/UserRegisterResponse.kt
new file mode 100644
index 0000000..79aefb1
--- /dev/null
+++ b/app/src/main/java/com/capstone/techwasmark02/data/remote/response/UserRegisterResponse.kt
@@ -0,0 +1,18 @@
+package com.capstone.techwasmark02.data.remote.response
+
+import com.google.gson.annotations.SerializedName
+
+data class UserRegisterResponse(
+ val error: String,
+ val message: String,
+ val registerResult: RegisterResult,
+)
+
+data class RegisterResult(
+ val signupToken: SignUpToken,
+)
+
+data class SignUpToken(
+ @SerializedName("access token")
+ val accessToken: String
+)
\ No newline at end of file
diff --git a/app/src/main/java/com/capstone/techwasmark02/di/AppModule.kt b/app/src/main/java/com/capstone/techwasmark02/di/AppModule.kt
index d9c151c..58585ec 100644
--- a/app/src/main/java/com/capstone/techwasmark02/di/AppModule.kt
+++ b/app/src/main/java/com/capstone/techwasmark02/di/AppModule.kt
@@ -4,8 +4,17 @@ import android.content.Context
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.preferencesDataStore
+import androidx.room.Room
+import com.capstone.techwasmark02.data.local.database.FavoriteArticleDatabase
+import com.capstone.techwasmark02.data.remote.apiService.TechwasArticleApi
+import com.capstone.techwasmark02.data.remote.apiService.TechwasComponentApi
+import com.capstone.techwasmark02.data.remote.apiService.TechwasForumApi
import com.capstone.techwasmark02.data.remote.apiService.TechwasPredictionApi
import com.capstone.techwasmark02.data.remote.apiService.TechwasUserApi
+import com.capstone.techwasmark02.repository.FavoriteArticleRepository
+import com.capstone.techwasmark02.repository.TechwasArticleRepository
+import com.capstone.techwasmark02.repository.TechwasComponentApiRepository
+import com.capstone.techwasmark02.repository.TechwasForumApiRepository
import com.capstone.techwasmark02.repository.TechwasUserApiRepository
import dagger.Module
import dagger.Provides
@@ -61,6 +70,57 @@ object AppModule {
return retrofit.create(TechwasPredictionApi::class.java)
}
+ @Provides
+ @Singleton
+ fun provideTechwasArticleApi(): TechwasArticleApi {
+ val loggingInterceptor =
+ HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)
+ val client = OkHttpClient.Builder()
+ .addInterceptor(loggingInterceptor)
+ .build()
+ val retrofit = Retrofit.Builder()
+ .baseUrl(TechwasArticleApi.BASE_URL)
+ .addConverterFactory(ScalarsConverterFactory.create())
+ .addConverterFactory(GsonConverterFactory.create())
+ .client(client)
+ .build()
+ return retrofit.create(TechwasArticleApi::class.java)
+ }
+
+ @Provides
+ @Singleton
+ fun provideTechwasComponentApi(): TechwasComponentApi {
+ val loggingInterceptor =
+ HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)
+ val client = OkHttpClient.Builder()
+ .addInterceptor(loggingInterceptor)
+ .build()
+ val retrofit = Retrofit.Builder()
+ .baseUrl(TechwasComponentApi.BASE_URL)
+ .addConverterFactory(ScalarsConverterFactory.create())
+ .addConverterFactory(GsonConverterFactory.create())
+ .client(client)
+ .build()
+ return retrofit.create(TechwasComponentApi::class.java)
+ }
+
+ @Provides
+ @Singleton
+ fun provideTechwasForumApi(): TechwasForumApi {
+ val loggingInterceptor =
+ HttpLoggingInterceptor().setLevel(HttpLoggingInterceptor.Level.BODY)
+ val client = OkHttpClient.Builder()
+ .addInterceptor(loggingInterceptor)
+ .build()
+ val retrofit = Retrofit.Builder()
+ .baseUrl(TechwasForumApi.BASE_URL)
+ .addConverterFactory(ScalarsConverterFactory.create())
+ .addConverterFactory(GsonConverterFactory.create())
+ .client(client)
+ .build()
+ return retrofit.create(TechwasForumApi::class.java)
+ }
+
@Provides
@Singleton
fun provideDataStorePreferences(
@@ -74,4 +134,34 @@ object AppModule {
api: TechwasUserApi
) = TechwasUserApiRepository(api)
+ @Provides
+ fun provideTechwasArticleRepository(
+ apiArticle: TechwasArticleApi
+ ) = TechwasArticleRepository(apiArticle)
+
+ @Provides
+ fun provideTechwasComponentApiRepository(
+ api: TechwasComponentApi
+ ) = TechwasComponentApiRepository(api)
+
+ @Provides
+ fun provideTechwasForumApiRepository(
+ api: TechwasForumApi
+ ) = TechwasForumApiRepository(api)
+
+ @Provides
+ @Singleton
+ fun provideFavoriteArticleDatabase(@ApplicationContext context: Context): FavoriteArticleDatabase {
+ return Room.databaseBuilder(
+ context,
+ FavoriteArticleDatabase::class.java,
+ "fav_article_database"
+ ).build()
+ }
+
+ @Provides
+ fun provideFavoriteArticleRepository(
+ favoriteArticleDatabase: FavoriteArticleDatabase
+ ) = FavoriteArticleRepository(favArticleDatabase = favoriteArticleDatabase)
+
}
\ No newline at end of file
diff --git a/app/src/main/java/com/capstone/techwasmark02/repository/FavoriteArticleRepository.kt b/app/src/main/java/com/capstone/techwasmark02/repository/FavoriteArticleRepository.kt
new file mode 100644
index 0000000..6b8f83c
--- /dev/null
+++ b/app/src/main/java/com/capstone/techwasmark02/repository/FavoriteArticleRepository.kt
@@ -0,0 +1,20 @@
+package com.capstone.techwasmark02.repository
+
+import com.capstone.techwasmark02.data.local.database.FavoriteArticleDatabase
+import com.capstone.techwasmark02.data.mappers.toFavoriteArticleEntity
+import com.capstone.techwasmark02.data.model.FavoriteArticle
+import javax.inject.Inject
+
+class FavoriteArticleRepository @Inject constructor(private val favArticleDatabase: FavoriteArticleDatabase) {
+
+ fun getFavArticles() = favArticleDatabase.favoriteArticleDao.getFavoriteArticles()
+
+ fun getFavArticleById(id: Int) = favArticleDatabase.favoriteArticleDao.getFavoriteArticleById(id = id)
+
+ suspend fun upsertFavoriteArticle(favoriteArticle: FavoriteArticle) = favArticleDatabase.favoriteArticleDao.upsertFavoriteArticle(favoriteArticle.toFavoriteArticleEntity())
+
+ suspend fun deleteFavoriteArticle(
+ favoriteArticle: FavoriteArticle
+ ) = favArticleDatabase.favoriteArticleDao.deleteFavoriteArticle(favoriteArticle.toFavoriteArticleEntity())
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/capstone/techwasmark02/repository/PreferencesRepository.kt b/app/src/main/java/com/capstone/techwasmark02/repository/PreferencesRepository.kt
index e8e1800..d06f440 100644
--- a/app/src/main/java/com/capstone/techwasmark02/repository/PreferencesRepository.kt
+++ b/app/src/main/java/com/capstone/techwasmark02/repository/PreferencesRepository.kt
@@ -1,14 +1,16 @@
package com.capstone.techwasmark02.repository
+import android.content.res.Resources
+import androidx.compose.ui.res.stringResource
import androidx.datastore.core.DataStore
import androidx.datastore.preferences.core.Preferences
import androidx.datastore.preferences.core.edit
import androidx.datastore.preferences.core.stringPreferencesKey
+import com.capstone.techwasmark02.R
import com.capstone.techwasmark02.common.Resource
import com.capstone.techwasmark02.data.model.UserSession
import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
-import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.map
import javax.inject.Inject
diff --git a/app/src/main/java/com/capstone/techwasmark02/repository/TechwasArticleRepository.kt b/app/src/main/java/com/capstone/techwasmark02/repository/TechwasArticleRepository.kt
new file mode 100644
index 0000000..76b98c9
--- /dev/null
+++ b/app/src/main/java/com/capstone/techwasmark02/repository/TechwasArticleRepository.kt
@@ -0,0 +1,53 @@
+package com.capstone.techwasmark02.repository
+
+import com.capstone.techwasmark02.data.remote.apiService.TechwasArticleApi
+import com.capstone.techwasmark02.data.remote.response.ArticleResultResponse
+import com.capstone.techwasmark02.data.remote.response.SingleArticleResponse
+import com.capstone.techwasmark02.ui.common.UiState
+import javax.inject.Inject
+
+class TechwasArticleRepository @Inject constructor(
+ private val articleApi: TechwasArticleApi
+) {
+ suspend fun getAllArticle(): UiState {
+ val response = try {
+ articleApi.getAllArticle()
+ } catch (e: Exception) {
+ return UiState.Error(message = "fail to fetch article, ${e.message}")
+ }
+ val articleResultResponse = ArticleResultResponse(
+ articleList = response.componentList,
+ error = response.error,
+ message = response.message
+ )
+
+ return UiState.Success(data = articleResultResponse, message = "Success to fetch article")
+ }
+
+ suspend fun getArticleById(id: Int): UiState {
+ val response = try {
+ articleApi.getArticleById(id)
+ } catch (e: Exception) {
+ return UiState.Error(message = "fail to fetch article, ${e.message}")
+ }
+ return UiState.Success(data = response, message = "Success to fetch article")
+ }
+
+ suspend fun getArticleByName(name: String): UiState {
+ val response = try {
+ articleApi.getArticleByName(name)
+ } catch (e: Exception) {
+ return UiState.Error(message = "fail to fetch article, ${e.message}")
+ }
+ return UiState.Success(data = response, message = "Success to fetch article")
+ }
+
+ suspend fun getArticleByComponentId(userToken: String = "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VySUQiOiJ1c2VyQGV4YW1wbGUuY29tIiwiZXhwaXJ5IjoxNjg1ODcyMDkwLjMwNjU4ODJ9.cvaEjnnWe4Z3Hl-ImAIKyguTWeuntb6vOuwGCa1rr2w", id: Int): UiState {
+ val response = try {
+ articleApi.getArticleByComponentId(token = userToken, compid = id)
+ } catch (e: Exception) {
+ return UiState.Error(message = "fail to fetch article, ${e.message}")
+ }
+ return UiState.Success(data = response, message = "Success to fetch article")
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/capstone/techwasmark02/repository/TechwasComponentApiRepository.kt b/app/src/main/java/com/capstone/techwasmark02/repository/TechwasComponentApiRepository.kt
new file mode 100644
index 0000000..f22ab12
--- /dev/null
+++ b/app/src/main/java/com/capstone/techwasmark02/repository/TechwasComponentApiRepository.kt
@@ -0,0 +1,43 @@
+package com.capstone.techwasmark02.repository
+
+import com.capstone.techwasmark02.data.remote.apiService.TechwasComponentApi
+import com.capstone.techwasmark02.data.remote.response.ComponentResponse
+import com.capstone.techwasmark02.data.remote.response.ComponentsResponse
+import com.capstone.techwasmark02.data.remote.response.UsableComponentsResponse
+import com.capstone.techwasmark02.ui.common.UiState
+import javax.inject.Inject
+
+class TechwasComponentApiRepository @Inject constructor(
+ private val componentApi: TechwasComponentApi
+) {
+
+ suspend fun fetchComponents(userToken: String = "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VySUQiOiJ1c2VyQGV4YW1wbGUuY29tIiwiZXhwaXJ5IjoxNjg1ODcyMDkwLjMwNjU4ODJ9.cvaEjnnWe4Z3Hl-ImAIKyguTWeuntb6vOuwGCa1rr2w") : UiState {
+
+ val response = try {
+ componentApi.fetchComponents(token = userToken)
+ } catch (e: Exception) {
+ return UiState.Error(message = e.message ?: "Fail to fetch components")
+ }
+ return UiState.Success(data = response)
+ }
+
+ suspend fun fetchComponentById(userToken: String = "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VySUQiOiJ1c2VyQGV4YW1wbGUuY29tIiwiZXhwaXJ5IjoxNjg1ODcyMDkwLjMwNjU4ODJ9.cvaEjnnWe4Z3Hl-ImAIKyguTWeuntb6vOuwGCa1rr2w", componentId: Int) : UiState {
+
+ val response = try {
+ componentApi.fetchComponentById(token = userToken, id = componentId)
+ } catch (e: Exception) {
+ return UiState.Error(message = e.message ?: "Fail to fetch components")
+ }
+ return UiState.Success(data = response)
+ }
+
+ suspend fun fetchUsableComponents(compId: Int): UiState {
+ val response = try {
+ componentApi.fetchUsableComponents(compid = compId)
+ } catch (e: Exception) {
+ return UiState.Error(message = e.message ?: "Fail to fetch usable components")
+ }
+ return UiState.Success(data = response)
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/capstone/techwasmark02/repository/TechwasForumApiRepository.kt b/app/src/main/java/com/capstone/techwasmark02/repository/TechwasForumApiRepository.kt
new file mode 100644
index 0000000..080f55d
--- /dev/null
+++ b/app/src/main/java/com/capstone/techwasmark02/repository/TechwasForumApiRepository.kt
@@ -0,0 +1,110 @@
+package com.capstone.techwasmark02.repository
+
+import com.capstone.techwasmark02.data.model.ForumCommentInfo
+import com.capstone.techwasmark02.data.model.ForumToCreateInfo
+import com.capstone.techwasmark02.data.remote.apiService.TechwasForumApi
+import com.capstone.techwasmark02.data.remote.response.CreateForumResponse
+import com.capstone.techwasmark02.data.remote.response.ForumCommentResponse
+import com.capstone.techwasmark02.data.remote.response.ForumResponse
+import com.capstone.techwasmark02.data.remote.response.ImageUrlResponse
+import com.capstone.techwasmark02.data.remote.response.PostForumCommentResponse
+import com.capstone.techwasmark02.ui.common.UiState
+import okhttp3.MediaType.Companion.toMediaTypeOrNull
+import okhttp3.MultipartBody
+import okhttp3.RequestBody.Companion.asRequestBody
+import java.io.File
+import javax.inject.Inject
+
+class TechwasForumApiRepository @Inject constructor(
+ private val forumApi: TechwasForumApi
+) {
+
+ suspend fun fetchAllForum(): UiState {
+ val response = try {
+ forumApi.fetchAllForum()
+ } catch (e: Exception) {
+ return UiState.Error(message = "fail to fetch forum, ${e.message}")
+ }
+ return UiState.Success(data = response, message = "Success to fetch forum")
+ }
+
+ suspend fun fetchForumById(id: Int, userToken: String): UiState {
+ val token = "Bearer $userToken"
+
+ val response = try {
+ forumApi.fetchForumById(
+ id = id,
+ token = token
+ )
+ } catch (e: Exception) {
+ return UiState.Error(message = "fail to fetch forum, ${e.message}")
+ }
+ return UiState.Success(data = response, message = response.message)
+ }
+
+ suspend fun fetchForumByCategory(category: String): UiState {
+ val response = try {
+ forumApi.fetchForumByCategory(category)
+ } catch (e: Exception) {
+ return UiState.Error(message = "fail to fetch forum, ${e.message}")
+ }
+ return UiState.Success(data = response, message = response.message)
+ }
+
+ suspend fun fetchForumCommentByForumId(forumId: Int): UiState {
+ val response = try {
+ forumApi.fetchForumComment(forumid = forumId)
+ } catch (e: Exception) {
+ return UiState.Error(message = "fail to fetch comment, ${e.message}")
+ }
+ return UiState.Success(data = response, message = response.message)
+ }
+
+ suspend fun postForumComment(forumCommentInfo: ForumCommentInfo, userToken: String): UiState {
+ val token = "Bearer $userToken"
+
+ val response = try {
+ forumApi.postForumComment(
+ token,
+ forumCommentInfo,
+ )
+ } catch (e: Exception) {
+ return UiState.Error(message = "fail to post comment, ${e.message}")
+ }
+ return UiState.Success(data = response, message = response.message)
+ }
+
+ suspend fun createNewForum(forumToCreateInfo: ForumToCreateInfo, userToken: String): UiState {
+ val token = "Bearer $userToken"
+
+ val response = try {
+ forumApi.createNewForum(forumToCreateInfo = forumToCreateInfo, token = token)
+ } catch (e: Exception) {
+ return UiState.Error(message = e.message ?: "Fail to create new forum")
+ }
+ return UiState.Success(data = response, message = response.message)
+ }
+
+ suspend fun uploadAndGetImageUrl(file: File, userToken: String): UiState {
+ val token = "Bearer ${userToken}"
+
+ val imageFile = file.asRequestBody("image/jpeg".toMediaTypeOrNull())
+ val imageMultiPart: MultipartBody.Part = MultipartBody.Part
+ .createFormData(
+ name = "file",
+ filename = file.name,
+ body = imageFile
+ )
+
+ val response = try {
+ forumApi.uploadAndGetImage(
+ token,
+ imageMultiPart
+ )
+ } catch (e: Exception) {
+ return UiState.Error(message = "fail to get image url, ${e.message}")
+ }
+ return UiState.Success(data = response, message = response.message)
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/capstone/techwasmark02/repository/TechwasPredictionApiRepository.kt b/app/src/main/java/com/capstone/techwasmark02/repository/TechwasPredictionApiRepository.kt
index a7736b3..5cd8623 100644
--- a/app/src/main/java/com/capstone/techwasmark02/repository/TechwasPredictionApiRepository.kt
+++ b/app/src/main/java/com/capstone/techwasmark02/repository/TechwasPredictionApiRepository.kt
@@ -1,6 +1,7 @@
package com.capstone.techwasmark02.repository
import com.capstone.techwasmark02.data.remote.apiService.TechwasPredictionApi
+import com.capstone.techwasmark02.data.remote.response.DetectionsResultResponse
import com.capstone.techwasmark02.ui.common.UiState
import okhttp3.MediaType.Companion.toMediaTypeOrNull
import okhttp3.MultipartBody
@@ -12,7 +13,7 @@ class TechwasPredictionApiRepository @Inject constructor(
private val predictionApi: TechwasPredictionApi
) {
- suspend fun predictWaste(file: File): UiState {
+ suspend fun predictWaste(file: File): UiState {
val imageFile = file.asRequestBody("image/jpeg".toMediaTypeOrNull())
val imageMultiPart: MultipartBody.Part = MultipartBody.Part.createFormData(
name = "file",
diff --git a/app/src/main/java/com/capstone/techwasmark02/repository/TechwasUserApiRepository.kt b/app/src/main/java/com/capstone/techwasmark02/repository/TechwasUserApiRepository.kt
index ca0821a..d943bff 100644
--- a/app/src/main/java/com/capstone/techwasmark02/repository/TechwasUserApiRepository.kt
+++ b/app/src/main/java/com/capstone/techwasmark02/repository/TechwasUserApiRepository.kt
@@ -4,6 +4,7 @@ import com.capstone.techwasmark02.data.model.UserLoginInfo
import com.capstone.techwasmark02.data.model.UserRegisterInfo
import com.capstone.techwasmark02.data.remote.apiService.TechwasUserApi
import com.capstone.techwasmark02.data.remote.response.UserLoginResponse
+import com.capstone.techwasmark02.data.remote.response.UserRegisterResponse
import com.capstone.techwasmark02.ui.common.UiState
import java.lang.Exception
import javax.inject.Inject
@@ -22,7 +23,7 @@ class TechwasUserApiRepository @Inject constructor(
return UiState.Success(data = response)
}
- suspend fun userRegister(userRegisterInfo: UserRegisterInfo) : UiState {
+ suspend fun userRegister(userRegisterInfo: UserRegisterInfo) : UiState {
val response = try {
userApi.register(userRegisterInfo)
diff --git a/app/src/main/java/com/capstone/techwasmark02/ui/component/ArticleCard.kt b/app/src/main/java/com/capstone/techwasmark02/ui/component/ArticleCard.kt
index 94943d4..72c930d 100644
--- a/app/src/main/java/com/capstone/techwasmark02/ui/component/ArticleCard.kt
+++ b/app/src/main/java/com/capstone/techwasmark02/ui/component/ArticleCard.kt
@@ -1,11 +1,13 @@
package com.capstone.techwasmark02.ui.component
-import androidx.compose.foundation.Canvas
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+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
@@ -16,21 +18,89 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.clipToBounds
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.graphics.drawscope.Stroke
+import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.font.FontWeight
+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 coil.compose.AsyncImage
import coil.compose.rememberAsyncImagePainter
import com.capstone.techwasmark02.R
+import com.capstone.techwasmark02.data.remote.response.ArticleList
import com.capstone.techwasmark02.ui.theme.TechwasMark02Theme
-import kotlin.random.Random
@Composable
fun ArticleCardBig(
- modifier: Modifier = Modifier
+ modifier: Modifier = Modifier,
+ article: ArticleList
+) {
+ ElevatedCard(
+ modifier = modifier
+ .height(175.dp),
+ shape = MaterialTheme.shapes.large,
+ elevation = CardDefaults.cardElevation(
+ defaultElevation = 6.dp
+ )
+ ) {
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .fillMaxHeight()
+ .background(MaterialTheme.colorScheme.tertiary)
+ ) {
+ Box(
+ modifier = Modifier
+ .weight(1f)
+ .background(Color.LightGray)
+ ) {
+ AsyncImage(
+ model = article.articleImageURL,
+ contentDescription = null,
+ contentScale = ContentScale.Crop,
+ modifier = Modifier
+ .fillMaxSize()
+ )
+ }
+
+ Column(
+ modifier = Modifier
+ .padding(horizontal = 16.dp)
+ .height(64.dp),
+ verticalArrangement = Arrangement.Center
+ ) {
+ article.name?.let {
+ Text(
+ text = it,
+ style = MaterialTheme.typography.titleSmall,
+ maxLines = 1,
+ overflow = TextOverflow.Ellipsis
+ )
+ }
+ article.desc?.let {
+ HtmlText(
+ html = it,
+ textStyle = MaterialTheme.typography.bodySmall.copy(
+ color = Color.Black,
+ fontWeight = FontWeight.Normal,
+ fontSize = 7.sp
+ ),
+ maxLine = 1
+ )
+ }
+ }
+ }
+ }
+}
+
+@Composable
+fun ArticleCardSmall(
+ modifier: Modifier = Modifier,
+ imgUrl: String?,
+ title: String?,
+ description: String?,
) {
ElevatedCard(
modifier = modifier,
@@ -42,13 +112,15 @@ fun ArticleCardBig(
Box(
modifier = Modifier
.height(107.dp)
+ .fillMaxWidth()
) {
Image(
modifier = Modifier
.fillMaxWidth()
.height(107.dp),
painter = rememberAsyncImagePainter(
- model = "https://picsum.photos/seed/${Random.nextInt()}/320/120",
+// model = "https://picsum.photos/seed/${Random.nextInt()}/320/120",
+ model = imgUrl,
placeholder = painterResource(id = R.drawable.place_holder),
),
contentScale = ContentScale.Crop,
@@ -63,16 +135,25 @@ fun ArticleCardBig(
.padding(horizontal = 16.dp),
verticalArrangement = Arrangement.Center
) {
- Text(
- text = "Do not throw electronic waste carelessly",
- style = MaterialTheme.typography.titleSmall,
- maxLines = 1
- )
- Text(
- text = "Lorem ipsum dolor sit amet, consectetur...",
- style = MaterialTheme.typography.bodySmall,
- maxLines = 1
- )
+ if (title != null) {
+ Text(
+ text = title,
+ style = MaterialTheme.typography.titleSmall,
+ maxLines = 1,
+ overflow = TextOverflow.Ellipsis
+ )
+ }
+ if (description != null) {
+ HtmlText(
+ html = description,
+ textStyle = MaterialTheme.typography.bodySmall.copy(
+ color = Color.Black,
+ fontWeight = FontWeight.Normal,
+ fontSize = 7.sp
+ ),
+ maxLine = 1
+ )
+ }
}
}
}
@@ -87,7 +168,23 @@ fun ArticleCardPreview() {
.background(MaterialTheme.colorScheme.background)
.padding(20.dp)
) {
- ArticleCardBig(modifier = Modifier.width(240.dp))
+ Column {
+ ArticleCardBig(modifier = Modifier.width(240.dp), article = ArticleList(
+ componentId = 2,
+ articleImageURL = null,
+ name = "Do not throw electronic was bg",
+ id = 2,
+ desc = "Lorem ipsum and the sum of the sum si sum for the sum"
+ )
+ )
+ Spacer(modifier = Modifier.height(20.dp))
+ ArticleCardSmall(
+ modifier = Modifier.width(150.dp),
+ imgUrl = "",
+ title = "judul satu",
+ description = "deskripsi ajah",
+ )
+ }
}
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/capstone/techwasmark02/ui/component/Banner.kt b/app/src/main/java/com/capstone/techwasmark02/ui/component/Banner.kt
index 793e532..26e7b94 100644
--- a/app/src/main/java/com/capstone/techwasmark02/ui/component/Banner.kt
+++ b/app/src/main/java/com/capstone/techwasmark02/ui/component/Banner.kt
@@ -1,14 +1,20 @@
package com.capstone.techwasmark02.ui.component
+import androidx.compose.foundation.Image
import androidx.compose.foundation.background
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.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
@@ -18,9 +24,22 @@ import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Shadow
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.SpanStyle
+import androidx.compose.ui.text.buildAnnotatedString
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import com.capstone.techwasmark02.R
+import com.capstone.techwasmark02.ui.theme.Black20
+import com.capstone.techwasmark02.ui.theme.Green77
import com.capstone.techwasmark02.ui.theme.TechwasMark02Theme
+import com.capstone.techwasmark02.ui.theme.Yellow77
+import com.capstone.techwasmark02.ui.theme.poppins
@Composable
fun SignInBanner(
@@ -39,24 +58,66 @@ fun SignInBanner(
.background(
MaterialTheme.colorScheme.primary,
),
- contentAlignment = Alignment.Center
) {
- Text(
- text = "Welcome to TechWaste",
- style = MaterialTheme.typography.headlineSmall.copy(
- shadow = Shadow(
- color = MaterialTheme.colorScheme.onTertiary.copy(
- alpha = 0.8f
+
+ Image(
+ painter = painterResource(id = R.drawable.img_bg_singin),
+ contentDescription = null,
+ modifier = Modifier
+ .fillMaxSize(),
+ contentScale = ContentScale.Crop
+ )
+
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(horizontal = 24.dp),
+ verticalArrangement = Arrangement.Center
+ ) {
+
+ Spacer(modifier = Modifier.height(10.dp))
+
+ Box(
+ modifier = Modifier
+ ) {
+
+ Text(
+ text = "Welcome",
+ style = MaterialTheme.typography.headlineMedium.copy(
+ fontWeight = FontWeight.Bold
),
- offset = Offset(
- x = 0f,
- y = 14f
+ color = MaterialTheme.colorScheme.onPrimary,
+ modifier = Modifier
+ .offset(
+ x = 0.dp,
+ y = -(18.dp)
+ )
+ )
+
+ Text(
+ text = "to Techwaste!",
+ style = MaterialTheme.typography.headlineMedium.copy(
+ fontWeight = FontWeight.Bold
),
- blurRadius = 16f
+ color = MaterialTheme.colorScheme.onPrimary,
+ modifier = Modifier
+ .offset(
+ x = 0.dp,
+ y = (10.dp)
+ )
)
- ),
- color = MaterialTheme.colorScheme.onPrimary,
- )
+ }
+
+ Spacer(modifier = Modifier.height(8.dp))
+
+ Text(
+ text = "Let's manage your e-waste properly.",
+ style = MaterialTheme.typography.bodyMedium.copy(
+ fontWeight = FontWeight.Medium
+ ),
+ color = MaterialTheme.colorScheme.onPrimary
+ )
+ }
}
}
@@ -66,7 +127,7 @@ fun SignUpBanner(
) {
Box(
modifier = modifier
- .height(100.dp)
+ .height(150.dp)
.fillMaxWidth()
.shadow(
elevation = 12.dp,
@@ -77,69 +138,259 @@ fun SignUpBanner(
.background(
MaterialTheme.colorScheme.primary,
),
- contentAlignment = Alignment.Center
) {
- Text(
- text = "Nice to meet you",
- style = MaterialTheme.typography.headlineSmall.copy(
- shadow = Shadow(
- color = MaterialTheme.colorScheme.onTertiary.copy(
- alpha = 0.8f
+
+ Image(
+ painter = painterResource(id = R.drawable.img_bg_signup),
+ contentDescription = null,
+ modifier = Modifier
+ .fillMaxSize(),
+ contentScale = ContentScale.Crop
+ )
+
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(horizontal = 24.dp),
+ verticalArrangement = Arrangement.Center
+ ) {
+ Box(
+ modifier = Modifier
+ ) {
+
+ Text(
+ text = "Let's start your",
+ style = MaterialTheme.typography.headlineSmall.copy(
+ fontWeight = FontWeight.Bold
),
- offset = Offset(
- x = 0f,
- y = 14f
+ color = MaterialTheme.colorScheme.onPrimary,
+ modifier = Modifier
+ .offset(
+ x = 0.dp,
+ y = -(28.dp)
+ )
+ )
+
+ Text(
+ text = "journey to dispose of",
+ style = MaterialTheme.typography.headlineSmall.copy(
+ fontWeight = FontWeight.Bold
),
- blurRadius = 16f
+ color = MaterialTheme.colorScheme.onPrimary,
+ modifier = Modifier
+ .offset(
+ x = 0.dp,
+ y = (0.dp)
+ )
)
- ),
- color = MaterialTheme.colorScheme.onPrimary,
- )
+
+ Text(
+ text = "e-waste!",
+ style = MaterialTheme.typography.headlineSmall.copy(
+ fontWeight = FontWeight.Bold
+ ),
+ color = MaterialTheme.colorScheme.onPrimary,
+ modifier = Modifier
+ .offset(
+ x = 0.dp,
+ y = (28.dp)
+ )
+ )
+ }
+ }
}
}
@Composable
fun DropPointBanner(modifier: Modifier = Modifier) {
Box(
- modifier = modifier
- .height(175.dp)
- .fillMaxWidth()
- .shadow(
- elevation = 12.dp,
- shape = MaterialTheme.shapes.large,
- clip = true,
- )
- .clip(MaterialTheme.shapes.large)
- .background(
- MaterialTheme.colorScheme.primary,
- )
- .padding(horizontal = 18.dp),
- contentAlignment = Alignment.CenterEnd
+ modifier = modifier.height(190.dp),
+ contentAlignment = Alignment.BottomStart
) {
- Column(
+ Box(
modifier = Modifier
- .fillMaxHeight(),
- verticalArrangement = Arrangement.Center,
- horizontalAlignment = Alignment.End
+ .height(175.dp)
+ .fillMaxWidth()
+ .clip(MaterialTheme.shapes.large)
+ .background(
+ Green77.copy(alpha = 0.5f),
+ ),
+ contentAlignment = Alignment.Center
) {
- Text(
- text = "Don't know where to",
- style = MaterialTheme.typography.labelLarge,
- color = MaterialTheme.colorScheme.onPrimary,
- )
- Text(
- text = "put your e-waste?",
- style = MaterialTheme.typography.labelLarge,
- color = MaterialTheme.colorScheme.onPrimary,
- )
- Text(
- text = "We've got you covered",
- style = MaterialTheme.typography.labelLarge,
- color = MaterialTheme.colorScheme.onPrimary,
- )
- Spacer(modifier = Modifier.height(12.dp))
- SmallButton(contentText = "LOCATE NOW")
+ Box(
+ modifier = Modifier
+ .fillMaxHeight()
+ .fillMaxWidth()
+ .clip(RoundedCornerShape(44.dp))
+ .background(
+ Green77.copy(alpha = 0.5f)
+ )
+ ) {
+ Box(
+ modifier = Modifier
+ .fillMaxHeight()
+ .fillMaxSize()
+ .clip(RoundedCornerShape(65.dp))
+ .background(
+ Green77.copy(alpha = 0.7f)
+ )
+ )
+ }
+ Row(
+ modifier = Modifier
+ .padding(horizontal = 18.dp)
+ .fillMaxWidth()
+ .fillMaxHeight(),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.SpaceBetween
+ ) {
+ Spacer(modifier = Modifier.width(150.dp))
+ Column(
+ modifier = Modifier.fillMaxWidth()
+ ) {
+
+ val text = buildAnnotatedString {
+ append("Don't know where to put your ")
+ withStyle(style = SpanStyle(color = Yellow77)) {
+ append("e-waste?")
+ }
+ }
+
+ Text(
+ text = text,
+ style = MaterialTheme.typography.labelLarge.copy(
+ shadow = Shadow(
+ color = Black20.copy(
+ alpha = 0.25f,
+ ),
+ offset = Offset(0f, 4.0f),
+ blurRadius = 4f
+ ),
+ fontSize = 22.sp,
+ lineHeight = 25.sp,
+ letterSpacing = 0.sp,
+ color = MaterialTheme.colorScheme.onPrimary,
+ fontFamily = poppins
+ )
+ )
+ Spacer(modifier = Modifier.height(10.dp))
+ Row(modifier = Modifier.align(alignment = Alignment.CenterHorizontally)) {
+ SmallButton(contentText = "LOCATE NOW", colorText = MaterialTheme.colorScheme.primary)
+ }
+ }
+ }
}
+ Image(
+ painter = painterResource(id = R.drawable.trash_bucket),
+ contentDescription = "Image",
+ modifier = Modifier
+ .height(400.dp)
+ .width(200.dp)
+ .offset(y = 0.dp, x = 10.dp)
+ )
+ }
+}
+
+@Composable
+fun ForumBanner(modifier: Modifier = Modifier) {
+ Box(
+ modifier = modifier.height(190.dp),
+ contentAlignment = Alignment.BottomStart
+ ) {
+ Box(
+ modifier = Modifier
+ .height(175.dp)
+ .fillMaxWidth()
+ .clip(MaterialTheme.shapes.large)
+ .background(
+ Green77.copy(alpha = 0.5f),
+ ),
+ contentAlignment = Alignment.Center
+ ) {
+ Box(
+ modifier = Modifier
+ .fillMaxHeight()
+ .fillMaxWidth()
+ .clip(RoundedCornerShape(39.dp))
+ .background(
+ Green77.copy(alpha = 0.5f)
+ )
+ ) {
+ Box(
+ modifier = Modifier
+ .fillMaxHeight()
+ .fillMaxSize()
+ .clip(RoundedCornerShape(72.dp))
+ .background(
+ Green77.copy(alpha = 0.7f)
+ )
+ )
+ }
+ Row(
+ modifier = Modifier
+ .padding(horizontal = 18.dp)
+ .fillMaxWidth()
+ .fillMaxHeight(),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.SpaceBetween
+ ) {
+ Spacer(modifier = Modifier.width(160.dp))
+ Column(
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ Row(modifier = Modifier.align(alignment = Alignment.CenterHorizontally)) {
+ SmallButton(contentText = "LOCATE NOW", colorText = MaterialTheme.colorScheme.primary)
+ }
+
+ Spacer(modifier = Modifier.height(10.dp))
+
+ val text = buildAnnotatedString {
+ append("Don't know where to put your ")
+ withStyle(style = SpanStyle(color = Yellow77)) {
+ append("e-waste?")
+ }
+ }
+
+ Text(
+ text = "Join the discussions!",
+ modifier = Modifier.width(200.dp),
+ style = MaterialTheme.typography.labelSmall.copy(
+ shadow = Shadow(
+ color = Black20.copy(
+ alpha = 0.25f,
+ ),
+ offset = Offset(0f, 4.0f),
+ blurRadius = 4f,
+ ),
+ fontSize = 13.sp,
+ lineHeight = 25.sp,
+ letterSpacing = 0.sp,
+ color = MaterialTheme.colorScheme.onPrimary,
+ fontFamily = poppins,
+ textAlign = TextAlign.End
+ )
+ )
+
+ Text(
+ text = "Get involved in discussion with other users.",
+ style = MaterialTheme.typography.bodyMedium,
+ fontSize = 11.sp,
+ textAlign = TextAlign.End,
+ color = MaterialTheme.colorScheme.onPrimary,
+ fontFamily = poppins
+ )
+
+ }
+ }
+ }
+ Image(
+ painter = painterResource(id = R.drawable.forum_chat),
+ contentDescription = "Image",
+ modifier = Modifier
+ .height(400.dp)
+ .width(170.dp)
+ .offset(y = 0.dp, x = 20.dp)
+ )
}
}
@@ -171,7 +422,11 @@ fun HomeBannerPreview() {
.padding(16.dp)
) {
DropPointBanner()
+
+ Spacer(modifier = Modifier.height(16.dp))
+
+ ForumBanner()
}
}
-
+
}
\ No newline at end of file
diff --git a/app/src/main/java/com/capstone/techwasmark02/ui/component/BottomBar.kt b/app/src/main/java/com/capstone/techwasmark02/ui/component/BottomBar.kt
index cd43d69..2daa4ca 100644
--- a/app/src/main/java/com/capstone/techwasmark02/ui/component/BottomBar.kt
+++ b/app/src/main/java/com/capstone/techwasmark02/ui/component/BottomBar.kt
@@ -4,13 +4,17 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.IntrinsicSize
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
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.size
import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
@@ -19,100 +23,159 @@ import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.draw.shadow
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
-import com.capstone.techwasmark02.R
import com.capstone.techwasmark02.ui.componentType.BottomBarItemType
import com.capstone.techwasmark02.ui.theme.TechwasMark02Theme
-import com.capstone.techwasmark02.ui.theme.customShape.BottomNavShape
-import com.capstone.techwasmark02.ui.theme.customShape.BottomNavShape2
@Composable
fun DefaultBottomBar(
modifier: Modifier = Modifier,
- selectedType: BottomBarItemType
+ selectedType: BottomBarItemType,
+ onClickBottomNavType: (bottomBarType: BottomBarItemType) -> Unit,
) {
- NavigationBar(
- modifier = modifier
- .graphicsLayer {
- shape = BottomNavShape()
- clip = true
- }
+ Box(
+ modifier = Modifier
.fillMaxWidth()
- .height(60.dp),
- containerColor = MaterialTheme.colorScheme.inverseSurface,
- contentColor = MaterialTheme.colorScheme.inverseOnSurface,
-
+ .padding(horizontal = 8.dp)
+ .padding(bottom = 8.dp)
) {
- Row(
- modifier = Modifier
+ NavigationBar(
+ modifier = modifier
+ .shadow(
+ elevation = 8.dp,
+// shape = BottomNavShape(),
+ clip = true
+ )
+ .clip(RoundedCornerShape(10.dp))
.fillMaxWidth()
- .fillMaxHeight(),
- verticalAlignment = Alignment.CenterVertically
+ .height(64.dp),
+ containerColor = MaterialTheme.colorScheme.inverseSurface,
+ contentColor = MaterialTheme.colorScheme.inverseOnSurface,
+ tonalElevation = 8.dp
) {
- Spacer(modifier = Modifier.weight(1f))
-
- BottomBarItem(bottomBarItemType = BottomBarItemType.Home, selectedType = selectedType)
-
- Spacer(modifier = Modifier.weight(1f))
-
- BottomBarItem(bottomBarItemType = BottomBarItemType.Forum, selectedType = selectedType)
-
- Spacer(modifier = Modifier.weight(1f))
-
- Spacer(modifier = Modifier.width(80.dp))
-
- Spacer(modifier = Modifier.weight(1f))
-
- BottomBarItem(bottomBarItemType = BottomBarItemType.Article, selectedType = selectedType)
-
- Spacer(modifier = Modifier.weight(1f))
-
- BottomBarItem(bottomBarItemType = BottomBarItemType.Profile, selectedType = selectedType)
-
- Spacer(modifier = Modifier.weight(1f))
-
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .fillMaxHeight()
+ .padding(horizontal = 24.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ BottomBarItem(
+ bottomBarItemType = BottomBarItemType.Home,
+ selectedType = selectedType,
+ onClick = {
+ onClickBottomNavType(it)
+ },
+ )
+
+ Spacer(modifier = Modifier.weight(1f))
+
+ BottomBarItem(
+ bottomBarItemType = BottomBarItemType.Forum,
+ selectedType = selectedType,
+ onClick = {
+ onClickBottomNavType(it)
+ },
+ )
+
+ Spacer(modifier = Modifier.weight(1f))
+
+ BottomBarItem(
+ bottomBarItemType = BottomBarItemType.Article,
+ selectedType = selectedType,
+ onClick = {
+ onClickBottomNavType(it)
+ },
+ )
+
+ Spacer(modifier = Modifier.weight(1f))
+
+ BottomBarItem(
+ bottomBarItemType = BottomBarItemType.Profile,
+ selectedType = selectedType,
+ onClick = {
+ onClickBottomNavType(it)
+ },
+ )
+ }
}
}
}
+
@Composable
fun BottomBarItem(
modifier: Modifier = Modifier,
bottomBarItemType: BottomBarItemType,
- selectedType: BottomBarItemType
+ selectedType: BottomBarItemType,
+ onClick: (BottomBarItemType) -> Unit,
) {
- Column(
+ Box(
modifier = modifier
- .width(44.dp)
- .height(44.dp),
- horizontalAlignment = Alignment.CenterHorizontally,
- verticalArrangement = Arrangement.SpaceBetween
+ .fillMaxHeight()
+ .width(IntrinsicSize.Max),
+ contentAlignment = Alignment.Center
) {
- Icon(
- painter = painterResource(id = bottomBarItemType.icon),
- contentDescription = null,
- tint = if (selectedType == bottomBarItemType) {
- MaterialTheme.colorScheme.primary
- } else {
- MaterialTheme.colorScheme.inverseOnSurface
- }
- )
- Text(
- text = bottomBarItemType.title,
- style = MaterialTheme.typography.labelSmall,
- color = if (selectedType == bottomBarItemType) {
- MaterialTheme.colorScheme.primary
- } else {
- MaterialTheme.colorScheme.inverseOnSurface
+ if (selectedType == bottomBarItemType) {
+ Column(
+ modifier = Modifier
+ .fillMaxHeight()
+ .fillMaxWidth(),
+ verticalArrangement = Arrangement.Bottom
+ ) {
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(4.dp)
+ .clip(RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp))
+ .background(MaterialTheme.colorScheme.primary)
+ )
}
- )
+ }
+
+ IconButton(
+ onClick = {
+ onClick(bottomBarItemType)
+ },
+ modifier = modifier
+ .size(44.dp)
+ ) {
+ Column(
+ modifier = Modifier
+ .fillMaxSize(),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.SpaceBetween
+ ) {
+ Icon(
+ painter = painterResource(id = bottomBarItemType.icon),
+ contentDescription = null,
+ tint = if (selectedType == bottomBarItemType) {
+ MaterialTheme.colorScheme.primary
+ } else {
+ MaterialTheme.colorScheme.inverseOnSurface
+ }
+
+ )
+ Text(
+ text = bottomBarItemType.title,
+ style = MaterialTheme.typography.labelSmall,
+ color = if (selectedType == bottomBarItemType) {
+ MaterialTheme.colorScheme.primary
+ } else {
+ MaterialTheme.colorScheme.inverseOnSurface
+ }
+ )
+ }
+ }
}
}
+
@Preview(showBackground = true)
@Composable
fun DefaultBottomBarPreview() {
@@ -122,37 +185,10 @@ fun DefaultBottomBarPreview() {
.fillMaxWidth()
.padding(20.dp)
) {
- DefaultBottomBar(selectedType = BottomBarItemType.Home)
+ DefaultBottomBar(
+ selectedType = BottomBarItemType.Home,
+ onClickBottomNavType = {},
+ )
}
}
-}
-
-@Composable
-fun CheckBottomNav(
- modifier: Modifier = Modifier
-) {
- Box(
- modifier = modifier
- .graphicsLayer {
- shape = BottomNavShape()
- clip = true
- }
- .fillMaxWidth()
- .height(58.dp)
- .background(MaterialTheme.colorScheme.inverseSurface)
- ) {
-
- }
-}
-
-@Preview (showBackground = true)
-@Composable
-fun CheckBottomNavPreview() {
- Box(
- modifier = Modifier
- .fillMaxWidth()
- .padding(20.dp)
- ) {
- CheckBottomNav()
- }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/capstone/techwasmark02/ui/component/Button.kt b/app/src/main/java/com/capstone/techwasmark02/ui/component/Button.kt
index 6cffb8c..93d6896 100644
--- a/app/src/main/java/com/capstone/techwasmark02/ui/component/Button.kt
+++ b/app/src/main/java/com/capstone/techwasmark02/ui/component/Button.kt
@@ -1,22 +1,21 @@
package com.capstone.techwasmark02.ui.component
import androidx.compose.foundation.BorderStroke
-import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
-import androidx.compose.foundation.layout.wrapContentHeight
-import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.material3.Button
+import androidx.compose.material3.ButtonColors
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
@@ -26,7 +25,8 @@ import com.capstone.techwasmark02.ui.theme.TechwasMark02Theme
fun DefaultButton(
modifier: Modifier = Modifier,
onClick: () -> Unit = {},
- contentText: String
+ contentText: String,
+ buttonColors: ButtonColors = ButtonDefaults.buttonColors()
) {
Button(
onClick = onClick,
@@ -35,6 +35,7 @@ fun DefaultButton(
elevation = ButtonDefaults.buttonElevation(
defaultElevation = 4.dp
),
+ colors = buttonColors
) {
Text(
text = contentText,
@@ -73,19 +74,16 @@ fun InverseButton(
}
@Composable
-fun SmallButton(modifier: Modifier = Modifier, contentText: String, onClick: () -> Unit = {}) {
+fun SmallButton(modifier: Modifier = Modifier, contentText: String, onClick: () -> Unit = {}, colorText: Color, containerColor: Color = MaterialTheme.colorScheme.tertiary) {
Button(
onClick = onClick,
modifier = modifier
.height(28.dp),
colors = ButtonDefaults.buttonColors(
contentColor = MaterialTheme.colorScheme.onTertiary,
- containerColor = MaterialTheme.colorScheme.tertiary
+ containerColor = containerColor
),
shape = MaterialTheme.shapes.large,
- elevation = ButtonDefaults.buttonElevation(
- defaultElevation = 6.dp
- ),
contentPadding = PaddingValues(horizontal = 12.dp),
) {
Text(
@@ -93,7 +91,7 @@ fun SmallButton(modifier: Modifier = Modifier, contentText: String, onClick: ()
style = MaterialTheme.typography.bodySmall.copy(
fontWeight = FontWeight.Bold
),
- color = MaterialTheme.colorScheme.primary
+ color = colorText
)
}
}
@@ -115,7 +113,10 @@ fun ButtonPreview() {
Spacer(modifier = Modifier.height(16.dp))
- SmallButton(contentText = "LOCATE NOW")
+ SmallButton(
+ contentText = "LOCATE NOW",
+ colorText = MaterialTheme.colorScheme.primary
+ )
}
}
}
diff --git a/app/src/main/java/com/capstone/techwasmark02/ui/component/CameraView.kt b/app/src/main/java/com/capstone/techwasmark02/ui/component/CameraView.kt
new file mode 100644
index 0000000..61656fc
--- /dev/null
+++ b/app/src/main/java/com/capstone/techwasmark02/ui/component/CameraView.kt
@@ -0,0 +1,341 @@
+package com.capstone.techwasmark02.ui.component
+
+import android.net.Uri
+import android.util.Log
+import androidx.activity.compose.rememberLauncherForActivityResult
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.camera.core.CameraSelector
+import androidx.camera.core.ImageCapture
+import androidx.camera.core.ImageCaptureException
+import androidx.camera.core.Preview
+import androidx.camera.lifecycle.ProcessCameraProvider
+import androidx.camera.view.PreviewView
+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.IntrinsicSize
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+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.size
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.MaterialTheme
+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.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.blur
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalLifecycleOwner
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.viewinterop.AndroidView
+import coil.compose.AsyncImage
+import com.capstone.techwasmark02.R
+import com.capstone.techwasmark02.ui.theme.TechwasMark02Theme
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.withContext
+import java.io.File
+import java.text.SimpleDateFormat
+import java.util.Locale
+import java.util.concurrent.Executor
+
+@Composable
+fun CameraView(
+ outputDirectory: File,
+ executor: Executor,
+ onImageCaptured: (Uri) -> Unit,
+ onError: (ImageCaptureException) -> Unit
+) {
+ val lensFacing = CameraSelector.LENS_FACING_BACK
+ val context = LocalContext.current
+ val lifecycleOwner = LocalLifecycleOwner.current
+
+ val preview = Preview.Builder().build()
+ val previewView = remember {
+ PreviewView(context)
+ }
+
+ var photoPreviewUri by remember {
+ mutableStateOf(Uri.EMPTY)
+ }
+ val imageCapture: ImageCapture = remember {
+ ImageCapture.Builder().build()
+ }
+ val cameraSelector = CameraSelector.Builder()
+ .requireLensFacing(lensFacing)
+ .build()
+
+ val galleryLauncher = rememberLauncherForActivityResult(
+ contract = ActivityResultContracts.GetContent(),
+ onResult = { galleryImageUri ->
+ if(galleryImageUri != null) {
+ photoPreviewUri = galleryImageUri
+ onImageCaptured(galleryImageUri)
+ }
+ }
+ )
+
+ LaunchedEffect(key1 = lensFacing) {
+ val cameraProvider = withContext(Dispatchers.IO) {
+ withContext(Dispatchers.Main) {
+ ProcessCameraProvider.getInstance(context)
+ }.get()
+ }
+ cameraProvider.unbindAll()
+ cameraProvider.bindToLifecycle(
+ lifecycleOwner,
+ cameraSelector,
+ preview,
+ imageCapture
+ )
+
+ preview.setSurfaceProvider(previewView.surfaceProvider)
+ }
+
+ Box(contentAlignment = Alignment.BottomCenter, modifier = Modifier.fillMaxSize()) {
+ TechwasMark02Theme() {
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(MaterialTheme.colorScheme.primary)
+ .padding(top = 16.dp)
+ ) {
+ Box(modifier = Modifier
+ .fillMaxSize()
+ .clip(RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp))
+ .background(Color.White)
+ )
+
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(horizontal = 8.dp, vertical = 8.dp)
+ .clip(RoundedCornerShape(20.dp))
+ .background(Color.LightGray),
+ contentAlignment = Alignment.BottomCenter,
+ ) {
+
+ if (photoPreviewUri != Uri.EMPTY) {
+ AsyncImage(
+ modifier = Modifier
+ .fillMaxSize()
+ .blur(16.dp),
+ model = photoPreviewUri,
+ contentDescription = null,
+ contentScale = ContentScale.Crop,
+ alpha = 0.7f
+ )
+ AsyncImage(
+ modifier = Modifier
+ .fillMaxSize(),
+ model = photoPreviewUri,
+ contentDescription = "Selected image",
+ contentScale = ContentScale.Fit
+ )
+ } else {
+ AndroidView({ previewView }, modifier = Modifier.fillMaxSize())
+ }
+
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(intrinsicSize = IntrinsicSize.Max)
+ .padding(bottom = 16.dp),
+ horizontalArrangement = Arrangement.Center
+ ) {
+ Row(
+ modifier = Modifier
+ .weight(1f)
+ .fillMaxHeight()
+ .padding(start = 20.dp),
+ horizontalArrangement = Arrangement.Start,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ IconButton(
+ onClick = { galleryLauncher.launch("image/*") },
+ modifier = Modifier
+ .clip(CircleShape)
+ .background(Color.White)
+ ) {
+ Icon(painter = painterResource(id = R.drawable.ic_gallery), contentDescription = null)
+ }
+ }
+
+ Box(
+ Modifier
+ .clip(CircleShape)
+ ) {
+ Box(
+ modifier = Modifier
+ .size(62.dp)
+ .clip(CircleShape)
+ .background(Color.Transparent)
+ .border(2.dp, Color.White, CircleShape)
+ .clickable {
+ Log.i("kilo", "ON CLICK")
+ takePhoto(
+ filenameFormat = "yyyy-MM-dd-HH-mm-ss-SSS",
+ imageCapture = imageCapture,
+ outputDirectory = outputDirectory,
+ executor = executor,
+ onImageCaptured = {
+ photoPreviewUri = it
+ onImageCaptured(it)
+ },
+ onError = onError
+ )
+ },
+ contentAlignment = Alignment.Center
+ ) {
+ Box(
+ modifier = Modifier
+ .size(52.dp)
+ .clip(CircleShape)
+ .background(Color.White)
+ )
+ }
+ }
+
+ Spacer(modifier = Modifier.weight(1f))
+ }
+ }
+ }
+ }
+ }
+}
+
+private fun takePhoto(
+ filenameFormat: String,
+ imageCapture: ImageCapture,
+ outputDirectory: File,
+ executor: Executor,
+ onImageCaptured: (Uri) -> Unit,
+ onError: (ImageCaptureException) -> Unit
+) {
+
+ val photoFile = File(
+ outputDirectory,
+ SimpleDateFormat(filenameFormat, Locale.US).format(System.currentTimeMillis()) + ".jpg"
+ )
+
+ val outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).build()
+
+ imageCapture.takePicture(outputOptions, executor, object: ImageCapture.OnImageSavedCallback {
+ override fun onError(exception: ImageCaptureException) {
+ Log.e("kilo", "Take photo error:", exception)
+ onError(exception)
+ }
+
+ override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
+ val savedUri = Uri.fromFile(photoFile)
+ onImageCaptured(savedUri)
+ }
+ })
+}
+
+@androidx.compose.ui.tooling.preview.Preview (showBackground = true)
+@Composable
+fun CameraViewPreview() {
+ TechwasMark02Theme() {
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(MaterialTheme.colorScheme.primary)
+ ) {
+
+ Box(modifier = Modifier
+ .fillMaxSize()
+ .clip(RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp))
+ .background(Color.White)
+ )
+
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(horizontal = 16.dp, vertical = 20.dp)
+ .clip(RoundedCornerShape(20.dp))
+ .background(Color.LightGray),
+ contentAlignment = Alignment.BottomCenter,
+ ) {
+// AndroidView({ previewView }, modifier = Modifier.fillMaxSize())
+
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(intrinsicSize = IntrinsicSize.Max)
+ .padding(bottom = 16.dp),
+ horizontalArrangement = Arrangement.Center
+ ) {
+ Row(
+ modifier = Modifier
+ .weight(1f)
+ .fillMaxHeight()
+ .padding(start = 20.dp),
+ horizontalArrangement = Arrangement.Start,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ IconButton(
+ onClick = { /*TODO*/ },
+ modifier = Modifier
+ .clip(CircleShape)
+ .background(Color.White)
+ ) {
+ Icon(painter = painterResource(id = R.drawable.ic_gallery), contentDescription = null)
+ }
+ }
+
+ Box(
+ Modifier
+ .clip(CircleShape)
+ ) {
+ Box(
+ modifier = Modifier
+ .size(62.dp)
+ .clip(CircleShape)
+ .background(Color.Transparent)
+ .border(2.dp, Color.White, CircleShape)
+ .clickable {
+// Log.i("kilo", "ON CLICK")
+// takePhoto(
+// filenameFormat = "yyyy-MM-dd-HH-mm-ss-SSS",
+// imageCapture = imageCapture,
+// outputDirectory = outputDirectory,
+// executor = executor,
+// onImageCaptured = onImageCaptured,
+// onError = onError
+// )
+ },
+ contentAlignment = Alignment.Center
+ ) {
+ Box(
+ modifier = Modifier
+ .size(52.dp)
+ .clip(CircleShape)
+ .background(Color.White)
+ )
+ }
+ }
+
+ Spacer(modifier = Modifier.weight(1f))
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/capstone/techwasmark02/ui/component/CatalogCard.kt b/app/src/main/java/com/capstone/techwasmark02/ui/component/CatalogCard.kt
new file mode 100644
index 0000000..fb20ae5
--- /dev/null
+++ b/app/src/main/java/com/capstone/techwasmark02/ui/component/CatalogCard.kt
@@ -0,0 +1,144 @@
+package com.capstone.techwasmark02.ui.component
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+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.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.shape.RoundedCornerShape
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.draw.shadow
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import coil.compose.AsyncImage
+import com.capstone.techwasmark02.R
+import com.capstone.techwasmark02.data.remote.response.Component
+import com.capstone.techwasmark02.ui.theme.TechwasMark02Theme
+import kotlin.random.Random
+
+@Composable
+fun CatalogCard(
+ modifier: Modifier = Modifier,
+ component: Component,
+) {
+
+ Box(
+ modifier = modifier
+ .width(300.dp)
+ .height(100.dp)
+ .shadow(
+ elevation = 6.dp,
+ shape = RoundedCornerShape(20.dp)
+ )
+// .clip(RoundedCornerShape(20.dp))
+ ) {
+ Image(
+ painter = painterResource(id = R.drawable.img_bg_catalog_card),
+ contentDescription = null,
+ contentScale = ContentScale.Crop,
+ modifier = Modifier.fillMaxSize()
+ )
+
+ Row(
+ modifier = Modifier
+ .fillMaxSize()
+ ) {
+ Column(
+ modifier = Modifier
+ .fillMaxHeight()
+ .weight(1f)
+ .padding(vertical = 8.dp)
+ .padding(start = 16.dp),
+ verticalArrangement = Arrangement.Center
+ ) {
+ Text(
+ text = component.name,
+ style = MaterialTheme.typography.titleSmall,
+ color = MaterialTheme.colorScheme.onPrimary
+ )
+
+ Text(
+ text = component.desc,
+ style = MaterialTheme.typography.bodySmall,
+ color = MaterialTheme.colorScheme.onPrimary,
+ maxLines = 3,
+ overflow = TextOverflow.Ellipsis
+ )
+ }
+
+ Spacer(modifier = Modifier.width(8.dp))
+
+
+ Box(
+ modifier = Modifier
+ .width(150.dp)
+ .fillMaxHeight()
+ .clip(RoundedCornerShape(topEnd = 20.dp, bottomEnd = 20.dp))
+ .background(Color.LightGray)
+ ) {
+ val greenColor = Color(0xff8bcd54)
+
+ AsyncImage(
+ model = component.imageExample,
+ contentDescription = null,
+ contentScale = ContentScale.Crop,
+ modifier = Modifier
+ .fillMaxSize()
+ )
+
+ Box(
+
+ modifier = Modifier
+ .fillMaxSize()
+ .background(
+ Brush.horizontalGradient(
+ .0f to greenColor,
+ .4F to greenColor.copy(alpha = 0.4f),
+ .6F to Color.Transparent,
+ )
+ )
+ )
+ }
+ }
+ }
+}
+
+@Preview(showBackground = true)
+@Composable
+fun CatalogCardPreview() {
+ TechwasMark02Theme {
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(20.dp),
+ contentAlignment = Alignment.Center
+ ) {
+ CatalogCard(
+ component = Component(
+ desc = "A small portable personal computer that not very reliable and not very versatile.",
+ id = 1,
+ name = "Laptop",
+ imageExample = "https://picsum.photos/seed/${Random.nextInt()}/320/120"
+ ),
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/capstone/techwasmark02/ui/component/DetectBox.kt b/app/src/main/java/com/capstone/techwasmark02/ui/component/DetectBox.kt
new file mode 100644
index 0000000..3e51644
--- /dev/null
+++ b/app/src/main/java/com/capstone/techwasmark02/ui/component/DetectBox.kt
@@ -0,0 +1,246 @@
+package com.capstone.techwasmark02.ui.component
+
+import androidx.compose.foundation.BorderStroke
+import androidx.compose.foundation.Image
+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.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.shape.CircleShape
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.draw.shadow
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.res.painterResource
+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.capstone.techwasmark02.R
+import com.capstone.techwasmark02.ui.componentType.FeatureBoxType
+import com.capstone.techwasmark02.ui.theme.TechwasMark02Theme
+import com.capstone.techwasmark02.ui.theme.gray
+
+@Composable
+fun FeatureBox(
+ modifier: Modifier = Modifier,
+ featureBoxType: FeatureBoxType,
+ onClick: () -> Unit
+) {
+ Box(
+ modifier = modifier
+ .width(150.dp)
+ .height(160.dp)
+ .shadow(
+ elevation = 4.dp,
+ shape = RoundedCornerShape(10.dp)
+ )
+ .clickable { onClick() }
+ ) {
+ Image(
+ painter = painterResource(id = featureBoxType.backGround),
+ contentDescription = null,
+ contentScale = ContentScale.Crop,
+ modifier = Modifier
+ .fillMaxSize()
+ )
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(16.dp)
+ ) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ ) {
+ Text(
+ text = featureBoxType.title,
+ style = MaterialTheme.typography.titleLarge,
+ color = Color.White
+ )
+ }
+
+ Spacer(modifier = Modifier.weight(1f))
+
+ Row(
+ modifier = Modifier
+ .fillMaxWidth(),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Icon(
+ modifier = Modifier.size(34.dp),
+ painter = painterResource(id = featureBoxType.icon),
+ contentDescription = null,
+ tint = Color.White
+ )
+ Spacer(modifier = Modifier.weight(1f))
+ SmallButton(
+ contentText = featureBoxType.buttonTitle,
+ onClick = onClick,
+ colorText = featureBoxType.buttonColor
+ )
+ }
+ }
+ }
+}
+
+@Composable
+fun FeatureBoxLarge(
+ modifier: Modifier = Modifier,
+ featureBoxType: FeatureBoxType,
+ onClick: () -> Unit
+) {
+ Box(
+ modifier = modifier
+ .fillMaxWidth()
+ .height(160.dp)
+ .shadow(
+ elevation = 4.dp,
+ shape = RoundedCornerShape(10.dp)
+ )
+ .clickable { onClick() }
+ ) {
+ Image(
+ painter = painterResource(id = featureBoxType.backGround),
+ contentDescription = null,
+ contentScale = ContentScale.Crop,
+ modifier = Modifier
+ .fillMaxSize()
+ )
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(16.dp)
+ ) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ ) {
+ Text(
+ text = featureBoxType.title,
+ style = MaterialTheme.typography.titleLarge.copy(
+ fontSize = 28.sp,
+ fontWeight = FontWeight.SemiBold
+ ),
+ color = Color.White,
+ )
+ }
+
+ Spacer(modifier = Modifier.weight(1f))
+
+ Row(
+ modifier = Modifier
+ .fillMaxWidth(),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Icon(
+ modifier = Modifier.size(44.dp),
+ painter = painterResource(id = featureBoxType.icon),
+ contentDescription = null,
+ tint = Color.White
+ )
+ Spacer(modifier = Modifier.weight(1f))
+ SmallButton(
+ contentText = featureBoxType.buttonTitle,
+ onClick = onClick,
+ colorText = featureBoxType.buttonColor
+ )
+ }
+ }
+ }
+}
+
+@Composable
+fun AboutUsBox(modifier: Modifier = Modifier) {
+ Box(
+ modifier = modifier
+ .width(150.dp)
+ .height(160.dp)
+ .clip(
+ RoundedCornerShape(
+ 10.dp
+ )
+ )
+ .background(gray)
+ ) {
+ Column(
+ modifier = Modifier.fillMaxSize(),
+ verticalArrangement = Arrangement.Center,
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Box(
+ modifier = Modifier
+ .size(50.dp)
+ .clip(CircleShape)
+ .border(
+ BorderStroke(
+ width = 2.dp,
+ color = Color.Black
+ ),
+ shape = CircleShape
+ ),
+ contentAlignment = Alignment.Center
+ ) {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_question_mark),
+ contentDescription = null
+ )
+ }
+ Text(
+ text = "About Us",
+ style = MaterialTheme.typography.titleMedium.copy(
+ fontWeight = FontWeight.Medium
+ )
+ )
+ }
+ }
+}
+
+@Preview(showBackground = true)
+@Composable
+fun DetectBoxPreview() {
+ TechwasMark02Theme {
+ Box(modifier = Modifier.padding(20.dp)) {
+ Column(modifier = Modifier.fillMaxWidth()) {
+
+ FeatureBoxLarge(featureBoxType = FeatureBoxType.Detection, onClick = {})
+
+ Spacer(modifier = Modifier.height(20.dp))
+
+ FeatureBox(
+ featureBoxType = FeatureBoxType.Detection,
+ onClick = {}
+ )
+
+ Spacer(modifier = Modifier.height(20.dp))
+
+ FeatureBox(featureBoxType = FeatureBoxType.Catalog, onClick = {})
+
+ Spacer(modifier = Modifier.height(20.dp))
+
+ FeatureBox(featureBoxType = FeatureBoxType.DropPoint, onClick = {})
+
+ Spacer(modifier = Modifier.height(20.dp))
+
+ AboutUsBox()
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/capstone/techwasmark02/ui/component/DetectionsResultBox.kt b/app/src/main/java/com/capstone/techwasmark02/ui/component/DetectionsResultBox.kt
new file mode 100644
index 0000000..fc9847e
--- /dev/null
+++ b/app/src/main/java/com/capstone/techwasmark02/ui/component/DetectionsResultBox.kt
@@ -0,0 +1,291 @@
+package com.capstone.techwasmark02.ui.component
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.background
+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.fillMaxHeight
+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.pager.PagerState
+import androidx.compose.foundation.pager.VerticalPager
+import androidx.compose.foundation.pager.rememberPagerState
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.*
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.alpha
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.draw.shadow
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.TransformOrigin
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.compose.ui.util.lerp
+import com.capstone.techwasmark02.R
+import com.capstone.techwasmark02.data.remote.response.Prediction
+import com.capstone.techwasmark02.ui.theme.TechwasMark02Theme
+import kotlinx.coroutines.launch
+import kotlin.math.absoluteValue
+
+@OptIn(ExperimentalFoundationApi::class)
+@Composable
+fun DetectionsResultBox(
+ modifier: Modifier = Modifier,
+ predictionList: List,
+ updateSelectedPrediction: (Int) -> Unit
+) {
+ val pagerState = rememberPagerState()
+ val coroutineScope = rememberCoroutineScope()
+
+ LaunchedEffect(pagerState) {
+ snapshotFlow { pagerState.currentPage }.collect { page ->
+ updateSelectedPrediction(page)
+ }
+ }
+
+ Box(
+ modifier = modifier
+ .fillMaxWidth()
+ .height(120.dp)
+ .shadow(
+ elevation = 6.dp,
+ shape = MaterialTheme.shapes.large
+ )
+ .background(MaterialTheme.colorScheme.tertiary)
+ .padding(horizontal = 24.dp),
+ contentAlignment = Alignment.Center
+ ) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth(),
+ horizontalArrangement = Arrangement.End
+ ) {
+ Box(
+ modifier = Modifier
+ .size(84.dp)
+ .clip(MaterialTheme.shapes.large)
+ .background(MaterialTheme.colorScheme.primaryContainer)
+ )
+ }
+
+ VerticalPager(
+ pageCount = predictionList.size,
+ modifier = Modifier
+ .height(84.dp),
+ pageSpacing = (-50).dp,
+ state = pagerState,
+ ) { page ->
+ ResultListItem(
+ detectionResult = predictionList[page],
+ pagerState = pagerState,
+ page = page,
+ modifier = Modifier
+ )
+ }
+
+ Column(
+ modifier = Modifier
+ .fillMaxHeight()
+ ) {
+ val prevButtonVisible by remember {
+ derivedStateOf {
+ pagerState.currentPage > 0
+ }
+ }
+
+ val nextButtonVisible by remember {
+ derivedStateOf {
+ pagerState.currentPage < dummyDetectionResultList.size - 1
+ }
+ }
+
+ IconButton(onClick = {
+ val prevPageIndex = pagerState.currentPage - 1
+ coroutineScope.launch { pagerState.animateScrollToPage(prevPageIndex) }
+ },
+ enabled = prevButtonVisible,
+ ) {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_arrow_up),
+ contentDescription = null,
+ tint = MaterialTheme.colorScheme.onTertiary,
+ modifier = Modifier
+ .alpha(if (prevButtonVisible) 1f else 0.3f),
+ )
+ }
+ Spacer(modifier = Modifier.weight(1f))
+ IconButton(onClick = {
+ val nextPageIndex = pagerState.currentPage + 1
+ coroutineScope.launch { pagerState.animateScrollToPage(nextPageIndex) }
+ },
+ enabled = nextButtonVisible
+ ) {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_arrow_down),
+ contentDescription = null,
+ tint = MaterialTheme.colorScheme.onTertiary,
+ modifier = Modifier
+ .alpha(if (nextButtonVisible) 1f else 0.3f)
+ )
+ }
+ }
+ }
+}
+
+@OptIn(ExperimentalFoundationApi::class)
+@Composable
+fun ResultListItem(
+ detectionResult: Prediction,
+ pagerState: PagerState,
+ page: Int,
+ modifier: Modifier = Modifier
+) {
+ Row(
+ modifier = modifier,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Text(
+ text = detectionResult.componentName,
+ style = MaterialTheme.typography.headlineSmall,
+ modifier = Modifier
+ .graphicsLayer {
+ val pageOffset = (
+ (pagerState.currentPage - page) + pagerState.currentPageOffsetFraction).absoluteValue
+ alpha = lerp(
+ start = 0.2f,
+ stop = 1f,
+ fraction = 1f - pageOffset.coerceIn(0f, 1f)
+ )
+ scaleX = lerp(
+ start = 0.6f,
+ stop = 1f,
+ fraction = 1f - pageOffset.coerceIn(0f, 1f)
+ )
+ scaleY = lerp(
+ start = 0.6f,
+ stop = 1f,
+ fraction = 1f - pageOffset.coerceIn(0f, 1f)
+ )
+ transformOrigin = TransformOrigin(
+ pivotFractionY = 0.4f,
+ pivotFractionX = 0.1f
+ )
+ }
+ )
+ Spacer(modifier = Modifier.weight(1f))
+
+ Box(
+ modifier = Modifier
+ .size(84.dp)
+ .background(Color.Transparent),
+ contentAlignment = Alignment.Center
+ ) {
+ Text(
+ text = "${detectionResult.componentValue.toInt()}%",
+ style = MaterialTheme.typography.headlineSmall.copy(
+ fontSize = 28.sp
+ ),
+ color = MaterialTheme.colorScheme.onPrimaryContainer,
+ modifier = Modifier
+ .graphicsLayer {
+ val pageOffset = (
+ (pagerState.currentPage - page) + pagerState.currentPageOffsetFraction).absoluteValue
+ alpha = lerp(
+ start = 0.5f,
+ stop = 1f,
+ fraction = 1f - pageOffset.coerceIn(0f, 1f)
+ )
+ scaleX = lerp(
+ start = 0.7f,
+ stop = 1f,
+ fraction = 1f - pageOffset.coerceIn(0f, 1f)
+ )
+ scaleY = lerp(
+ start = 0.7f,
+ stop = 1f,
+ fraction = 1f - pageOffset.coerceIn(0f, 1f)
+ )
+ transformOrigin = TransformOrigin(
+ pivotFractionY = 0.4f,
+ pivotFractionX = 0.5f
+ )
+ }
+ )
+ }
+ }
+}
+
+
+@Preview (showBackground = false)
+@Composable
+fun DetectionsResultBoxPreview() {
+ TechwasMark02Theme {
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(20.dp)
+ ) {
+ DetectionsResultBox(
+ predictionList = dummyDetectionResultList,
+ updateSelectedPrediction = {}
+ )
+ }
+ }
+}
+
+@OptIn(ExperimentalFoundationApi::class)
+@Preview (showBackground = true)
+@Composable
+fun ResultListItemPreview() {
+ TechwasMark02Theme {
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .background(MaterialTheme.colorScheme.primaryContainer)
+ .padding(20.dp)
+ ) {
+ ResultListItem(
+ detectionResult = Prediction(
+ componentId = 1,
+ componentName = "PCB",
+ componentValue = 80.988
+ ),
+ pagerState = PagerState(
+ initialPage = 0,
+ initialPageOffsetFraction = 0f,
+ ),
+ page = 0
+ )
+ }
+ }
+}
+
+private val dummyDetectionResultList: List = listOf(
+ Prediction(
+ componentId = 1,
+ componentName = "PCB",
+ componentValue = 80.00
+ ),
+ Prediction(
+ componentId = 2,
+ componentName = "Monitor",
+ componentValue = 15.00
+ ),
+ Prediction(
+ componentId = 3,
+ componentName = "Mouse",
+ componentValue = 5.00
+ )
+)
diff --git a/app/src/main/java/com/capstone/techwasmark02/ui/component/DetectionsResultSaveBottomSheet.kt b/app/src/main/java/com/capstone/techwasmark02/ui/component/DetectionsResultSaveBottomSheet.kt
new file mode 100644
index 0000000..7a3f657
--- /dev/null
+++ b/app/src/main/java/com/capstone/techwasmark02/ui/component/DetectionsResultSaveBottomSheet.kt
@@ -0,0 +1,57 @@
+package com.capstone.techwasmark02.ui.component
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.capstone.techwasmark02.ui.theme.TechwasMark02Theme
+
+@Composable
+fun DetectionsResultSaveBottomSheet(modifier: Modifier = Modifier) {
+ Column(
+ modifier = modifier
+ .fillMaxWidth()
+ .height(172.dp)
+ .background(MaterialTheme.colorScheme.tertiary)
+ .padding(horizontal = 20.dp)
+ .padding(top = 24.dp, bottom = 42.dp)
+ ) {
+ Text(
+ text = "Save your result",
+ style = MaterialTheme.typography.labelLarge
+ )
+
+ Spacer(modifier = Modifier.height(36.dp))
+
+ DefaultButton(
+ contentText = "Save",
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = 20.dp)
+ )
+ }
+}
+
+@Preview
+@Composable
+fun DetectionsResultSaveBottomSheetPreview() {
+ TechwasMark02Theme {
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .background(MaterialTheme.colorScheme.primaryContainer)
+ .padding(vertical = 20.dp)
+ ) {
+ DetectionsResultSaveBottomSheet()
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/capstone/techwasmark02/ui/component/ForumBox.kt b/app/src/main/java/com/capstone/techwasmark02/ui/component/ForumBox.kt
new file mode 100644
index 0000000..3e87a7a
--- /dev/null
+++ b/app/src/main/java/com/capstone/techwasmark02/ui/component/ForumBox.kt
@@ -0,0 +1,135 @@
+package com.capstone.techwasmark02.ui.component
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+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.shape.RoundedCornerShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.KeyboardArrowRight
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.draw.shadow
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import coil.compose.rememberAsyncImagePainter
+import com.capstone.techwasmark02.R
+import com.capstone.techwasmark02.ui.theme.TechwasMark02Theme
+import kotlin.random.Random
+
+@Composable
+fun ForumBox(
+ modifier: Modifier = Modifier,
+ photoUrl: String? = null,
+ onClick: () -> Unit = {},
+ title: String,
+ place: String,
+ desc: String
+
+) {
+ Box(
+ modifier = modifier
+ .height(80.dp)
+ .width(328.dp)
+ .shadow(
+ elevation = 8.dp,
+ shape = RoundedCornerShape(20.dp)
+ )
+ .background(Color.White)
+ .clip(RoundedCornerShape(20.dp))
+ .clickable {
+ onClick()
+ }
+ ) {
+ Row(
+ modifier
+ .fillMaxSize()
+ .padding(end = 27.5.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Image(
+ modifier = Modifier
+ .width(100.dp)
+ .fillMaxHeight()
+ .clip(RoundedCornerShape(
+ topStart= 20.dp,
+ bottomStart= 20.dp
+ )),
+ painter = rememberAsyncImagePainter(
+ model = photoUrl ?: "https://picsum.photos/seed/${Random.nextInt()}/320/120",
+ placeholder = painterResource(id = R.drawable.place_holder),
+ ),
+ contentScale = ContentScale.Crop,
+ contentDescription = null
+ )
+ Column(
+ modifier = Modifier
+ .weight(1f)
+ .padding(start = 8.2.dp)
+ ) {
+ Text(
+ text = title,
+ style = MaterialTheme.typography.labelMedium,
+ overflow = TextOverflow.Ellipsis,
+ maxLines = 1,
+ color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.8f)
+ )
+ Text(
+ text = desc,
+ style = MaterialTheme.typography.bodySmall,
+ color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f),
+ overflow = TextOverflow.Ellipsis,
+ maxLines = 1
+ )
+ Text(
+ text = place,
+ style = MaterialTheme.typography.bodySmall.copy(
+ fontWeight = FontWeight.SemiBold
+ ),
+ color = MaterialTheme.colorScheme.onSurface.copy(alpha = 0.7f),
+ overflow = TextOverflow.Ellipsis
+ )
+ }
+ Icon(
+ imageVector = Icons.Default.KeyboardArrowRight,
+ tint = MaterialTheme.colorScheme.onSurface,
+ contentDescription = null
+ )
+ }
+ }
+}
+
+@Preview(showBackground = true)
+@Composable
+fun ForumBoxPreview() {
+ TechwasMark02Theme() {
+ Box(modifier = Modifier
+ .fillMaxWidth()
+ .background(MaterialTheme.colorScheme.background)
+ .padding(20.dp)) {
+ ForumBox(
+ title = "Laptop Rusak",
+ desc = "Ini adalah akhir dunia",
+ place = "Bandung"
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/capstone/techwasmark02/ui/component/HtmlText.kt b/app/src/main/java/com/capstone/techwasmark02/ui/component/HtmlText.kt
new file mode 100644
index 0000000..da6a2d0
--- /dev/null
+++ b/app/src/main/java/com/capstone/techwasmark02/ui/component/HtmlText.kt
@@ -0,0 +1,69 @@
+package com.capstone.techwasmark02.ui.component
+
+import android.content.Context
+import android.text.TextUtils
+import android.util.TypedValue
+import android.view.Gravity
+import android.widget.TextView
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.toArgb
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.viewinterop.AndroidView
+import androidx.core.content.res.ResourcesCompat
+import com.capstone.techwasmark02.R
+import com.capstone.techwasmark02.utils.html.fromHtml
+import kotlin.math.max
+
+private const val SPACING_FIX = 3f
+
+@Composable
+fun HtmlText(
+ modifier: Modifier = Modifier,
+ html: String,
+ textStyle: TextStyle = MaterialTheme.typography.bodyMedium,
+ maxLine: Int? = null
+) {
+ AndroidView(
+ modifier = modifier,
+ update = { it.text = fromHtml(it.context, html) },
+ factory = { context ->
+ val spacingReady =
+ max(textStyle.lineHeight.value - textStyle.fontSize.value - SPACING_FIX, 0f)
+ val extraSpacing = spToPx(spacingReady.toInt(), context)
+ val gravity = when (textStyle.textAlign) {
+ TextAlign.Center -> Gravity.CENTER
+ TextAlign.End -> Gravity.END
+ else -> Gravity.START
+ }
+ val fontResId = when (textStyle.fontWeight) {
+ FontWeight.Medium -> R.font.poppins_medium
+ else -> R.font.poppins_regular
+ }
+ val font = ResourcesCompat.getFont(context, fontResId)
+
+ TextView(context).apply {
+ // general style
+ textSize = textStyle.fontSize.value
+ setLineSpacing(extraSpacing, 1f)
+ setTextColor(textStyle.color.toArgb())
+ setGravity(gravity)
+ typeface = font
+ if (maxLine != null) {
+ maxLines = maxLine
+ ellipsize = TextUtils.TruncateAt.END
+ }
+ }
+ }
+ )
+}
+
+fun spToPx(sp: Int, context: Context): Float =
+ TypedValue.applyDimension(
+ TypedValue.COMPLEX_UNIT_SP,
+ sp.toFloat(),
+ context.resources.displayMetrics
+ )
\ No newline at end of file
diff --git a/app/src/main/java/com/capstone/techwasmark02/ui/component/MainTextField.kt b/app/src/main/java/com/capstone/techwasmark02/ui/component/MainTextField.kt
index b42a779..37cfe76 100644
--- a/app/src/main/java/com/capstone/techwasmark02/ui/component/MainTextField.kt
+++ b/app/src/main/java/com/capstone/techwasmark02/ui/component/MainTextField.kt
@@ -1,24 +1,32 @@
package com.capstone.techwasmark02.ui.component
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text
+import androidx.compose.material3.TextField
import androidx.compose.material3.TextFieldDefaults
import androidx.compose.runtime.*
import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.onFocusChanged
+import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.input.PasswordVisualTransformation
import androidx.compose.ui.text.input.VisualTransformation
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
import com.capstone.techwasmark02.R
import com.capstone.techwasmark02.ui.theme.TechwasMark02Theme
@@ -31,33 +39,66 @@ fun DefaultTextField(
labelText: String,
placeHolderText: String
) {
- OutlinedTextField(
- value = value,
- onValueChange = onValueChange,
- label = {
- Text(
- text = labelText,
- style = MaterialTheme.typography.bodyMedium
- )
- },
- placeholder = {
- Text(
- text = placeHolderText,
- style = MaterialTheme.typography.bodyMedium
- )
- },
- singleLine = true,
- shape = MaterialTheme.shapes.large,
- colors = TextFieldDefaults.outlinedTextFieldColors(
- textColor = MaterialTheme.colorScheme.onTertiary,
- containerColor = MaterialTheme.colorScheme.tertiary,
- focusedLabelColor = MaterialTheme.colorScheme.onTertiary,
- focusedBorderColor = MaterialTheme.colorScheme.onTertiary,
- cursorColor = MaterialTheme.colorScheme.onTertiary,
- ),
- textStyle = MaterialTheme.typography.bodyMedium,
+ var hasFocus by remember {
+ mutableStateOf(false)
+ }
+
+ val focusColor = if (hasFocus) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onTertiary.copy(alpha = 0.6f)
+
+ Box(
modifier = modifier
- )
+ ) {
+ TextField(
+ value = value,
+ onValueChange = onValueChange,
+ placeholder = {
+ Text(
+ text = placeHolderText,
+ style = MaterialTheme.typography.bodyMedium
+ )
+ },
+ singleLine = true,
+ shape = MaterialTheme.shapes.large,
+ colors = TextFieldDefaults.textFieldColors(
+ textColor = if (hasFocus) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onTertiary,
+ containerColor = Color.Transparent,
+ focusedLabelColor = MaterialTheme.colorScheme.primary,
+ cursorColor = MaterialTheme.colorScheme.primary,
+ focusedIndicatorColor = Color.Transparent,
+ unfocusedIndicatorColor = Color.Transparent,
+ placeholderColor = MaterialTheme.colorScheme.onTertiary.copy(alpha = 0.6f),
+ focusedTrailingIconColor = focusColor
+ ),
+ textStyle = MaterialTheme.typography.bodyMedium.copy(
+ fontWeight = FontWeight.SemiBold
+ ),
+ modifier = modifier
+ .onFocusChanged { focusState -> hasFocus = focusState.hasFocus }
+ .border(
+ width = if (hasFocus) 3.dp else 1.5.dp,
+ color = if (hasFocus) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onTertiary.copy(
+ alpha = 0.6f
+ ),
+ shape = MaterialTheme.shapes.large
+ ),
+ )
+
+ Text(
+ text = labelText,
+ style = MaterialTheme.typography.labelMedium.copy(
+ fontWeight = FontWeight.SemiBold,
+ letterSpacing = 0.sp
+ ),
+ modifier = Modifier
+ .offset(
+ x = 16.dp,
+ y = -(10.dp)
+ )
+ .background(MaterialTheme.colorScheme.background)
+ .padding(horizontal = 4.dp),
+ color = focusColor
+ )
+ }
}
@OptIn(ExperimentalMaterial3Api::class)
@@ -69,53 +110,85 @@ fun PasswordTextField(
toggleShowPassword: () -> Unit,
modifier: Modifier = Modifier
) {
- OutlinedTextField(
- value = value,
- onValueChange = onValueChange,
- modifier = modifier,
- label = {
- Text(
- text = "Password",
- style = MaterialTheme.typography.bodyMedium
- )
- },
- placeholder = {
- Text(
- text = "user password",
- style = MaterialTheme.typography.bodyMedium
- )
- },
- singleLine = true,
- shape = MaterialTheme.shapes.large,
- colors = TextFieldDefaults.outlinedTextFieldColors(
- textColor = MaterialTheme.colorScheme.onTertiary,
- containerColor = MaterialTheme.colorScheme.tertiary,
- focusedLabelColor = MaterialTheme.colorScheme.onTertiary,
- focusedBorderColor = MaterialTheme.colorScheme.onTertiary,
- cursorColor = MaterialTheme.colorScheme.onTertiary,
- ),
- textStyle = MaterialTheme.typography.bodyMedium,
- visualTransformation = if (showPassword) {
- VisualTransformation.None
- } else {
- PasswordVisualTransformation()
- },
- trailingIcon = {
- IconButton(onClick = toggleShowPassword ) {
- if (showPassword) {
- Icon(
- painter = painterResource(id = R.drawable.ic_visibility_on),
- contentDescription = null
- )
- } else {
- Icon(
- painter = painterResource(id = R.drawable.ic_visibility_off),
- contentDescription = null
- )
+
+ var hasFocus by remember {
+ mutableStateOf(false)
+ }
+
+ val focusColor = if (hasFocus) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onTertiary.copy(alpha = 0.6f)
+
+ Box(
+ modifier = Modifier
+ ) {
+ TextField(
+ value = value,
+ onValueChange = onValueChange,
+ modifier = modifier
+ .onFocusChanged { focusState -> hasFocus = focusState.hasFocus }
+ .border(
+ width = if (hasFocus) 3.dp else 1.5.dp,
+ color = focusColor,
+ shape = MaterialTheme.shapes.large
+ ),
+ placeholder = {
+ Text(
+ text = "user password",
+ style = MaterialTheme.typography.bodyMedium
+ )
+ },
+ singleLine = true,
+ shape = MaterialTheme.shapes.large,
+ colors = TextFieldDefaults.textFieldColors(
+ textColor = focusColor,
+ containerColor = Color.Transparent,
+ focusedLabelColor = MaterialTheme.colorScheme.primary,
+ cursorColor = MaterialTheme.colorScheme.primary,
+ focusedIndicatorColor = Color.Transparent,
+ unfocusedIndicatorColor = Color.Transparent,
+ placeholderColor = MaterialTheme.colorScheme.onTertiary.copy(alpha = 0.6f),
+ focusedTrailingIconColor = focusColor,
+ unfocusedLabelColor = focusColor
+ ),
+ textStyle = MaterialTheme.typography.bodyMedium,
+ visualTransformation = if (showPassword) {
+ VisualTransformation.None
+ } else {
+ PasswordVisualTransformation()
+ },
+ trailingIcon = {
+ IconButton(onClick = toggleShowPassword ) {
+ if (showPassword) {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_visibility_on),
+ contentDescription = null
+ )
+ } else {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_visibility_off),
+ contentDescription = null
+ )
+ }
}
}
- }
- )
+ )
+
+
+ Text(
+ text = "Password",
+ style = MaterialTheme.typography.labelMedium.copy(
+ fontWeight = FontWeight.SemiBold,
+ letterSpacing = 0.sp
+ ),
+ modifier = Modifier
+ .offset(
+ x = 16.dp,
+ y = -(10.dp)
+ )
+ .background(MaterialTheme.colorScheme.background)
+ .padding(horizontal = 4.dp),
+ color = focusColor
+ )
+ }
}
@Preview (showBackground = true)
@@ -134,6 +207,7 @@ fun TextFieldPreview() {
Column(
modifier = Modifier
.fillMaxWidth()
+ .background(MaterialTheme.colorScheme.background)
.padding(20.dp)
) {
DefaultTextField(
diff --git a/app/src/main/java/com/capstone/techwasmark02/ui/component/ProfileBox.kt b/app/src/main/java/com/capstone/techwasmark02/ui/component/ProfileBox.kt
new file mode 100644
index 0000000..e1a8f2b
--- /dev/null
+++ b/app/src/main/java/com/capstone/techwasmark02/ui/component/ProfileBox.kt
@@ -0,0 +1,95 @@
+package com.capstone.techwasmark02.ui.component
+
+import androidx.compose.foundation.background
+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.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.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.capstone.techwasmark02.R
+import com.capstone.techwasmark02.ui.theme.TechwasMark02Theme
+
+@Composable
+fun ProfileBox(
+ modifier: Modifier = Modifier,
+ navigateToSetting: () -> Unit
+) {
+ Box(
+ modifier = modifier
+ .fillMaxWidth()
+ .height(120.dp)
+ .clip(MaterialTheme.shapes.large)
+ .background(MaterialTheme.colorScheme.tertiary)
+ .padding(horizontal = 24.dp),
+ contentAlignment = Alignment.Center
+ ) {
+ Column {
+ Row(
+ modifier = Modifier.fillMaxWidth(),
+ horizontalArrangement = Arrangement.Start,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Icon(
+ modifier = Modifier.size(24.dp),
+ painter = painterResource(id = R.drawable.ic_language),
+ contentDescription = "Language",
+ tint = MaterialTheme.colorScheme.onTertiary
+ )
+ Spacer(modifier = Modifier.width(8.dp))
+ Text(
+ text = "Language",
+ style = MaterialTheme.typography.labelMedium
+ )
+ }
+ Spacer(modifier = Modifier.height(8.dp))
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .clickable { navigateToSetting() },
+ horizontalArrangement = Arrangement.Start,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Icon(
+ modifier = Modifier.size(24.dp),
+ painter = painterResource(id = R.drawable.ic_settings),
+ contentDescription = "Settings",
+ tint = MaterialTheme.colorScheme.onTertiary
+ )
+ Spacer(modifier = Modifier.width(8.dp))
+ Text(
+ text = "Settings",
+ style = MaterialTheme.typography.labelMedium,
+ color = MaterialTheme.colorScheme.onTertiary
+ )
+ }
+ }
+ }
+}
+
+@Preview(showBackground = false)
+@Composable
+fun ProfileBoxPreview() {
+ TechwasMark02Theme {
+ Box(modifier = Modifier.padding(20.dp)) {
+ ProfileBox(
+ navigateToSetting = {}
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/capstone/techwasmark02/ui/component/SearchBox.kt b/app/src/main/java/com/capstone/techwasmark02/ui/component/SearchBox.kt
new file mode 100644
index 0000000..50613f9
--- /dev/null
+++ b/app/src/main/java/com/capstone/techwasmark02/ui/component/SearchBox.kt
@@ -0,0 +1,108 @@
+package com.capstone.techwasmark02.ui.component
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextField
+import androidx.compose.material3.TextFieldDefaults
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.draw.shadow
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.capstone.techwasmark02.R
+import com.capstone.techwasmark02.ui.theme.TechwasMark02Theme
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun SearchBox(
+ modifier: Modifier = Modifier,
+ value: String,
+ onValueChange: (String) -> Unit,
+) {
+
+ Box(
+ modifier = modifier
+ .fillMaxWidth()
+ .height(50.dp)
+ .shadow(
+ elevation = 4.dp,
+ shape = RoundedCornerShape(20.dp)
+ )
+ .background(Color.White)
+ .clip(RoundedCornerShape(20.dp)),
+ contentAlignment = Alignment.Center
+ ) {
+ TextField(
+ modifier = Modifier
+ .fillMaxHeight()
+ .fillMaxWidth(),
+ value = value,
+ onValueChange = onValueChange,
+ placeholder = {
+ Text(
+ text = "Search",
+ style = MaterialTheme.typography.bodyMedium,
+ color = Color.Gray
+ )
+ },
+ singleLine = true,
+ colors = TextFieldDefaults.textFieldColors(
+ containerColor = Color.White,
+ textColor = Color.Gray,
+ cursorColor = MaterialTheme.colorScheme.primary,
+ focusedIndicatorColor = Color.Transparent,
+ disabledIndicatorColor = Color.Transparent,
+ unfocusedIndicatorColor = Color.Transparent
+ ),
+ trailingIcon = {
+ IconButton(onClick = { /*TODO*/ }) {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_search),
+ contentDescription = null,
+ tint = MaterialTheme.colorScheme.primary,
+ modifier = Modifier
+ .padding(end = 12.dp)
+ )
+ }
+ }
+ )
+ }
+}
+
+@Preview(showBackground = true)
+@Composable
+fun SearchBoxPreview() {
+ TechwasMark02Theme {
+ Box(modifier = Modifier.padding(20.dp)) {
+
+ var value by remember {
+ mutableStateOf("")
+ }
+
+ SearchBox(
+ value = value,
+ onValueChange = { newValue ->
+ value = newValue
+ }
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/capstone/techwasmark02/ui/component/SelectableText.kt b/app/src/main/java/com/capstone/techwasmark02/ui/component/SelectableText.kt
new file mode 100644
index 0000000..bceaa2d
--- /dev/null
+++ b/app/src/main/java/com/capstone/techwasmark02/ui/component/SelectableText.kt
@@ -0,0 +1,71 @@
+package com.capstone.techwasmark02.ui.component
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+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.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.capstone.techwasmark02.ui.componentType.ArticleFilterType
+import com.capstone.techwasmark02.ui.theme.TechwasMark02Theme
+
+@Composable
+fun SelectableText(
+ modifier: Modifier = Modifier,
+ filterType: ArticleFilterType,
+ selected: Boolean,
+ onClick: () -> Unit
+) {
+ Box(
+ modifier = modifier
+ .clip(RoundedCornerShape(20.dp))
+ .clickable {
+ onClick()
+ }
+ .background(
+ if (selected) MaterialTheme.colorScheme.primary else Color.LightGray.copy(alpha = 0.6f)
+ )
+ .padding(vertical = 4.dp, horizontal = 8.dp),
+ ) {
+ Text(
+ maxLines = 1,
+ text = filterType.type,
+ style = MaterialTheme.typography.bodySmall,
+ modifier = Modifier,
+ color = if (selected) MaterialTheme.colorScheme.onPrimary else MaterialTheme.colorScheme.onBackground
+ )
+ }
+}
+
+@Preview(showBackground = true)
+@Composable
+fun SelectableTextPreview() {
+ TechwasMark02Theme {
+ Row(modifier = Modifier.padding(20.dp), verticalAlignment = Alignment.CenterVertically){
+ SelectableText(
+ filterType = ArticleFilterType.WashingMachine,
+ selected = true,
+ onClick = {}
+ )
+
+ Spacer(modifier = Modifier.width(20.dp))
+
+ SelectableText(
+ filterType = ArticleFilterType.General,
+ selected = false,
+ onClick = {}
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/capstone/techwasmark02/ui/component/SettingItem.kt b/app/src/main/java/com/capstone/techwasmark02/ui/component/SettingItem.kt
new file mode 100644
index 0000000..3bab856
--- /dev/null
+++ b/app/src/main/java/com/capstone/techwasmark02/ui/component/SettingItem.kt
@@ -0,0 +1,88 @@
+package com.capstone.techwasmark02.ui.component
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+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.material.icons.Icons
+import androidx.compose.material.icons.filled.KeyboardArrowRight
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.shadow
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.capstone.techwasmark02.ui.componentType.SettingItemType
+import com.capstone.techwasmark02.ui.theme.TechwasMark02Theme
+
+@Composable
+fun SettingItem(
+ modifier: Modifier = Modifier,
+ icon: Int,
+ title: String
+) {
+ Box(
+ modifier = modifier
+ .fillMaxWidth()
+ .height(56.dp)
+ .shadow(
+ elevation = 4.dp,
+ shape = RoundedCornerShape(10.dp)
+ )
+ .background(Color.White),
+ contentAlignment = Alignment.Center
+ ) {
+ Row(
+ horizontalArrangement = Arrangement.SpaceBetween,
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = 16.dp)
+ ) {
+ Row(
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Icon(
+ painter = painterResource(id = icon),
+ tint = MaterialTheme.colorScheme.onSurface,
+ contentDescription = title
+ )
+ Spacer(modifier = Modifier.width(8.dp))
+ Text(
+ text = title,
+ style = MaterialTheme.typography.labelMedium,
+ color = MaterialTheme.colorScheme.onTertiary
+ )
+ }
+ Icon(
+ imageVector = Icons.Default.KeyboardArrowRight,
+ tint = MaterialTheme.colorScheme.onSurface,
+ contentDescription = null
+ )
+ }
+ }
+}
+
+@Preview(showBackground = true)
+@Composable
+fun SettingItemPreview() {
+ TechwasMark02Theme {
+ Box(modifier = Modifier.padding(20.dp)) {
+ SettingItem(
+ modifier = Modifier.fillMaxWidth(),
+ icon = SettingItemType.Password.icon,
+ title = SettingItemType.Password.title
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/capstone/techwasmark02/ui/component/TopBar.kt b/app/src/main/java/com/capstone/techwasmark02/ui/component/TopBar.kt
index b566d7b..ca0d008 100644
--- a/app/src/main/java/com/capstone/techwasmark02/ui/component/TopBar.kt
+++ b/app/src/main/java/com/capstone/techwasmark02/ui/component/TopBar.kt
@@ -1,10 +1,9 @@
package com.capstone.techwasmark02.ui.component
+import androidx.activity.compose.BackHandler
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Spacer
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.material3.ExperimentalMaterial3Api
@@ -18,17 +17,19 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
-import androidx.compose.ui.text.font.Font
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.capstone.techwasmark02.R
import com.capstone.techwasmark02.ui.theme.TechwasMark02Theme
@OptIn(ExperimentalMaterial3Api::class)
@Composable
-fun InverseTopBar(onClickNavigationIcon: () -> Unit, modifier: Modifier = Modifier) {
+fun InverseTopBar(onClickNavigationIcon: () -> Unit, modifier: Modifier = Modifier, pageTitle: String = "") {
+ BackHandler(true) {
+ onClickNavigationIcon()
+ }
+
TopAppBar(
navigationIcon = {
IconButton(
@@ -38,12 +39,22 @@ fun InverseTopBar(onClickNavigationIcon: () -> Unit, modifier: Modifier = Modifi
painter = painterResource(id = R.drawable.ic_arrow_back),
contentDescription = null,
tint = MaterialTheme.colorScheme.primary,
- modifier = Modifier.size(32.dp)
+ modifier = Modifier.size(24.dp)
)
}
},
title = {},
- actions = {},
+ actions = {
+ Text(
+ text = pageTitle,
+ color = MaterialTheme.colorScheme.primary,
+ style = MaterialTheme.typography.labelLarge.copy(
+ fontWeight = FontWeight.Normal
+ ),
+ modifier = Modifier
+ .padding(end = 20.dp),
+ )
+ },
colors = TopAppBarDefaults.smallTopAppBarColors(
containerColor = Color.Transparent,
),
@@ -63,21 +74,21 @@ fun DefaultTopBar(pageTitle: String = "", onClickNavigationIcon: () -> Unit, mod
painter = painterResource(id = R.drawable.ic_arrow_back),
contentDescription = null,
tint = MaterialTheme.colorScheme.onPrimary,
- modifier = Modifier.size(32.dp)
+ modifier = Modifier.size(24.dp)
)
}
},
title = {},
actions = {
- Text(
- text = pageTitle,
- color = MaterialTheme.colorScheme.onPrimary,
- style = MaterialTheme.typography.labelLarge.copy(
- fontWeight = FontWeight.Normal
- ),
- modifier = Modifier
- .padding(end = 20.dp)
- )
+ Text(
+ text = pageTitle,
+ color = MaterialTheme.colorScheme.onPrimary,
+ style = MaterialTheme.typography.labelLarge.copy(
+ fontWeight = FontWeight.Normal
+ ),
+ modifier = Modifier
+ .padding(end = 20.dp)
+ )
},
colors = TopAppBarDefaults.smallTopAppBarColors(
containerColor = MaterialTheme.colorScheme.primary,
@@ -86,6 +97,41 @@ fun DefaultTopBar(pageTitle: String = "", onClickNavigationIcon: () -> Unit, mod
)
}
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun TransparentTopBar(pageTitle: String = "", onClickNavigationIcon: () -> Unit, modifier: Modifier = Modifier) {
+ TopAppBar(
+ navigationIcon = {
+ IconButton(
+ onClick = onClickNavigationIcon,
+ ) {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_arrow_back),
+ contentDescription = null,
+ tint = MaterialTheme.colorScheme.onPrimary,
+ modifier = Modifier.size(24.dp)
+ )
+ }
+ },
+ title = {},
+ actions = {
+ Text(
+ text = pageTitle,
+ color = MaterialTheme.colorScheme.onPrimary,
+ style = MaterialTheme.typography.labelLarge.copy(
+ fontWeight = FontWeight.Normal
+ ),
+ modifier = Modifier
+ .padding(end = 20.dp)
+ )
+ },
+ colors = TopAppBarDefaults.smallTopAppBarColors(
+ containerColor = Color.Transparent,
+ ),
+ modifier = modifier
+ )
+}
+
@Preview (showBackground = true)
@Composable
fun InverseTopBarPreview() {
@@ -116,4 +162,22 @@ fun DefaultTopBarPreview() {
)
}
}
+}
+
+@Preview (showBackground = true)
+@Composable
+fun TransparantTopBarPreview() {
+ TechwasMark02Theme {
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .background(MaterialTheme.colorScheme.onBackground)
+ .padding(20.dp)
+ ) {
+ TransparentTopBar(
+ pageTitle = "Detail",
+ onClickNavigationIcon = {}
+ )
+ }
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/capstone/techwasmark02/ui/component/UsableComponentBottomSheet.kt b/app/src/main/java/com/capstone/techwasmark02/ui/component/UsableComponentBottomSheet.kt
new file mode 100644
index 0000000..d6da43c
--- /dev/null
+++ b/app/src/main/java/com/capstone/techwasmark02/ui/component/UsableComponentBottomSheet.kt
@@ -0,0 +1,93 @@
+package com.capstone.techwasmark02.ui.component
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import coil.compose.AsyncImage
+import com.capstone.techwasmark02.data.remote.response.SmallPart
+import com.capstone.techwasmark02.ui.theme.TechwasMark02Theme
+
+@Composable
+fun UsableComponentBottomSheet(
+ modifier: Modifier = Modifier,
+ smallPart: SmallPart
+) {
+ Column(
+ modifier = modifier
+ .fillMaxWidth()
+ .height(360.dp)
+ .background(MaterialTheme.colorScheme.tertiary)
+ .padding(top = 32.dp, bottom = 42.dp)
+ ) {
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = 20.dp)
+ ) {
+ Text(
+ text = smallPart.name,
+ style = MaterialTheme.typography.labelLarge
+ )
+
+ Text(
+ text = smallPart.description,
+ style = MaterialTheme.typography.bodySmall
+ )
+
+ Spacer(modifier = Modifier.height(24.dp))
+
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .clip(MaterialTheme.shapes.large)
+ .background(Color.LightGray)
+ ) {
+ AsyncImage(
+ model = smallPart.imageURL,
+ contentDescription = null,
+ contentScale = ContentScale.Crop,
+ modifier = Modifier
+ .fillMaxSize()
+ )
+ }
+ }
+ }
+
+}
+
+@Preview (showBackground = true)
+@Composable
+fun UsableComponentBottomSheetPreview() {
+ TechwasMark02Theme {
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .background(MaterialTheme.colorScheme.primaryContainer)
+ .padding(vertical = 20.dp)
+ ) {
+ UsableComponentBottomSheet(
+ smallPart = SmallPart(
+ compID = 0,
+ description = "jkdfau adkjfaku aksdufka ufausd f",
+ id = 0,
+ imageURL = "",
+ name = "RAM"
+ )
+ )
+ }
+ }
+}
diff --git a/app/src/main/java/com/capstone/techwasmark02/ui/component/UsableComponentItem.kt b/app/src/main/java/com/capstone/techwasmark02/ui/component/UsableComponentItem.kt
new file mode 100644
index 0000000..7c338c9
--- /dev/null
+++ b/app/src/main/java/com/capstone/techwasmark02/ui/component/UsableComponentItem.kt
@@ -0,0 +1,98 @@
+package com.capstone.techwasmark02.ui.component
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Box
+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.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.shadow
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.text.style.TextOverflow
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import coil.compose.AsyncImage
+import com.capstone.techwasmark02.R
+import com.capstone.techwasmark02.data.remote.response.SmallPart
+import com.capstone.techwasmark02.ui.theme.TechwasMark02Theme
+
+@Composable
+fun UsableComponentItem(
+ modifier: Modifier = Modifier,
+ onClick: () -> Unit,
+ usableComponent: SmallPart
+) {
+ Row(
+ modifier = modifier
+ .width(152.dp)
+ .height(60.dp)
+ .shadow(
+ elevation = 6.dp,
+ shape = MaterialTheme.shapes.large
+ )
+ .background(MaterialTheme.colorScheme.tertiary)
+ .clickable { onClick() }
+ ) {
+ AsyncImage(
+ model = usableComponent.imageURL,
+ contentDescription = null,
+ placeholder = painterResource(id = R.drawable.place_holder),
+ contentScale = ContentScale.Crop,
+ modifier = Modifier
+ .width(55.dp)
+ .fillMaxHeight()
+ )
+
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(8.dp),
+ contentAlignment = Alignment.Center
+ ) {
+ Text(
+ text = usableComponent.name,
+ style = MaterialTheme.typography.labelLarge.copy(
+ fontWeight = FontWeight.SemiBold
+ ),
+ overflow = TextOverflow.Ellipsis,
+ maxLines = 1
+ )
+ }
+ }
+}
+
+@Preview (showBackground = true)
+@Composable
+fun UsableCompoenentItemPreview() {
+ TechwasMark02Theme {
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .background(MaterialTheme.colorScheme.primaryContainer)
+ .padding(20.dp),
+ contentAlignment = Alignment.Center
+ ) {
+ UsableComponentItem(
+ onClick = {},
+ usableComponent = SmallPart(
+ compID = 3,
+ description = "",
+ id = 2,
+ imageURL = "",
+ name = "RAM"
+ )
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/capstone/techwasmark02/ui/component/UserGreet.kt b/app/src/main/java/com/capstone/techwasmark02/ui/component/UserGreet.kt
index 38255be..389f798 100644
--- a/app/src/main/java/com/capstone/techwasmark02/ui/component/UserGreet.kt
+++ b/app/src/main/java/com/capstone/techwasmark02/ui/component/UserGreet.kt
@@ -19,6 +19,9 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.SpanStyle
+import androidx.compose.ui.text.buildAnnotatedString
+import androidx.compose.ui.text.withStyle
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.capstone.techwasmark02.R
@@ -40,16 +43,18 @@ fun UserGreet(
Row(
modifier = Modifier
) {
+ val text = buildAnnotatedString {
+ append("Hello,\n")
+ withStyle(style = SpanStyle(color = MaterialTheme.colorScheme.primary)) {
+ append("$userName.")
+ }
+ }
+
Text(
- text = "Hello, ",
+ text = text,
style = MaterialTheme.typography.headlineSmall,
color = MaterialTheme.colorScheme.inverseSurface,
)
- Text(
- text = "$userName.",
- style = MaterialTheme.typography.headlineSmall,
- color = MaterialTheme.colorScheme.primary
- )
}
Box(
diff --git a/app/src/main/java/com/capstone/techwasmark02/ui/componentType/ArticleFilterType.kt b/app/src/main/java/com/capstone/techwasmark02/ui/componentType/ArticleFilterType.kt
new file mode 100644
index 0000000..adf78bc
--- /dev/null
+++ b/app/src/main/java/com/capstone/techwasmark02/ui/componentType/ArticleFilterType.kt
@@ -0,0 +1,37 @@
+package com.capstone.techwasmark02.ui.componentType
+
+sealed class ArticleFilterType(val type: String, val id: Int) {
+
+ object General: ArticleFilterType(type = "General", id = 0)
+
+ object Battery: ArticleFilterType(type = "Battery", id = 1)
+
+ object Cable: ArticleFilterType(type = "Cable", id = 2)
+
+ object CrtTv: ArticleFilterType(type = "CRT TV", id = 3)
+
+ object EKettle: ArticleFilterType(type = "E-kettle", id = 4)
+
+ object Refrigerator: ArticleFilterType(type = "Refrigerator", id = 5)
+
+ object Keyboard: ArticleFilterType(type = "Keyboard", id = 6)
+
+ object Laptop: ArticleFilterType(type = "Laptop", id = 7)
+
+ object LightBulb: ArticleFilterType(type = "Light Bulb", id = 8)
+
+ object Monitor: ArticleFilterType(type = "Monitor", id = 9)
+
+ object Mouse: ArticleFilterType(type = "Mouse", id = 10)
+
+ object PCB: ArticleFilterType(type = "PCB", id = 11)
+
+ object Printer: ArticleFilterType(type = "Printer", id = 12)
+
+ object RiceCooker: ArticleFilterType(type = "Rice Cooker", id = 13)
+
+ object WashingMachine: ArticleFilterType(type = "Washing Machine", id = 14)
+
+ object Phone: ArticleFilterType(type = "Phone", id = 15)
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/capstone/techwasmark02/ui/componentType/BottomBarItemType.kt b/app/src/main/java/com/capstone/techwasmark02/ui/componentType/BottomBarItemType.kt
index 8faca17..551b166 100644
--- a/app/src/main/java/com/capstone/techwasmark02/ui/componentType/BottomBarItemType.kt
+++ b/app/src/main/java/com/capstone/techwasmark02/ui/componentType/BottomBarItemType.kt
@@ -2,9 +2,9 @@ package com.capstone.techwasmark02.ui.componentType
import com.capstone.techwasmark02.R
-sealed class BottomBarItemType(val title: String, val icon: Int) {
- object Home: BottomBarItemType(title = "Home", icon = R.drawable.ic_home)
- object Forum: BottomBarItemType(title = "Forum", icon = R.drawable.ic_forum)
- object Article: BottomBarItemType(title = "Article", icon = R.drawable.ic_article)
- object Profile: BottomBarItemType(title = "Profile", icon = R.drawable.ic_profile)
+sealed class BottomBarItemType(val title: String, val icon: Int, val pageIndex: Int) {
+ object Home: BottomBarItemType(title = "Home", icon = R.drawable.ic_home, pageIndex = 0)
+ object Forum: BottomBarItemType(title = "Forum", icon = R.drawable.ic_forum, pageIndex = 1)
+ object Article: BottomBarItemType(title = "Article", icon = R.drawable.ic_article, pageIndex = 2)
+ object Profile: BottomBarItemType(title = "Profile", icon = R.drawable.ic_profile, pageIndex = 3)
}
diff --git a/app/src/main/java/com/capstone/techwasmark02/ui/componentType/FeatureBoxType.kt b/app/src/main/java/com/capstone/techwasmark02/ui/componentType/FeatureBoxType.kt
new file mode 100644
index 0000000..7eeef60
--- /dev/null
+++ b/app/src/main/java/com/capstone/techwasmark02/ui/componentType/FeatureBoxType.kt
@@ -0,0 +1,13 @@
+package com.capstone.techwasmark02.ui.componentType
+
+import androidx.compose.ui.graphics.Color
+import com.capstone.techwasmark02.R
+import com.capstone.techwasmark02.ui.theme.Green35
+import com.capstone.techwasmark02.ui.theme.purple
+import com.capstone.techwasmark02.ui.theme.sakura
+
+sealed class FeatureBoxType(val title: String, val buttonTitle: String, val icon: Int, val backGround: Int, val buttonColor: Color) {
+ object Detection: FeatureBoxType(title = "Detect\ne-waste", buttonTitle = "Detect", icon = R.drawable.ic_center_focus, backGround = R.drawable.img_bg_green_large, buttonColor = Green35)
+ object Catalog: FeatureBoxType(title = "E-waste catalog", buttonTitle = "Open", icon = R.drawable.ic_menu_book, backGround = R.drawable.img_bg_purple, buttonColor = purple)
+ object DropPoint: FeatureBoxType(title = "Nearby drop point", buttonTitle = "Locate", icon = R.drawable.ic_location_on, backGround = R.drawable.img_bg_sakura, buttonColor = sakura)
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/capstone/techwasmark02/ui/componentType/SettingItemType.kt b/app/src/main/java/com/capstone/techwasmark02/ui/componentType/SettingItemType.kt
new file mode 100644
index 0000000..2473e93
--- /dev/null
+++ b/app/src/main/java/com/capstone/techwasmark02/ui/componentType/SettingItemType.kt
@@ -0,0 +1,14 @@
+package com.capstone.techwasmark02.ui.componentType
+
+import com.capstone.techwasmark02.R
+
+sealed class SettingItemType(val title: String, val icon: Int) {
+
+ object Password: SettingItemType(title = "Security", icon = R.drawable.ic_shield)
+
+ object Comment: SettingItemType(title = "Comments", icon = R.drawable.ic_chat_buble)
+
+ object Notification: SettingItemType(title = "Notifications", icon = R.drawable.ic_fill_notification)
+
+ object Language: SettingItemType(title = "Language", icon = R.drawable.ic_language)
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/capstone/techwasmark02/ui/navigation/Screen.kt b/app/src/main/java/com/capstone/techwasmark02/ui/navigation/Screen.kt
new file mode 100644
index 0000000..9312814
--- /dev/null
+++ b/app/src/main/java/com/capstone/techwasmark02/ui/navigation/Screen.kt
@@ -0,0 +1,292 @@
+package com.capstone.techwasmark02.ui.navigation
+
+import androidx.compose.animation.AnimatedContentScope
+import androidx.compose.animation.EnterTransition
+import androidx.compose.animation.ExitTransition
+import androidx.compose.animation.ExperimentalAnimationApi
+import androidx.compose.animation.core.tween
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
+import androidx.navigation.NavBackStackEntry
+
+@OptIn(ExperimentalAnimationApi::class)
+sealed class Screen constructor(val route: String, val enterTransition: (AnimatedContentScope.() -> EnterTransition?), val exitTransition: (AnimatedContentScope.() -> ExitTransition?)) {
+ object OnBoarding: Screen(
+ route = "OnBoarding",
+ enterTransition = {
+ fadeIn(animationSpec = tween(700))
+ },
+ exitTransition = {
+// slideOutOfContainer(AnimatedContentScope.SlideDirection.Left, animationSpec = tween(700))
+ fadeOut(
+ animationSpec = tween(700)
+ )
+ }
+ )
+
+ object SignIn: Screen(
+ route = "SignIn",
+ enterTransition = {
+// slideIntoContainer(
+// AnimatedContentScope.SlideDirection.Left,
+// animationSpec = tween(700)
+// )
+ fadeIn(animationSpec = tween(700))
+ },
+ exitTransition = {
+// slideOutOfContainer(AnimatedContentScope.SlideDirection.Left, animationSpec = tween(700))
+ fadeOut(
+ animationSpec = tween(700)
+ )
+ }
+ )
+
+ object SignUp: Screen(
+ route = "SignUp",
+ enterTransition = {
+// slideIntoContainer(
+// AnimatedContentScope.SlideDirection.Left,
+// animationSpec = tween(700)
+// )
+ fadeIn(animationSpec = tween(700))
+ },
+ exitTransition = {
+// slideOutOfContainer(AnimatedContentScope.SlideDirection.Left, animationSpec = tween(700))
+ fadeOut(
+ animationSpec = tween(700)
+ )
+ }
+ )
+
+ object Main: Screen(
+ route = "Main",
+ enterTransition = {
+// slideIntoContainer(
+// AnimatedContentScope.SlideDirection.Left,
+// animationSpec = tween(700)
+// )
+ fadeIn(animationSpec = tween(300))
+ },
+ exitTransition = {
+// slideOutOfContainer(AnimatedContentScope.SlideDirection.Up, animationSpec = tween(700))
+ fadeOut(
+ animationSpec = tween(700)
+ )
+ }
+ )
+
+ object Home: Screen(
+ route = "home",
+ enterTransition = {
+ slideIntoContainer(
+ AnimatedContentScope.SlideDirection.Left,
+ animationSpec = tween(700)
+ )
+ },
+ exitTransition = {
+// slideOutOfContainer(
+// AnimatedContentScope.SlideDirection.Left, animationSpec = tween(
+// durationMillis = 700,
+// )
+// )
+ fadeOut(
+ animationSpec = tween(700)
+ )
+ }
+ )
+
+ object Forum: Screen(
+ route = "forum",
+ enterTransition = {
+ slideIntoContainer(
+ AnimatedContentScope.SlideDirection.Left,
+ animationSpec = tween(700)
+ )
+ },
+ exitTransition = {
+ slideOutOfContainer(
+ AnimatedContentScope.SlideDirection.Left, animationSpec = tween(
+ durationMillis = 700,
+ )
+ )
+ }
+ )
+
+ object SingleForum: Screen(
+ route = "SingleForum",
+ enterTransition = {
+ slideIntoContainer(
+ AnimatedContentScope.SlideDirection.Left,
+ animationSpec = tween(700)
+ )
+ },
+ exitTransition = {
+ slideOutOfContainer(
+ AnimatedContentScope.SlideDirection.Left, animationSpec = tween(
+ durationMillis = 700,
+ )
+ )
+ }
+ )
+
+ object CreateForum: Screen(
+ route = "CreateForum",
+ enterTransition = {
+ fadeIn(
+ animationSpec = tween(700)
+ )
+ },
+ exitTransition = {
+ fadeOut(
+ animationSpec = tween(700)
+ )
+ }
+ )
+
+ object Profile: Screen(
+ route = "profile",
+ enterTransition = {
+ slideIntoContainer(
+ AnimatedContentScope.SlideDirection.Left,
+ animationSpec = tween(700)
+ )
+ },
+ exitTransition = {
+ slideOutOfContainer(
+ AnimatedContentScope.SlideDirection.Left, animationSpec = tween(
+ durationMillis = 700,
+ )
+ )
+ }
+ )
+
+ object Setting: Screen(
+ route = "setting",
+ enterTransition = {
+ slideIntoContainer(
+ AnimatedContentScope.SlideDirection.Left,
+ animationSpec = tween(700)
+ )
+ },
+ exitTransition = {
+ slideOutOfContainer(
+ AnimatedContentScope.SlideDirection.Left, animationSpec = tween(
+ durationMillis = 700,
+ )
+ )
+ }
+ )
+
+ object Camera: Screen(
+ route = "camera",
+ enterTransition = {
+// slideIntoContainer(
+// AnimatedContentScope.SlideDirection.Up,
+// animationSpec = tween(700)
+// )
+ fadeIn(animationSpec = tween(1000))
+ },
+ exitTransition = {
+ slideOutOfContainer(AnimatedContentScope.SlideDirection.Down, animationSpec = tween(700))
+ fadeOut(animationSpec = tween(1000))
+ }
+ )
+
+ object Catalog: Screen(
+ route = "catalog",
+ enterTransition = {
+// slideIntoContainer(
+// AnimatedContentScope.SlideDirection.Left,
+// animationSpec = tween(700)
+// )
+ fadeIn(animationSpec = tween(300))
+ },
+ exitTransition = {
+// slideOutOfContainer(AnimatedContentScope.SlideDirection.Left, animationSpec = tween(700))
+ fadeOut(animationSpec = tween(300))
+ }
+ )
+
+ object SingleCatalog: Screen(
+ route = "singleCatalog",
+ enterTransition = {
+ slideIntoContainer(
+ AnimatedContentScope.SlideDirection.Left,
+ animationSpec = tween(700)
+ )
+ },
+ exitTransition = {
+ slideOutOfContainer(AnimatedContentScope.SlideDirection.Left, animationSpec = tween(700))
+ }
+ )
+
+ object DetectionResult: Screen(
+ route = "detectionResult",
+ enterTransition = {
+ slideIntoContainer(
+ AnimatedContentScope.SlideDirection.Left,
+ animationSpec = tween(700)
+ )
+ },
+ exitTransition = {
+ slideOutOfContainer(AnimatedContentScope.SlideDirection.Left, animationSpec = tween(700))
+ }
+ )
+
+ object Article: Screen(
+ route = "article",
+ enterTransition = {
+ slideIntoContainer(
+ AnimatedContentScope.SlideDirection.Left,
+ animationSpec = tween(700)
+ )
+ },
+ exitTransition = {
+ slideOutOfContainer(AnimatedContentScope.SlideDirection.Left, animationSpec = tween(700))
+ }
+ )
+
+ object SingleArticle: Screen(
+ route = "singleArticle",
+ enterTransition = {
+ slideIntoContainer(
+ AnimatedContentScope.SlideDirection.Left,
+ animationSpec = tween(700)
+ )
+ },
+ exitTransition = {
+ slideOutOfContainer(AnimatedContentScope.SlideDirection.Left, animationSpec = tween(700))
+ }
+ )
+
+ object Splash: Screen(
+ route = "splash",
+ enterTransition = {
+// slideIntoContainer(
+// AnimatedContentScope.SlideDirection.Left,
+// animationSpec = tween(700)
+// )
+ fadeIn(animationSpec = tween(300))
+ },
+ exitTransition = {
+// slideOutOfContainer(AnimatedContentScope.SlideDirection.Left, animationSpec = tween(700))
+ fadeOut(animationSpec = tween(300))
+ }
+ )
+
+ object Maps: Screen(
+ route = "maps",
+ enterTransition = {
+ slideIntoContainer(
+ AnimatedContentScope.SlideDirection.Left,
+ animationSpec = tween(700)
+ )
+ },
+ exitTransition = {
+ slideOutOfContainer(AnimatedContentScope.SlideDirection.Left, animationSpec = tween(
+ transitionDuration))
+ }
+ )
+}
+
+private const val transitionDuration = 300
\ No newline at end of file
diff --git a/app/src/main/java/com/capstone/techwasmark02/ui/screen/article/ArticleScreen.kt b/app/src/main/java/com/capstone/techwasmark02/ui/screen/article/ArticleScreen.kt
new file mode 100644
index 0000000..66f9b5a
--- /dev/null
+++ b/app/src/main/java/com/capstone/techwasmark02/ui/screen/article/ArticleScreen.kt
@@ -0,0 +1,261 @@
+package com.capstone.techwasmark02.ui.screen.article
+
+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.PaddingValues
+import androidx.compose.foundation.layout.Spacer
+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.lazy.LazyRow
+import androidx.compose.foundation.lazy.grid.GridCells
+import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
+import androidx.compose.foundation.lazy.grid.items
+import androidx.compose.foundation.lazy.items
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.CircularProgressIndicator
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.navigation.NavHostController
+import com.capstone.techwasmark02.data.remote.response.ArticleResultResponse
+import com.capstone.techwasmark02.ui.common.UiState
+import com.capstone.techwasmark02.ui.component.ArticleCardSmall
+import com.capstone.techwasmark02.ui.component.SearchBox
+import com.capstone.techwasmark02.ui.component.SelectableText
+import com.capstone.techwasmark02.ui.componentType.ArticleFilterType
+import com.capstone.techwasmark02.ui.navigation.Screen
+import com.capstone.techwasmark02.ui.theme.TechwasMark02Theme
+
+@Composable
+fun ArticleScreen(
+ viewModel: ArticleScreenViewModel = hiltViewModel(),
+ navController: NavHostController
+) {
+ val articleList by viewModel.articleList.collectAsState()
+
+ ArticleContent(
+ viewModel = viewModel,
+ articleList = articleList,
+ navigateToSingleArticle = { navController.navigate("${Screen.SingleArticle.route}/$it") },
+ )
+}
+
+@Composable
+fun ArticleContent(
+ viewModel: ArticleScreenViewModel,
+ articleList: UiState?,
+ navigateToSingleArticle: (idArticle: Int) -> Unit,
+) {
+
+ var inputValue by remember {
+ mutableStateOf("")
+ }
+
+ val filterTypeList = listOf(
+ ArticleFilterType.General,
+ ArticleFilterType.Battery,
+ ArticleFilterType.Cable,
+ ArticleFilterType.CrtTv,
+ ArticleFilterType.EKettle,
+ ArticleFilterType.Refrigerator,
+ ArticleFilterType.Keyboard,
+ ArticleFilterType.Laptop,
+ ArticleFilterType.LightBulb,
+ ArticleFilterType.Monitor,
+ ArticleFilterType.Mouse,
+ ArticleFilterType.PCB,
+ ArticleFilterType.Printer,
+ ArticleFilterType.RiceCooker,
+ ArticleFilterType.WashingMachine,
+ ArticleFilterType.Phone
+ )
+
+ var selectedFilter by remember {
+ mutableStateOf(filterTypeList.firstOrNull())
+ }
+
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(MaterialTheme.colorScheme.background)
+ ) {
+
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(top = 20.dp, bottom = 80.dp)
+ ) {
+ Column(
+ modifier = Modifier
+ .padding(horizontal = 16.dp)
+ ) {
+ Text(
+ text = "Articles",
+ style = MaterialTheme.typography.headlineLarge,
+ fontWeight = FontWeight.Bold
+ )
+
+ Spacer(modifier = Modifier.height(8.dp))
+
+ Text(
+ text = "Check out these articles!",
+ style = MaterialTheme.typography.bodyLarge.copy(
+ fontWeight = FontWeight.Medium
+ )
+ )
+
+ Spacer(modifier = Modifier.height(16.dp))
+
+ SearchBox(
+ value = inputValue,
+ onValueChange = { newValue ->
+ inputValue = newValue
+ if(inputValue.isEmpty()) {
+ viewModel.getAllFilterArticle(selectedFilter?.id ?: 0)
+ } else {
+ viewModel.getArticleByName(inputValue, selectedFilter ?: ArticleFilterType.General)
+ }
+ },
+ )
+
+ Spacer(modifier = Modifier.height(16.dp))
+ }
+
+ LazyRow(
+ modifier = Modifier.height(48.dp),
+ horizontalArrangement = Arrangement.spacedBy(8.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ contentPadding = PaddingValues(horizontal = 16.dp)
+ ) {
+ items(
+ items = filterTypeList,
+ ) { item ->
+ SelectableText(
+ filterType = item,
+ selected = item == selectedFilter,
+ onClick = {
+ selectedFilter = item
+ viewModel.getAllFilterArticle(item.id)
+ }
+ )
+ }
+ }
+
+ Column(
+ modifier = Modifier
+ .padding(horizontal = 16.dp)
+ ) {
+ Spacer(modifier = Modifier.height(16.dp))
+
+ if (articleList != null) {
+ when (articleList) {
+ is UiState.Loading -> {
+ Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
+ CircularProgressIndicator()
+ }
+ }
+ is UiState.Error -> {
+ articleList.message?.let {
+ Text(text = it)
+ }
+ }
+ is UiState.Success -> {
+ val componentListArticle = articleList.data?.articleList
+ if(!componentListArticle.isNullOrEmpty()) {
+ LazyVerticalGrid(
+ modifier = Modifier
+ .fillMaxWidth()
+ .fillMaxHeight()
+ .padding(bottom = 20.dp),
+ columns = GridCells.Fixed(2),
+ contentPadding = PaddingValues(vertical = 8.dp),
+ verticalArrangement = Arrangement.spacedBy(16.dp),
+ horizontalArrangement = Arrangement.spacedBy(16.dp),
+ ) {
+ items(componentListArticle) { item ->
+ ArticleCardSmall(
+ modifier = Modifier
+ .width(150.dp)
+ .clickable {
+ item?.id?.let { navigateToSingleArticle(it) }
+ },
+ imgUrl = item?.articleImageURL,
+ title = item?.name,
+ description = item?.desc,
+ )
+ }
+ }
+ } else {
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(100.dp),
+ contentAlignment = Alignment.Center
+ ) {
+ Box(
+ modifier = Modifier
+ .border(
+ width = 1.dp,
+ color = Color.Red,
+ shape = RoundedCornerShape(20.dp)
+ )
+ .padding(8.dp)
+
+ ) {
+ Text(
+ text = "There's no related article",
+ style = MaterialTheme.typography.labelSmall,
+ color = Color.Red
+ )
+ }
+ }
+ }
+
+ }
+ }
+ }
+ }
+ }
+ }
+
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ ) {
+ Spacer(modifier = Modifier.weight(1f))
+ }
+}
+
+@Preview(showBackground = true)
+@Composable
+fun ArticleScreenPreview() {
+ TechwasMark02Theme {
+// ArticleContent(
+// articleList = UiState.Loading(),
+// navigateToHome = {},
+// navigateToArticle = {},
+// navigateToForum = {},
+// navigateToProfile = {}
+// )
+ }
+}
diff --git a/app/src/main/java/com/capstone/techwasmark02/ui/screen/article/ArticleScreenViewModel.kt b/app/src/main/java/com/capstone/techwasmark02/ui/screen/article/ArticleScreenViewModel.kt
new file mode 100644
index 0000000..e9ca610
--- /dev/null
+++ b/app/src/main/java/com/capstone/techwasmark02/ui/screen/article/ArticleScreenViewModel.kt
@@ -0,0 +1,90 @@
+package com.capstone.techwasmark02.ui.screen.article
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.capstone.techwasmark02.data.remote.response.ArticleResultResponse
+import com.capstone.techwasmark02.repository.TechwasArticleRepository
+import com.capstone.techwasmark02.ui.common.UiState
+import com.capstone.techwasmark02.ui.componentType.ArticleFilterType
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class ArticleScreenViewModel @Inject constructor(
+ private val articleRepository: TechwasArticleRepository
+): ViewModel() {
+
+ private val _articleList: MutableStateFlow?> = MutableStateFlow(null)
+ val articleList = _articleList.asStateFlow()
+
+ init {
+ viewModelScope.launch {
+ getAllFilterArticle(0)
+ }
+ }
+
+ fun getAllFilterArticle(id: Int) {
+ _articleList.value = UiState.Loading()
+ viewModelScope.launch {
+ val result = if(id == 0) {
+ articleRepository.getAllArticle()
+ } else {
+ articleRepository.getArticleByComponentId(userToken = "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VySUQiOiJ1c2VyQGV4YW1wbGUuY29tIiwiZXhwaXJ5IjoxNjg2MDQ5NDcwLjc1NjgyMTR9.eJfMxHb3UsQu-kyzyzN_3PdV8OvvwTmD8vOyoTRENyQ'", id = id)
+ }
+ when(result) {
+ is UiState.Success -> {
+ _articleList.value = result
+ }
+ is UiState.Error -> {
+ _articleList.value = result
+ }
+ else -> {
+ // do nothing
+ }
+ }
+ }
+ }
+
+ fun getArticleByName(name: String, filterType: ArticleFilterType) {
+ _articleList.value = UiState.Loading()
+ viewModelScope.launch {
+ if (name.isEmpty()) { // input kosong fetch article by filter
+ getAllFilterArticle(filterType.id)
+ } else {
+ val result = if(filterType.id == 0) {
+ articleRepository.getAllArticle()
+ } else {
+ articleRepository.getArticleByComponentId(userToken = "Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJ1c2VySUQiOiJ1c2VyQGV4YW1wbGUuY29tIiwiZXhwaXJ5IjoxNjg2MDQ5NDcwLjc1NjgyMTR9.eJfMxHb3UsQu-kyzyzN_3PdV8OvvwTmD8vOyoTRENyQ'", id = filterType.id)
+ }
+ when(result) {
+ is UiState.Success -> {
+ val filteredArticles = if (filterType.id == 0) { // general
+ result.data?.articleList?.filter {
+ it?.name?.contains(name, ignoreCase = true) == true
+ }
+ } else {
+ result.data?.articleList?.filter {
+ it?.name?.contains(name, ignoreCase = true) == true && it.componentId == filterType.id
+ }
+ }
+ val filteredResult = ArticleResultResponse(
+ articleList = filteredArticles.orEmpty(),
+ error = result.message,
+ message = result.message
+ )
+ _articleList.value = UiState.Success(filteredResult)
+ }
+ is UiState.Error -> {
+ _articleList.value = result
+ }
+ else -> {
+ // do nothing
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/capstone/techwasmark02/ui/screen/camera/CameraScreen.kt b/app/src/main/java/com/capstone/techwasmark02/ui/screen/camera/CameraScreen.kt
new file mode 100644
index 0000000..1be1cba
--- /dev/null
+++ b/app/src/main/java/com/capstone/techwasmark02/ui/screen/camera/CameraScreen.kt
@@ -0,0 +1,195 @@
+package com.capstone.techwasmark02.ui.screen.camera
+
+import android.content.Context
+import android.net.Uri
+import android.util.Log
+import android.widget.Toast
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material3.CircularProgressIndicator
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.navigation.NavHostController
+import com.capstone.techwasmark02.R
+import com.capstone.techwasmark02.data.remote.response.DetectionsResultResponse
+import com.capstone.techwasmark02.ui.common.UiState
+import com.capstone.techwasmark02.ui.component.CameraView
+import com.capstone.techwasmark02.ui.theme.TechwasMark02Theme
+import com.squareup.moshi.Moshi
+import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
+import java.io.File
+import java.util.concurrent.Executors
+
+@Composable
+fun CameraScreen(
+ viewModel: CameraScreenViewModel = hiltViewModel(),
+ navController: NavHostController
+) {
+
+ val photoUri by viewModel.photoUri.collectAsState()
+ val predictImageState by viewModel.predictImageState.collectAsState()
+
+ CameraContent(
+ photoUri = photoUri,
+ predictImageState = predictImageState,
+ updatePhotoUri = { viewModel.updatePhotoUri(it) },
+ predictImage = { viewModel.predictImage(it)},
+ navigateToResult = { uri, result ->
+ navController.navigate("detectionResult/$uri/$result")
+ }
+ ) { viewModel.onSuccessPredictImage() }
+}
+
+@Composable
+fun CameraContent(
+ photoUri: Uri?,
+ predictImageState: UiState?,
+ updatePhotoUri: (Uri) -> Unit,
+ predictImage: (Context) -> Unit,
+ navigateToResult: (uri: String, result: String) -> Unit,
+ onSuccessPredictImage:() -> Unit
+) {
+
+ val context = LocalContext.current
+ val outputDirectory = getOutputDirectory(context)
+ val cameraExecutor = Executors.newSingleThreadExecutor()
+
+ var imageIsTaken by remember {
+ mutableStateOf(false)
+ }
+
+ val moshi = Moshi.Builder()
+ .add(KotlinJsonAdapterFactory())
+ .build()
+ val adapter = moshi.adapter(DetectionsResultResponse::class.java)
+
+ LaunchedEffect(key1 = imageIsTaken) {
+ if (imageIsTaken) {
+ predictImage(context)
+ }
+ }
+
+ LaunchedEffect(key1 = predictImageState) {
+ if (predictImageState != null) {
+ when (predictImageState) {
+ is UiState.Success -> {
+ val predictResult = Uri.encode(adapter.toJson(predictImageState.data))
+// Toast.makeText(context,"Result: $predictResult", Toast.LENGTH_SHORT).show()
+// val predictResult = predictImageState.data?.predictions?.let {
+// getAndSortResult(
+// it
+// )
+// }
+
+ val stringUri = Uri.encode(photoUri.toString())
+ onSuccessPredictImage()
+ if (predictResult != null) {
+ navigateToResult(stringUri, predictResult)
+ }
+// navigateToResult(stringUri, "alo")
+ }
+ is UiState.Error -> {
+ val predictResult = predictImageState.message
+ Toast.makeText(context,"$predictResult", Toast.LENGTH_SHORT).show()
+ imageIsTaken = false
+ }
+ is UiState.Loading -> {
+ Toast.makeText(context,"Your e-waste is being predicted", Toast.LENGTH_SHORT).show()
+ }
+ }
+ }
+ }
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ ) {
+ CameraView(
+ outputDirectory = outputDirectory,
+ executor = cameraExecutor,
+ onImageCaptured = { uri ->
+ updatePhotoUri(uri)
+ imageIsTaken = true
+ },
+ onError = { Log.e("zhahrany", "View error:", it)}
+ )
+
+ if (predictImageState != null) {
+ when (predictImageState) {
+ is UiState.Success, is UiState.Error -> {
+ // do nothing
+ }
+ is UiState.Loading -> {
+ Box(
+ modifier = Modifier
+ .fillMaxSize(),
+ contentAlignment = Alignment.Center
+ ) {
+
+ CircularProgressIndicator(
+ color = Color.White
+ )
+ }
+ }
+ }
+ }
+ }
+
+}
+
+private fun getOutputDirectory(context: Context): File {
+ val cacheDir = context.externalCacheDirs.firstOrNull()?.let {
+ File(it, context.resources.getString(R.string.app_name)).apply { mkdirs() }
+ }
+
+ return if (cacheDir != null && cacheDir.exists()) cacheDir else context.filesDir
+}
+
+//private fun getAndSortResult(detectionResult: Predictions): String {
+// val resultComponentList = emptyList().toMutableList()
+// for (property in Predictions::class.memberProperties) {
+// if (property.get(detectionResult) != null) {
+// val resultComponentItem = ResultComponentType(
+// name = property.name,
+// percentage = property.get(detectionResult).toString().toDouble()
+// )
+// resultComponentList.add(resultComponentItem)
+// }
+// }
+//
+// val sortedResultComponentList = resultComponentList.sortedWith(compareBy({it.percentage})).reversed()
+//
+// val stringResultList = emptyList().toMutableList()
+//
+// sortedResultComponentList.forEach { componentItem ->
+// val stringResultItem = "${componentItem.name}: ${componentItem.percentage}"
+// stringResultList.add(stringResultItem)
+// }
+//
+// return stringResultList.joinToString(separator = ",")
+//
+//}
+
+@Preview(showBackground = true)
+@Composable
+fun CameraScreenPreview() {
+ TechwasMark02Theme() {
+ CameraContent(
+ photoUri = null,
+ updatePhotoUri = {},
+ navigateToResult = { uri, result -> },
+ predictImage = {},
+ predictImageState = null
+ ) {}
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/capstone/techwasmark02/ui/screen/camera/CameraScreenViewModel.kt b/app/src/main/java/com/capstone/techwasmark02/ui/screen/camera/CameraScreenViewModel.kt
new file mode 100644
index 0000000..814b773
--- /dev/null
+++ b/app/src/main/java/com/capstone/techwasmark02/ui/screen/camera/CameraScreenViewModel.kt
@@ -0,0 +1,72 @@
+package com.capstone.techwasmark02.ui.screen.camera
+
+import android.content.Context
+import android.net.Uri
+import android.webkit.MimeTypeMap
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.capstone.techwasmark02.data.remote.response.DetectionsResultResponse
+import com.capstone.techwasmark02.repository.TechwasPredictionApiRepository
+import com.capstone.techwasmark02.ui.common.UiState
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.launch
+import java.io.File
+import java.io.FileOutputStream
+import javax.inject.Inject
+
+@HiltViewModel
+class CameraScreenViewModel @Inject constructor(
+ private val predictionApiRepository: TechwasPredictionApiRepository
+): ViewModel() {
+
+ private val _photoUri: MutableStateFlow = MutableStateFlow(null)
+ val photoUri = _photoUri.asStateFlow()
+
+ private val _predictImageState: MutableStateFlow?> = MutableStateFlow(null)
+ val predictImageState = _predictImageState.asStateFlow()
+
+ fun updatePhotoUri(newUri: Uri) {
+ _photoUri.value = newUri
+ }
+
+ fun predictImage(context: Context) {
+ _predictImageState.value = UiState.Loading()
+
+ val imageFileToUpload = _photoUri.value?.let { convertUriToFile(context = context, uri = it) }
+ viewModelScope.launch {
+ _predictImageState.value = imageFileToUpload?.let {
+ predictionApiRepository.predictWaste(it)
+ }
+ }
+ }
+
+ fun onSuccessPredictImage() {
+ _predictImageState.value = null
+ }
+
+}
+
+private fun convertUriToFile(context: Context, uri: Uri): File? {
+ val inputStream = context.contentResolver.openInputStream(uri)
+ inputStream?.let {
+ val file = createTempFile(context, getFileExtension(uri))
+ val outputStream = FileOutputStream(file)
+ inputStream.copyTo(outputStream)
+ outputStream.close()
+ inputStream.close()
+ return file
+ }
+ return null
+}
+
+private fun createTempFile(context: Context, fileExtension: String): File {
+ val directory = context.cacheDir
+ return File.createTempFile("temp", ".$fileExtension", directory)
+}
+
+private fun getFileExtension(uri: Uri): String {
+ val extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(uri.toString())
+ return extension ?: "jpg" // Default to "jpg" if extension is null
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/capstone/techwasmark02/ui/screen/catalog/CatalogScreen.kt b/app/src/main/java/com/capstone/techwasmark02/ui/screen/catalog/CatalogScreen.kt
new file mode 100644
index 0000000..b9be8ee
--- /dev/null
+++ b/app/src/main/java/com/capstone/techwasmark02/ui/screen/catalog/CatalogScreen.kt
@@ -0,0 +1,192 @@
+package com.capstone.techwasmark02.ui.screen.catalog
+
+import android.net.Uri
+import androidx.activity.compose.BackHandler
+import androidx.compose.foundation.background
+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.PaddingValues
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.material3.CircularProgressIndicator
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.navigation.NavHostController
+import com.capstone.techwasmark02.data.remote.response.Component
+import com.capstone.techwasmark02.data.remote.response.ComponentsResponse
+import com.capstone.techwasmark02.ui.common.UiState
+import com.capstone.techwasmark02.ui.component.CatalogCard
+import com.capstone.techwasmark02.ui.component.InverseTopBar
+import com.capstone.techwasmark02.ui.component.SearchBox
+import com.capstone.techwasmark02.ui.navigation.Screen
+import com.capstone.techwasmark02.ui.theme.TechwasMark02Theme
+import com.squareup.moshi.Moshi
+import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
+
+@Composable
+fun CatalogScreen(
+ viewModel: CatalogScreenViewModel = hiltViewModel(),
+ navController: NavHostController
+) {
+ val componentsState by viewModel.componentState.collectAsState()
+ val searchBoxValue by viewModel.searchBoxValue.collectAsState()
+
+ CatalogContent(
+ componentsState = componentsState,
+ navigateToSingleComponent = { navController.navigate("${Screen.SingleCatalog.route}/$it") },
+ searchBoxValue = searchBoxValue,
+ onSearchBoxValueChange = { viewModel.updateSearchBoxValue(it) },
+ navigateBackToMain = { navController.navigate("${Screen.Main.route}/0") }
+ )
+}
+
+@Composable
+fun CatalogContent(
+ componentsState: UiState?,
+ navigateToSingleComponent: (component: String) -> Unit,
+ searchBoxValue: String,
+ onSearchBoxValueChange: (String) -> Unit,
+ navigateBackToMain: () -> Unit
+) {
+
+ BackHandler(true) {
+ navigateBackToMain()
+ }
+
+ val moshi = Moshi.Builder()
+ .add(KotlinJsonAdapterFactory())
+ .build()
+ val adapter = moshi.adapter(Component::class.java)
+
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ ) {
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(horizontal = 16.dp)
+ .padding(top = 60.dp)
+ ) {
+ Text(
+ text = "Catalog",
+ style = MaterialTheme.typography.headlineSmall
+ )
+ Text(
+ text = "Find your e-waste component here",
+ style = MaterialTheme.typography.bodyMedium
+ )
+
+ Spacer(modifier = Modifier.height(10.dp))
+
+ SearchBox(onValueChange = onSearchBoxValueChange, value = searchBoxValue)
+
+ Spacer(modifier = Modifier.height(16.dp))
+
+ if (componentsState != null) {
+ when(componentsState) {
+ is UiState.Success -> {
+ val components = componentsState.data?.components
+
+ var filteredComponents by remember {
+ mutableStateOf(components)
+ }
+
+ LaunchedEffect(key1 = searchBoxValue) {
+ if (components != null) {
+ filteredComponents = searchComponent(
+ componentList = components,
+ searchBoxValue = searchBoxValue
+ )
+ }
+ }
+
+ LazyColumn(
+ modifier = Modifier
+ .fillMaxSize(),
+ contentPadding = PaddingValues(bottom = 20.dp),
+ verticalArrangement = Arrangement.spacedBy(12.dp)
+ ) {
+ filteredComponents?.size?.let {
+ items(
+ count = it,
+ ) {index ->
+ CatalogCard(
+ component = filteredComponents!![index],
+ modifier = Modifier
+ .fillMaxWidth()
+ .clickable {
+ val componentJson =
+ Uri.encode(adapter.toJson(filteredComponents!![index]))
+ navigateToSingleComponent(componentJson)
+ },
+ )
+ }
+ }
+ }
+ }
+ is UiState.Error -> {
+
+ }
+ is UiState.Loading -> {
+ Box(
+ modifier = Modifier
+ .fillMaxSize(),
+ contentAlignment = Alignment.Center
+ ) {
+ CircularProgressIndicator()
+ }
+ }
+ }
+ }
+ }
+
+ InverseTopBar(
+ onClickNavigationIcon = navigateBackToMain,
+ pageTitle = "Catalog",
+ modifier = Modifier
+ .background(MaterialTheme.colorScheme.background)
+ )
+ }
+}
+
+private fun searchComponent(componentList: List, searchBoxValue: String) : List {
+ if (searchBoxValue == "") {
+ return componentList
+ }
+ return componentList.filter { component ->
+ component.name.contains(searchBoxValue, ignoreCase = true)
+ }
+}
+
+@Preview (showBackground = true)
+@Composable
+fun CatalogScreenPreview() {
+ TechwasMark02Theme {
+ CatalogContent(
+ componentsState = UiState.Loading(),
+ navigateToSingleComponent = {},
+ searchBoxValue = "",
+ onSearchBoxValueChange = {},
+ navigateBackToMain = {}
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/capstone/techwasmark02/ui/screen/catalog/CatalogScreenViewModel.kt b/app/src/main/java/com/capstone/techwasmark02/ui/screen/catalog/CatalogScreenViewModel.kt
new file mode 100644
index 0000000..3175b1c
--- /dev/null
+++ b/app/src/main/java/com/capstone/techwasmark02/ui/screen/catalog/CatalogScreenViewModel.kt
@@ -0,0 +1,37 @@
+package com.capstone.techwasmark02.ui.screen.catalog
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.capstone.techwasmark02.data.remote.response.ComponentsResponse
+import com.capstone.techwasmark02.repository.TechwasComponentApiRepository
+import com.capstone.techwasmark02.ui.common.UiState
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class CatalogScreenViewModel @Inject constructor(
+ private val componentApiRepository: TechwasComponentApiRepository
+): ViewModel() {
+
+ private val _componentsState: MutableStateFlow?> = MutableStateFlow(null)
+ val componentState = _componentsState.asStateFlow()
+
+ private val _searchBoxValue: MutableStateFlow = MutableStateFlow("")
+ val searchBoxValue = _searchBoxValue.asStateFlow()
+
+ fun updateSearchBoxValue(newValue: String) {
+ _searchBoxValue.value = newValue
+ }
+
+ init {
+ _componentsState.value = UiState.Loading()
+
+ viewModelScope.launch {
+ _componentsState.value = componentApiRepository.fetchComponents()
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/capstone/techwasmark02/ui/screen/catalogSingleComponent/CatalogSingleComponentScreen.kt b/app/src/main/java/com/capstone/techwasmark02/ui/screen/catalogSingleComponent/CatalogSingleComponentScreen.kt
new file mode 100644
index 0000000..103ea24
--- /dev/null
+++ b/app/src/main/java/com/capstone/techwasmark02/ui/screen/catalogSingleComponent/CatalogSingleComponentScreen.kt
@@ -0,0 +1,462 @@
+package com.capstone.techwasmark02.ui.screen.catalogSingleComponent
+
+import androidx.activity.compose.BackHandler
+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.PaddingValues
+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.offset
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.lazy.LazyRow
+import androidx.compose.foundation.lazy.grid.GridCells
+import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.ExperimentalMaterialApi
+import androidx.compose.material.ModalBottomSheetLayout
+import androidx.compose.material.ModalBottomSheetValue
+import androidx.compose.material.rememberModalBottomSheetState
+import androidx.compose.material3.CircularProgressIndicator
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+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.draw.shadow
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.layout.onGloballyPositioned
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.navigation.NavHostController
+import coil.compose.AsyncImage
+import com.capstone.techwasmark02.data.remote.response.ArticleResultResponse
+import com.capstone.techwasmark02.data.remote.response.Component
+import com.capstone.techwasmark02.data.remote.response.SmallPart
+import com.capstone.techwasmark02.data.remote.response.UsableComponentsResponse
+import com.capstone.techwasmark02.ui.common.UiState
+import com.capstone.techwasmark02.ui.component.ArticleCardBig
+import com.capstone.techwasmark02.ui.component.DefaultTopBar
+import com.capstone.techwasmark02.ui.component.UsableComponentBottomSheet
+import com.capstone.techwasmark02.ui.component.UsableComponentItem
+import com.capstone.techwasmark02.ui.navigation.Screen
+import com.capstone.techwasmark02.ui.theme.TechwasMark02Theme
+import com.squareup.moshi.Moshi
+import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
+import kotlinx.coroutines.launch
+
+@Composable
+fun CatalogSingleComponentScreen(
+ viewModel: CatalogSingleComponentScreenViewModel = hiltViewModel(),
+ componentJson: String,
+ navController: NavHostController
+) {
+
+ val moshi = Moshi.Builder()
+ .add(KotlinJsonAdapterFactory())
+ .build()
+ val adapter = moshi.adapter(Component::class.java)
+ val component = adapter.fromJson(componentJson)
+
+ val usableComponentsState by viewModel.usableComponentsState.collectAsState()
+ val usableComponentList by viewModel.usableComponentList.collectAsState()
+ val relatedArticleListState by viewModel.relatedArticleListState.collectAsState()
+
+ if (component != null) {
+ CatalogSingleComponentContent(
+ component = component,
+ usableComponentsState = usableComponentsState,
+ updateUsableComponentsState = { viewModel.updateUsableComponentsState(it) },
+ usableComponentList = usableComponentList,
+ updateUsableComponentsList = { viewModel.updateUsableComponentList(it) },
+ relatedArticleListState = relatedArticleListState,
+ updateRelatedArticleListState = { viewModel.updateRelatedArticleListState(it) },
+ navigateToSingleArticle = { navController.navigate("${Screen.SingleArticle.route}/$it") },
+ navigateBackToCatalog = { navController.popBackStack() }
+ )
+ }
+}
+
+@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class)
+@Composable
+fun CatalogSingleComponentContent(
+ component: Component,
+ usableComponentsState: UiState?,
+ updateUsableComponentsState: (Int) -> Unit,
+ usableComponentList: List,
+ updateUsableComponentsList: (List) -> Unit,
+ relatedArticleListState: UiState?,
+ updateRelatedArticleListState: (Int) -> Unit,
+ navigateToSingleArticle: (idArticle: Int) -> Unit,
+ navigateBackToCatalog: () -> Unit
+ ) {
+
+ val modalSheetState = rememberModalBottomSheetState(
+ initialValue = ModalBottomSheetValue.Hidden,
+ confirmValueChange = { it != ModalBottomSheetValue.HalfExpanded},
+ skipHalfExpanded = false
+ )
+
+ val coroutineScope = rememberCoroutineScope()
+
+ var currentlyClickedUsableComponent by remember {
+ mutableStateOf(0)
+ }
+
+ var isBottomSheetOpen by remember {
+ mutableStateOf(false)
+ }
+
+ LaunchedEffect(Unit) {
+ updateUsableComponentsState(component.id)
+ updateRelatedArticleListState(component.id)
+ }
+
+ BackHandler(isBottomSheetOpen) {
+ if (isBottomSheetOpen) {
+ coroutineScope.launch {
+ modalSheetState.hide()
+ isBottomSheetOpen = false
+ }
+ }
+ }
+
+ ModalBottomSheetLayout(
+ sheetState = modalSheetState,
+ sheetShape = RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp),
+ sheetContent = {
+ if (usableComponentList.isNotEmpty()) {
+ UsableComponentBottomSheet(
+ smallPart = usableComponentList[currentlyClickedUsableComponent]
+ )
+ }
+ }
+ ) {
+ Scaffold(
+ topBar = {
+ DefaultTopBar(
+ pageTitle = "Result",
+ onClickNavigationIcon = { navigateBackToCatalog() }
+ )
+ }
+ ) { innerPadding ->
+ val scrollState = rememberScrollState()
+
+ Column(
+ modifier = Modifier
+ .verticalScroll(scrollState)
+ .padding(innerPadding)
+ .padding(bottom = 20.dp)
+ ) {
+
+ var componentWidth by remember {
+ mutableStateOf(0.dp)
+ }
+ val density = LocalDensity.current
+
+
+ Box(
+ modifier = Modifier
+ ) {
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(440.dp)
+ .clip(
+ RoundedCornerShape(
+ bottomStart = 20.dp,
+ bottomEnd = 20.dp
+ )
+ )
+ .background(Color(0xff656565))
+ .onGloballyPositioned {
+ componentWidth = with(density) {
+ it.size.width.toDp()
+ }
+ }
+ .offset(
+ x = -(componentWidth * 15 / 88)
+ ),
+ ) {
+// AsyncImage(
+// model = component.imageExample,
+// contentDescription = null,
+// contentScale = ContentScale.Crop,
+// alpha = 0.8f,
+// modifier = Modifier
+// .fillMaxSize()
+// .blur(16.dp)
+// )
+ AsyncImage(
+ model = component.imageExample,
+ contentDescription = null,
+ contentScale = ContentScale.Fit,
+ modifier = Modifier
+ .fillMaxSize()
+ )
+ }
+
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(top = 402.dp),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Box(
+ modifier = Modifier
+ .padding(horizontal = 20.dp)
+ .fillMaxWidth()
+ .height(64.dp)
+ .shadow(
+ elevation = 8.dp,
+ shape = RoundedCornerShape(20.dp)
+ )
+ .background(MaterialTheme.colorScheme.tertiary)
+ .padding(start = 20.dp),
+ contentAlignment = Alignment.CenterStart
+ ) {
+ Text(
+ text = component.name,
+ style = MaterialTheme.typography.headlineSmall
+ )
+ }
+
+// if (detectionsResultObj != null) {
+// DetectionsResultBox(
+// modifier = Modifier
+// .padding(horizontal = 20.dp),
+// predictionList = detectionsResultObj.predictions
+// )
+// }
+
+ }
+ }
+
+ Column(
+ modifier = Modifier
+ .padding(horizontal = 20.dp)
+ .padding(top = 20.dp)
+ ) {
+
+ Text(
+ text = component.desc,
+ style = MaterialTheme.typography.bodyMedium
+ )
+
+ Spacer(modifier = Modifier.height(20.dp))
+
+ Text(
+ text = "Usable Components",
+ style = MaterialTheme.typography.labelLarge,
+ color = MaterialTheme.colorScheme.onBackground
+ )
+
+ Spacer(modifier = Modifier.height(8.dp))
+
+ if (usableComponentsState != null) {
+ when(usableComponentsState) {
+ is UiState.Success -> {
+ usableComponentsState.data?.smallParts?.size?.let {
+ LazyVerticalGrid(
+ columns = GridCells.Fixed(
+ 2
+ ),
+ horizontalArrangement = Arrangement.spacedBy(16.dp),
+ verticalArrangement = Arrangement.spacedBy(12.dp),
+ modifier = Modifier
+ .height((it * 72).dp),
+ ) {
+ items(usableComponentsState.data.smallParts.size) { index ->
+ currentlyClickedUsableComponent = index
+ updateUsableComponentsList(usableComponentsState.data.smallParts)
+
+ UsableComponentItem(
+ onClick = {
+ isBottomSheetOpen = true
+ coroutineScope.launch {
+ modalSheetState.show()
+ }
+ },
+ usableComponent = usableComponentsState.data.smallParts[index]
+ )
+ }
+
+ }
+ }
+ }
+ is UiState.Error -> {
+ Box(
+ modifier = Modifier
+ .height(100.dp)
+ .fillMaxWidth(),
+ contentAlignment = Alignment.Center
+ ) {
+ Text(
+ text = "Fail to fetch usable component",
+ style = MaterialTheme.typography.labelSmall,
+ color = Color.Red
+ )
+ }
+ }
+ is UiState.Loading -> {
+ Box(
+ modifier = Modifier
+ .height(100.dp)
+ .fillMaxWidth(),
+ contentAlignment = Alignment.Center
+ ) {
+ CircularProgressIndicator()
+ }
+ }
+ }
+ }
+
+ Spacer(modifier = Modifier.height(20.dp))
+
+ Text(
+ text = "Related Articles",
+ style = MaterialTheme.typography.labelLarge,
+ color = MaterialTheme.colorScheme.onBackground
+ )
+
+ Spacer(modifier = Modifier.height(8.dp))
+ }
+
+
+ if (relatedArticleListState != null) {
+ when(relatedArticleListState) {
+ is UiState.Loading -> {
+ Box(
+ modifier = Modifier
+ .height(100.dp)
+ .fillMaxWidth(),
+ contentAlignment = Alignment.Center
+ ) {
+ CircularProgressIndicator()
+ }
+ }
+ is UiState.Error -> {
+ Box(
+ modifier = Modifier
+ .height(100.dp)
+ .fillMaxWidth(),
+ contentAlignment = Alignment.Center
+ ) {
+ Text(
+ text = "Fail to fetch article",
+ style = MaterialTheme.typography.labelSmall,
+ color = Color.Red
+ )
+ }
+ }
+ is UiState.Success -> {
+ val articleList = relatedArticleListState.data?.articleList
+
+ if (articleList?.isNotEmpty() == true) {
+ LazyRow(
+ contentPadding = PaddingValues(horizontal = 16.dp),
+ horizontalArrangement = Arrangement.spacedBy(16.dp),
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ relatedArticleListState.data.articleList.let {
+ if (it.isNotEmpty()) {
+ items(
+ count = it.size,
+ ) { index ->
+ articleList.get(index)?.let { it1 ->
+ ArticleCardBig(
+ modifier = Modifier
+ .width(150.dp)
+ .clickable {
+ it1.id?.let { it2 ->
+ navigateToSingleArticle(
+ it2
+ )
+ }
+ },
+ article = it1
+ )
+ }
+ }
+ }
+ }
+ }
+ } else {
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(100.dp),
+ contentAlignment = Alignment.Center
+ ) {
+ Box(
+ modifier = Modifier
+ .border(
+ width = 1.dp,
+ color = Color.Red,
+ shape = RoundedCornerShape(20.dp)
+ )
+ .padding(8.dp)
+
+ ) {
+ Text(
+ text = "There's no related article",
+ style = MaterialTheme.typography.labelSmall,
+ color = Color.Red
+ )
+ }
+ }
+ }
+ }
+ }
+ }
+
+ Spacer(modifier = Modifier.height(28.dp))
+
+ }
+ }
+ }
+}
+
+@Preview(showBackground = true)
+@Composable
+fun CatalogSingleComponentScreenPreview() {
+ TechwasMark02Theme {
+ CatalogSingleComponentContent(
+ component = Component(
+ desc = "kdfiasu kadfiau kaud fiajdfkua dfja ufiauj dfkaud ifaju",
+ id = 2,
+ imageExample = "",
+ name = "Laptop"
+ ),
+ usableComponentsState = UiState.Loading(),
+ updateUsableComponentsState = {},
+ usableComponentList = emptyList(),
+ updateUsableComponentsList = {},
+ relatedArticleListState = UiState.Loading(),
+ updateRelatedArticleListState = {},
+ navigateToSingleArticle = {},
+ navigateBackToCatalog = {}
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/capstone/techwasmark02/ui/screen/catalogSingleComponent/CatalogSingleComponentScreenViewModel.kt b/app/src/main/java/com/capstone/techwasmark02/ui/screen/catalogSingleComponent/CatalogSingleComponentScreenViewModel.kt
new file mode 100644
index 0000000..1050175
--- /dev/null
+++ b/app/src/main/java/com/capstone/techwasmark02/ui/screen/catalogSingleComponent/CatalogSingleComponentScreenViewModel.kt
@@ -0,0 +1,50 @@
+package com.capstone.techwasmark02.ui.screen.catalogSingleComponent
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.capstone.techwasmark02.data.remote.response.ArticleResultResponse
+import com.capstone.techwasmark02.data.remote.response.SmallPart
+import com.capstone.techwasmark02.data.remote.response.UsableComponentsResponse
+import com.capstone.techwasmark02.repository.TechwasArticleRepository
+import com.capstone.techwasmark02.repository.TechwasComponentApiRepository
+import com.capstone.techwasmark02.ui.common.UiState
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class CatalogSingleComponentScreenViewModel @Inject constructor(
+ private val componentApiRepository: TechwasComponentApiRepository,
+ private val articleRepository: TechwasArticleRepository
+) : ViewModel() {
+
+ private val _usableComponentsState: MutableStateFlow?> = MutableStateFlow(null)
+ val usableComponentsState = _usableComponentsState.asStateFlow()
+
+ private val _usableComponentList: MutableStateFlow> = MutableStateFlow(emptyList())
+ val usableComponentList = _usableComponentList.asStateFlow()
+
+ private val _relatedArticleListState: MutableStateFlow?> = MutableStateFlow(null)
+ val relatedArticleListState = _relatedArticleListState.asStateFlow()
+
+ fun updateUsableComponentsState(compId: Int) {
+ _usableComponentsState.value = UiState.Loading()
+ viewModelScope.launch {
+ _usableComponentsState.value = componentApiRepository.fetchUsableComponents(compId)
+ }
+ }
+
+ fun updateUsableComponentList(newList: List) {
+ _usableComponentList.value = newList
+ }
+
+ fun updateRelatedArticleListState(compId: Int) {
+ _relatedArticleListState.value = UiState.Loading()
+ viewModelScope.launch {
+ _relatedArticleListState.value = articleRepository.getArticleByComponentId(id = compId)
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/capstone/techwasmark02/ui/screen/detectionResult/DetectionResultScreen.kt b/app/src/main/java/com/capstone/techwasmark02/ui/screen/detectionResult/DetectionResultScreen.kt
new file mode 100644
index 0000000..84248d3
--- /dev/null
+++ b/app/src/main/java/com/capstone/techwasmark02/ui/screen/detectionResult/DetectionResultScreen.kt
@@ -0,0 +1,606 @@
+package com.capstone.techwasmark02.ui.screen.detectionResult
+
+import android.net.Uri
+import androidx.activity.compose.BackHandler
+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.PaddingValues
+import androidx.compose.foundation.layout.Row
+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.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.lazy.LazyRow
+import androidx.compose.foundation.lazy.grid.GridCells
+import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.ExperimentalMaterialApi
+import androidx.compose.material.ModalBottomSheetLayout
+import androidx.compose.material.ModalBottomSheetValue
+import androidx.compose.material.rememberModalBottomSheetState
+import androidx.compose.material3.Button
+import androidx.compose.material3.ButtonDefaults
+import androidx.compose.material3.CircularProgressIndicator
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.blur
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.draw.shadow
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.navigation.NavHostController
+import coil.compose.AsyncImage
+import com.capstone.techwasmark02.R
+import com.capstone.techwasmark02.data.remote.response.ArticleResultResponse
+import com.capstone.techwasmark02.data.remote.response.DetectionsResultResponse
+import com.capstone.techwasmark02.data.remote.response.Prediction
+import com.capstone.techwasmark02.data.remote.response.SmallPart
+import com.capstone.techwasmark02.data.remote.response.UsableComponentsResponse
+import com.capstone.techwasmark02.ui.common.UiState
+import com.capstone.techwasmark02.ui.component.ArticleCardBig
+import com.capstone.techwasmark02.ui.component.DefaultTopBar
+import com.capstone.techwasmark02.ui.component.DetectionsResultBox
+import com.capstone.techwasmark02.ui.component.UsableComponentBottomSheet
+import com.capstone.techwasmark02.ui.component.UsableComponentItem
+import com.capstone.techwasmark02.ui.navigation.Screen
+import com.capstone.techwasmark02.ui.theme.TechwasMark02Theme
+import com.squareup.moshi.Moshi
+import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
+import kotlinx.coroutines.launch
+
+
+@Composable
+fun DetectionResultScreen(
+ stringUri: String,
+ detectionResult: String,
+ viewModel: DetectionResultScreenViewModel = hiltViewModel(),
+ navController: NavHostController
+) {
+
+ val selectedPrediction by viewModel.selectedPrediction.collectAsState()
+ val usableComponentsListState by viewModel.usableComponentsListState.collectAsState()
+ val currentlySelectedUsableComponentList by viewModel.currentlySelectedUsableComponentList.collectAsState()
+ val relatedArticleListState by viewModel.relatedArticleListState.collectAsState()
+
+ DetectionResultContent(
+ stringUri = stringUri,
+ detectionResult = detectionResult,
+ selectedPrediction = selectedPrediction,
+ updateSelectedPrediction = { viewModel.updateSelectedPrediction(it) },
+ usableComponentsListState = usableComponentsListState,
+ fetchAllUsableComponents = { viewModel.fetchAllUsableComponents(it) },
+ currentlySelectedUsableComponentList = currentlySelectedUsableComponentList,
+ updateCurrentlySelectedUsableComponentList = { viewModel.updateCurrentlySelectedUsableComponentList(it) },
+ relatedArticleListState = relatedArticleListState,
+ navigateToSingleArticle = { navController.navigate("${Screen.SingleArticle.route}/$it") },
+ navigateToMain = { navController.navigate("${Screen.Main.route}/0") },
+ navigateToForum = { navController.navigate("${Screen.Main.route}/1")},
+ navigateToMaps = { navController.navigate(Screen.Maps.route) }
+ )
+}
+
+@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class)
+@Composable
+fun DetectionResultContent(
+ stringUri: String,
+ detectionResult: String,
+ selectedPrediction: Int,
+ updateSelectedPrediction: (Int) -> Unit,
+ usableComponentsListState: List>?,
+ fetchAllUsableComponents: (List) -> Unit,
+ currentlySelectedUsableComponentList: List,
+ updateCurrentlySelectedUsableComponentList: (List) -> Unit,
+ relatedArticleListState: List>?,
+ navigateToSingleArticle: (idArticle: Int) -> Unit,
+ navigateToMain: () -> Unit,
+ navigateToForum: () -> Unit,
+ navigateToMaps: () -> Unit,
+) {
+ val coroutineScope = rememberCoroutineScope()
+ val modalSheetState = rememberModalBottomSheetState(
+ initialValue = ModalBottomSheetValue.Hidden,
+ confirmValueChange = { it != ModalBottomSheetValue.HalfExpanded},
+ skipHalfExpanded = false
+ )
+
+ val photoUri = Uri.parse(stringUri)
+
+ val moshi = Moshi.Builder()
+ .add(KotlinJsonAdapterFactory())
+ .build()
+ val adapter = moshi.adapter(DetectionsResultResponse::class.java)
+ val detectionsResultObj = adapter.fromJson(detectionResult)
+
+ LaunchedEffect(Unit) {
+ val componentIdList = detectionsResultObj?.predictions
+
+ if (componentIdList != null) {
+ fetchAllUsableComponents(componentIdList)
+ }
+ }
+
+ var currentlyClickedUsableComponent by remember {
+ mutableStateOf(0)
+ }
+
+ var isBottomSheetOpen by remember {
+ mutableStateOf(false)
+ }
+
+ BackHandler(
+ enabled = true
+ ) {
+ if (isBottomSheetOpen) {
+ coroutineScope.launch {
+ modalSheetState.hide()
+ isBottomSheetOpen = false
+ }
+ } else {
+ navigateToMain()
+ }
+ }
+
+ ModalBottomSheetLayout(
+ sheetState = modalSheetState,
+ sheetShape = RoundedCornerShape(topStart = 20.dp, topEnd = 20.dp),
+ sheetContent = {
+ if (currentlySelectedUsableComponentList.isNotEmpty()) {
+ val currentUsableComponents = currentlySelectedUsableComponentList[currentlyClickedUsableComponent]
+ UsableComponentBottomSheet(
+ smallPart = currentUsableComponents
+ )
+ }
+ }
+ ) {
+
+ Scaffold(
+ topBar = {
+ DefaultTopBar(
+ pageTitle = "Result",
+ onClickNavigationIcon = {
+ navigateToMain()
+ }
+ )
+ }
+ ) { innerPadding ->
+ val scrollState = rememberScrollState()
+
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ ) {
+ Column(
+ modifier = Modifier
+ .verticalScroll(scrollState)
+ .padding(innerPadding)
+ .padding(bottom = 20.dp)
+ ) {
+ Box(
+ modifier = Modifier
+ ) {
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(500.dp)
+ .clip(
+ RoundedCornerShape(
+ bottomStart = 20.dp,
+ bottomEnd = 20.dp
+ )
+ )
+ .background(MaterialTheme.colorScheme.primary),
+ ) {
+ AsyncImage(
+ model = photoUri,
+ contentDescription = null,
+ contentScale = ContentScale.Crop,
+ alpha = 0.8f,
+ modifier = Modifier
+ .fillMaxSize()
+ .blur(16.dp)
+ )
+ AsyncImage(
+ model = photoUri,
+ contentDescription = null,
+ contentScale = ContentScale.Fit,
+ modifier = Modifier
+ .fillMaxSize()
+ )
+ }
+
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(top = 402.dp),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Text(
+ text = "Detected as",
+ style = MaterialTheme.typography.labelLarge,
+ color = MaterialTheme.colorScheme.onPrimary
+ )
+
+ if (detectionsResultObj != null) {
+ DetectionsResultBox(
+ modifier = Modifier
+ .padding(horizontal = 20.dp),
+ predictionList = detectionsResultObj.predictions,
+ updateSelectedPrediction = updateSelectedPrediction
+ )
+ }
+
+ }
+ }
+
+ Column(
+ modifier = Modifier
+ .padding(horizontal = 20.dp)
+ .padding(top = 28.dp)
+ ) {
+ Text(
+ text = "Usable Components",
+ style = MaterialTheme.typography.labelLarge,
+ color = MaterialTheme.colorScheme.onBackground
+ )
+
+ Spacer(modifier = Modifier.height(8.dp))
+
+ if (usableComponentsListState != null) {
+ val currentlySelectedPrediction = usableComponentsListState[selectedPrediction]
+ when(currentlySelectedPrediction) {
+ is UiState.Loading -> {
+ Box(
+ modifier = Modifier
+ .height(100.dp)
+ .fillMaxWidth(),
+ contentAlignment = Alignment.Center
+ ) {
+ CircularProgressIndicator()
+ }
+ }
+ is UiState.Success -> {
+ val usableComponentList = currentlySelectedPrediction.data?.smallParts
+
+ usableComponentList?.size?.let {
+ LazyVerticalGrid(
+ columns = GridCells.Fixed(
+ 2
+ ),
+ horizontalArrangement = Arrangement.spacedBy(16.dp),
+ verticalArrangement = Arrangement.spacedBy(12.dp),
+ modifier = Modifier.height((it * 72).dp)
+ ) {
+ items(usableComponentList.size) {index ->
+ currentlyClickedUsableComponent = index
+ updateCurrentlySelectedUsableComponentList(usableComponentList)
+
+ UsableComponentItem(
+ onClick = {
+ isBottomSheetOpen = true
+ coroutineScope.launch {
+ modalSheetState.show()
+ }
+ },
+ usableComponent = usableComponentList[index]
+ )
+ }
+ }
+ }
+ }
+ is UiState.Error -> {
+ Box(
+ modifier = Modifier
+ .height(100.dp)
+ .fillMaxWidth(),
+ contentAlignment = Alignment.Center
+ ) {
+ Text(
+ text = "Fail to fetch usable component",
+ style = MaterialTheme.typography.labelSmall,
+ color = Color.Red
+ )
+ }
+ }
+ }
+ } else {
+ Box(
+ modifier = Modifier
+ .height(100.dp)
+ .fillMaxWidth(),
+ contentAlignment = Alignment.Center
+ ) {
+ CircularProgressIndicator()
+ }
+ }
+
+ Spacer(modifier = Modifier.height(28.dp))
+
+ Text(
+ text = "Related Articles",
+ style = MaterialTheme.typography.labelLarge,
+ color = MaterialTheme.colorScheme.onBackground
+ )
+
+ Spacer(modifier = Modifier.height(8.dp))
+ }
+
+ if (relatedArticleListState != null) {
+ val currentSelectedArticleListState = relatedArticleListState[selectedPrediction]
+ when(currentSelectedArticleListState) {
+ is UiState.Loading -> {
+ Box(
+ modifier = Modifier
+ .height(175.dp)
+ .fillMaxWidth(),
+ contentAlignment = Alignment.Center
+ ) {
+ CircularProgressIndicator()
+ }
+ }
+ is UiState.Error -> {
+ Box(
+ modifier = Modifier
+ .height(175.dp)
+ .fillMaxWidth(),
+ contentAlignment = Alignment.Center
+ ) {
+ Text(
+ text = "Fail to fetch article",
+ style = MaterialTheme.typography.labelSmall,
+ color = Color.Red
+ )
+ }
+ }
+ is UiState.Success -> {
+ val articleList = relatedArticleListState[selectedPrediction].data?.articleList
+
+ if (articleList?.isNotEmpty() == true) {
+ LazyRow(
+ contentPadding = PaddingValues(horizontal = 16.dp),
+ horizontalArrangement = Arrangement.spacedBy(16.dp),
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ relatedArticleListState[selectedPrediction].data?.articleList?.let {
+ items(
+ count = it.size,
+ ) { index ->
+ articleList[index]?.let { it1 ->
+ ArticleCardBig(
+ modifier = Modifier
+ .width(150.dp)
+ .clickable {
+ it1.id?.let { it2 ->
+ navigateToSingleArticle(
+ it2
+ )
+ }
+ },
+ article = it1
+ )
+ }
+ }
+ }
+ }
+ } else {
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(175.dp),
+ contentAlignment = Alignment.Center
+ ) {
+ Box(
+ modifier = Modifier
+ .border(
+ width = 1.dp,
+ color = Color.Red,
+ shape = RoundedCornerShape(20.dp)
+ )
+ .padding(8.dp)
+
+ ) {
+ Text(
+ text = "There's no related article",
+ style = MaterialTheme.typography.labelSmall,
+ color = Color.Red
+ )
+ }
+ }
+ }
+ }
+ }
+ } else {
+ Box(
+ modifier = Modifier
+ .height(100.dp)
+ .fillMaxWidth(),
+ contentAlignment = Alignment.Center
+ ) {
+ CircularProgressIndicator()
+ }
+ }
+
+ Spacer(modifier = Modifier.height(80.dp))
+
+ }
+
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(horizontal = 16.dp)
+ ) {
+ Spacer(modifier = Modifier.weight(1f))
+
+ ThrowNSellButton(
+ navigateToMaps = navigateToMaps,
+ navigateToForum = navigateToForum
+ )
+ }
+ }
+ }
+
+ }
+}
+
+@Composable
+fun ThrowNSellButton(
+ navigateToMaps: () -> Unit,
+ navigateToForum: () -> Unit
+) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth(),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.Center
+ ) {
+ Button(
+ onClick = navigateToMaps,
+ modifier = Modifier
+ .weight(1f)
+ .padding(vertical = 18.dp)
+// .border(
+// width = 2.dp,
+// color = MaterialTheme.colorScheme.primary,
+// shape = RoundedCornerShape(20.dp)
+// )
+ .shadow(
+ elevation = 8.dp,
+ shape = RoundedCornerShape(20.dp)
+ ),
+ colors = ButtonDefaults.buttonColors(
+ containerColor = Color.White,
+ contentColor = MaterialTheme.colorScheme.primary
+ )
+ ) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth(),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.Start
+ ) {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_trash_outline),
+ contentDescription = null,
+ modifier = Modifier
+ .size(26.dp)
+ )
+
+ Spacer(modifier = Modifier.width(4.dp))
+
+ Text(
+ text = "Throw it",
+ style = MaterialTheme.typography.labelMedium,
+ modifier = Modifier
+ .padding(top = 4.dp)
+ )
+ }
+ }
+
+ Spacer(modifier = Modifier.width(8.dp))
+
+ Button(
+ onClick = navigateToForum,
+ modifier = Modifier
+ .weight(1f)
+ .padding(vertical = 18.dp)
+// .border(
+// width = 2.dp,
+// color = MaterialTheme.colorScheme.primary,
+// shape = RoundedCornerShape(20.dp)
+// )
+ .shadow(
+ elevation = 8.dp,
+ shape = RoundedCornerShape(20.dp)
+ ),
+ colors = ButtonDefaults.buttonColors(
+ containerColor = Color.White,
+ contentColor = MaterialTheme.colorScheme.primary
+ )
+ ) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth(),
+ verticalAlignment = Alignment.CenterVertically,
+ horizontalArrangement = Arrangement.Start
+ ) {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_sell),
+ contentDescription = null,
+ modifier = Modifier
+ .size(26.dp)
+ )
+
+ Spacer(modifier = Modifier.width(4.dp))
+
+ Text(
+ text = "Sell it",
+ style = MaterialTheme.typography.labelMedium,
+ modifier = Modifier
+ .padding(top = 4.dp)
+ )
+ }
+ }
+
+ }
+}
+
+//@Preview
+//@Composable
+//fun DetectionResultContentPreview() {
+// TechwasMark02Theme {
+// DetectionResultContent(
+// stringUri = "",
+// detectionResult = "",
+// selectedPrediction = 0,
+// updateSelectedPrediction = {},
+// usableComponentsListState = emptyList(),
+// fetchAllUsableComponents = {},
+// currentlySelectedUsableComponentList = emptyList(),
+// updateCurrentlySelectedUsableComponentList = {},
+// relatedArticleListState = emptyList(),
+// navigateToSingleArticle = {},
+// navigateToMain = {}
+// )
+// }
+//}
+
+@Preview(showBackground = true)
+@Composable
+fun ThrowNSellButtonPreview() {
+ TechwasMark02Theme {
+ Box(
+ modifier = Modifier
+ .padding(20.dp)
+ ) {
+ ThrowNSellButton(
+ navigateToForum = {},
+ navigateToMaps = {}
+ )
+ }
+ }
+}
diff --git a/app/src/main/java/com/capstone/techwasmark02/ui/screen/detectionResult/DetectionResultScreenViewModel.kt b/app/src/main/java/com/capstone/techwasmark02/ui/screen/detectionResult/DetectionResultScreenViewModel.kt
new file mode 100644
index 0000000..e7060b7
--- /dev/null
+++ b/app/src/main/java/com/capstone/techwasmark02/ui/screen/detectionResult/DetectionResultScreenViewModel.kt
@@ -0,0 +1,71 @@
+package com.capstone.techwasmark02.ui.screen.detectionResult
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.capstone.techwasmark02.data.remote.response.ArticleResultResponse
+import com.capstone.techwasmark02.data.remote.response.Prediction
+import com.capstone.techwasmark02.data.remote.response.SmallPart
+import com.capstone.techwasmark02.data.remote.response.UsableComponentsResponse
+import com.capstone.techwasmark02.repository.TechwasArticleRepository
+import com.capstone.techwasmark02.repository.TechwasComponentApiRepository
+import com.capstone.techwasmark02.ui.common.UiState
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class DetectionResultScreenViewModel @Inject constructor(
+ private val componentApiRepository: TechwasComponentApiRepository,
+ private val articleRepository: TechwasArticleRepository
+) : ViewModel() {
+
+ private val _usableComponentsListState: MutableStateFlow>?> = MutableStateFlow(null)
+ val usableComponentsListState = _usableComponentsListState.asStateFlow()
+
+ private val _selectedPrediction: MutableStateFlow = MutableStateFlow(0)
+ val selectedPrediction = _selectedPrediction.asStateFlow()
+
+ private val _currentlySelectedUsableComponentList: MutableStateFlow> = MutableStateFlow(
+ emptyList()
+ )
+ val currentlySelectedUsableComponentList = _currentlySelectedUsableComponentList.asStateFlow()
+
+ private val _relatedArticleListState: MutableStateFlow>?> = MutableStateFlow(null)
+ val relatedArticleListState = _relatedArticleListState.asStateFlow()
+
+ fun fetchAllUsableComponents(componentIdList: List) {
+ viewModelScope.launch {
+ val usableComponentsList = mutableListOf>()
+ val articlesList = mutableListOf>()
+ componentIdList.forEach { prediction ->
+
+ prediction.componentId.let {
+ val usableComponents = componentApiRepository.fetchUsableComponents(compId = it)
+ usableComponentsList.add(usableComponents)
+
+ val relatedArticleList = articleRepository.getArticleByComponentId(id = it)
+ articlesList.add(relatedArticleList)
+ }
+ }
+ _usableComponentsListState.value = usableComponentsList
+ _relatedArticleListState.value = articlesList
+ }
+ }
+
+ fun updateSelectedPrediction(currentlySelectedPrediction: Int) {
+ _selectedPrediction.value = currentlySelectedPrediction
+ }
+
+ fun updateCurrentlySelectedUsableComponentList(newList: List) {
+ _currentlySelectedUsableComponentList.value = newList
+ }
+
+// fun updateRelatedArticleListState(compId: Int) {
+// _relatedArticleListState.value = UiState.Loading()
+// viewModelScope.launch {
+// _relatedArticleListState.value = articleRepository.getArticleByComponentId(id = compId)
+// }
+// }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/capstone/techwasmark02/ui/screen/forum/ForumScreen.kt b/app/src/main/java/com/capstone/techwasmark02/ui/screen/forum/ForumScreen.kt
new file mode 100644
index 0000000..ce0abda
--- /dev/null
+++ b/app/src/main/java/com/capstone/techwasmark02/ui/screen/forum/ForumScreen.kt
@@ -0,0 +1,331 @@
+package com.capstone.techwasmark02.ui.screen.forum
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Row
+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.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.LazyRow
+import androidx.compose.foundation.lazy.items
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.Icon
+import androidx.compose.material3.CircularProgressIndicator
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.navigation.NavHostController
+import com.capstone.techwasmark02.R
+import com.capstone.techwasmark02.data.remote.response.Forum
+import com.capstone.techwasmark02.data.remote.response.ForumResponse
+import com.capstone.techwasmark02.ui.common.UiState
+import com.capstone.techwasmark02.ui.component.ForumBox
+import com.capstone.techwasmark02.ui.component.SearchBox
+import com.capstone.techwasmark02.ui.component.SelectableText
+import com.capstone.techwasmark02.ui.componentType.ArticleFilterType
+import com.capstone.techwasmark02.ui.navigation.Screen
+import com.capstone.techwasmark02.ui.theme.TechwasMark02Theme
+
+@Composable
+fun ForumScreen(
+ navController: NavHostController,
+ viewModel: ForumScreenViewModel = hiltViewModel()
+) {
+ val forumListState by viewModel.forumList.collectAsState()
+ val searchBoxValue by viewModel.searchBoxValue.collectAsState()
+
+ ForumContent(
+ navigateToSingleForum = { navController.navigate("${Screen.SingleForum.route}/$it")},
+ forumListState = forumListState,
+ navigateToCreateForum = { navController.navigate(Screen.CreateForum.route)},
+ searchBoxValue = searchBoxValue,
+ onSearchBoxValueChange = { viewModel.updateSearchBoxValue(it)}
+ )
+}
+
+@Composable
+fun ForumContent(
+ navigateToSingleForum: (Int) -> Unit,
+ forumListState: UiState?,
+ navigateToCreateForum: () -> Unit,
+ searchBoxValue: String,
+ onSearchBoxValueChange: (String) -> Unit
+) {
+
+ val filterTypeList = listOf(
+ ArticleFilterType.General,
+ ArticleFilterType.Battery,
+ ArticleFilterType.Cable,
+ ArticleFilterType.CrtTv,
+ ArticleFilterType.EKettle,
+ ArticleFilterType.Refrigerator,
+ ArticleFilterType.Keyboard,
+ ArticleFilterType.Laptop,
+ ArticleFilterType.LightBulb,
+ ArticleFilterType.Monitor,
+ ArticleFilterType.Mouse,
+ ArticleFilterType.PCB,
+ ArticleFilterType.Printer,
+ ArticleFilterType.RiceCooker,
+ ArticleFilterType.WashingMachine,
+ ArticleFilterType.Phone
+ )
+
+ var selectedFilter by remember {
+ mutableStateOf(filterTypeList.firstOrNull())
+ }
+
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(MaterialTheme.colorScheme.background)
+ ) {
+
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(top = 20.dp, bottom = 80.dp)
+ ) {
+ Column(
+ modifier = Modifier
+ .padding(horizontal = 16.dp)
+ ) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth(),
+ verticalAlignment = Alignment.Top
+ ) {
+ Column(
+ modifier = Modifier
+ .width(280.dp)
+ ) {
+ Text(
+ text = "Forum",
+ style = MaterialTheme.typography.headlineLarge,
+ fontWeight = FontWeight.Bold
+ )
+
+ Spacer(modifier = Modifier.height(8.dp))
+
+ Text(
+ text = "Share your thoughts and connect with others",
+ style = MaterialTheme.typography.bodyLarge.copy(
+ fontWeight = FontWeight.Medium
+ )
+ )
+ }
+
+ Spacer(modifier = Modifier.weight(1f))
+
+ Box(
+ modifier = Modifier
+ .padding(top = 8.dp, end = 8.dp)
+ ) {
+ IconButton(
+ onClick = navigateToCreateForum,
+ modifier = Modifier
+ .size(36.dp)
+ .clip(CircleShape)
+ .background(MaterialTheme.colorScheme.primary)
+ ) {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_create),
+ contentDescription = null,
+ tint = MaterialTheme.colorScheme.onPrimary
+ )
+ }
+ }
+ }
+
+ Spacer(modifier = Modifier.height(16.dp))
+
+ SearchBox(
+ value = searchBoxValue,
+ onValueChange = onSearchBoxValueChange,
+ )
+
+ Spacer(modifier = Modifier.height(16.dp))
+ }
+
+ LazyRow(
+ modifier = Modifier.height(48.dp),
+ horizontalArrangement = Arrangement.spacedBy(8.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ contentPadding = PaddingValues(horizontal = 16.dp)
+ ) {
+ items(
+ items = filterTypeList,
+ ) { item ->
+ SelectableText(
+ filterType = item,
+ selected = item == selectedFilter,
+ modifier = Modifier,
+ onClick = {
+ selectedFilter = item
+ }
+ )
+ }
+ }
+
+ if (forumListState != null) {
+ when(forumListState) {
+ is UiState.Loading -> {
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .weight(1f),
+ contentAlignment = Alignment.Center
+ ) {
+ CircularProgressIndicator()
+ }
+ }
+ is UiState.Error -> {
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(175.dp),
+ contentAlignment = Alignment.Center
+ ) {
+ Box(
+ modifier = Modifier
+ .border(
+ width = 1.dp,
+ color = Color.Red,
+ shape = RoundedCornerShape(20.dp)
+ )
+ .padding(8.dp)
+
+ ) {
+ Text(
+ text = "Fail to fetch forum",
+ style = MaterialTheme.typography.labelSmall,
+ color = Color.Red
+ )
+ }
+ }
+ }
+ is UiState.Success -> {
+ val currentForumList = forumListState.data?.forum
+
+ var filteredForumList by remember {
+ mutableStateOf(currentForumList)
+ }
+
+ LaunchedEffect(key1 = searchBoxValue, key2 = selectedFilter) {
+ if (currentForumList != null) {
+ filteredForumList = selectedFilter?.type?.let {
+ searchForum(
+ forumList = currentForumList,
+ searchBoxValue = searchBoxValue,
+ selectedFilter = it
+ )
+ }
+ }
+ }
+
+ if (!filteredForumList.isNullOrEmpty() && filteredForumList!!.isNotEmpty()) {
+ LazyColumn(
+ modifier = Modifier,
+ contentPadding = PaddingValues(vertical = 20.dp, horizontal = 16.dp),
+ verticalArrangement = Arrangement.spacedBy(10.dp)
+ ) {
+ items(filteredForumList!!) { forum ->
+ ForumBox(
+ modifier = Modifier
+ .fillMaxWidth(),
+ title = forum.title,
+ place = forum.location,
+ desc = forum.content,
+ onClick = { navigateToSingleForum(forum.id) },
+ photoUrl = forum.imageURL
+ )
+ }
+ }
+ } else {
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(175.dp),
+ contentAlignment = Alignment.Center
+ ) {
+ Box(
+ modifier = Modifier
+ .border(
+ width = 1.dp,
+ color = Color.Red,
+ shape = RoundedCornerShape(20.dp)
+ )
+ .padding(8.dp)
+
+ ) {
+ Text(
+ text = "No forum to view",
+ style = MaterialTheme.typography.labelSmall,
+ color = Color.Red
+ )
+ }
+ }
+ }
+ }
+ }
+ }
+
+ }
+ }
+}
+
+private fun searchForum(forumList: List, searchBoxValue: String, selectedFilter: String): List {
+ if (searchBoxValue == "" && selectedFilter == "General") {
+ return forumList
+ }
+
+ val searchBoxFilter = forumList.filter { forum ->
+ forum.title.contains(searchBoxValue, ignoreCase = true)
+ }
+
+ val selectedFilterList = searchBoxFilter.filter { forum ->
+ forum.category == selectedFilter
+ }
+
+ return selectedFilterList
+}
+
+@Preview
+@Composable
+fun ForumScreenPreview() {
+ TechwasMark02Theme {
+ ForumContent(
+ navigateToSingleForum = {},
+ forumListState = null,
+ navigateToCreateForum = {},
+ searchBoxValue = "",
+ onSearchBoxValueChange = {}
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/capstone/techwasmark02/ui/screen/forum/ForumScreenViewModel.kt b/app/src/main/java/com/capstone/techwasmark02/ui/screen/forum/ForumScreenViewModel.kt
new file mode 100644
index 0000000..6afb7dc
--- /dev/null
+++ b/app/src/main/java/com/capstone/techwasmark02/ui/screen/forum/ForumScreenViewModel.kt
@@ -0,0 +1,36 @@
+package com.capstone.techwasmark02.ui.screen.forum
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.capstone.techwasmark02.data.remote.response.ForumResponse
+import com.capstone.techwasmark02.repository.TechwasForumApiRepository
+import com.capstone.techwasmark02.ui.common.UiState
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class ForumScreenViewModel @Inject constructor(
+ private val forumApiRepository: TechwasForumApiRepository
+): ViewModel() {
+
+ private val _forumList: MutableStateFlow?> = MutableStateFlow(null)
+ val forumList = _forumList.asStateFlow()
+
+ private val _searchBoxValue: MutableStateFlow = MutableStateFlow("")
+ val searchBoxValue = _searchBoxValue.asStateFlow()
+
+ fun updateSearchBoxValue(newValue: String) {
+ _searchBoxValue.value = newValue
+ }
+
+ init {
+ _forumList.value = UiState.Loading()
+ viewModelScope.launch {
+ _forumList.value = forumApiRepository.fetchAllForum()
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/capstone/techwasmark02/ui/screen/forumCreate/ForumCreateScreen.kt b/app/src/main/java/com/capstone/techwasmark02/ui/screen/forumCreate/ForumCreateScreen.kt
new file mode 100644
index 0000000..4594bb7
--- /dev/null
+++ b/app/src/main/java/com/capstone/techwasmark02/ui/screen/forumCreate/ForumCreateScreen.kt
@@ -0,0 +1,451 @@
+package com.capstone.techwasmark02.ui.screen.forumCreate
+
+import android.content.Context
+import android.net.Uri
+import android.widget.Toast
+import androidx.activity.compose.rememberLauncherForActivityResult
+import androidx.activity.result.contract.ActivityResultContracts
+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.PaddingValues
+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.size
+import androidx.compose.foundation.lazy.LazyRow
+import androidx.compose.foundation.lazy.items
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material3.ButtonDefaults
+import androidx.compose.material3.CircularProgressIndicator
+import androidx.compose.material3.ElevatedButton
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextField
+import androidx.compose.material3.TextFieldDefaults
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.navigation.NavHostController
+import coil.compose.AsyncImage
+import com.capstone.techwasmark02.R
+import com.capstone.techwasmark02.data.model.ForumToCreateInfo
+import com.capstone.techwasmark02.data.remote.response.CreateForumResponse
+import com.capstone.techwasmark02.data.remote.response.ImageUrlResponse
+import com.capstone.techwasmark02.ui.common.UiState
+import com.capstone.techwasmark02.ui.component.DefaultTopBar
+import com.capstone.techwasmark02.ui.component.SelectableText
+import com.capstone.techwasmark02.ui.componentType.ArticleFilterType
+import com.capstone.techwasmark02.ui.navigation.Screen
+import com.capstone.techwasmark02.ui.theme.TechwasMark02Theme
+
+@Composable
+fun ForumCreateScreen(
+ navController: NavHostController,
+ viewModel: ForumCreateScreenViewModel = hiltViewModel()
+) {
+
+ val forumToCreateInfo by viewModel.forumToCreateInfo.collectAsState()
+ val createForumState by viewModel.createForumState.collectAsState()
+ val imageUri by viewModel.imageUri.collectAsState()
+ val uploadAndGetImageUrlState by viewModel.uploadAndGetImageUrlState.collectAsState()
+
+ ForumCreateContent(
+ navigateBackToForum = {navController.navigate("${Screen.Main.route}/1")},
+ forumToCreateInfo = forumToCreateInfo,
+ updateForumToCreateInfo = { viewModel.updateForumToCreateInfo(it) },
+ createForumState = createForumState,
+ createNewForum = { viewModel.createNewForum() },
+ imageUri = imageUri,
+ updateImageUri = { viewModel.updateImageUri(it) },
+ uploadAndGetImageUrl = { viewModel.uploadAndGetImageUrl(it)},
+ uploadAndGetImageUrlState = uploadAndGetImageUrlState
+ )
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun ForumCreateContent(
+ navigateBackToForum: () -> Unit,
+ forumToCreateInfo: ForumToCreateInfo,
+ updateForumToCreateInfo: (ForumToCreateInfo) -> Unit,
+ createForumState: UiState?,
+ createNewForum: () -> Unit,
+ imageUri: Uri?,
+ updateImageUri: (Uri) -> Unit,
+ uploadAndGetImageUrl: (Context) -> Unit,
+ uploadAndGetImageUrlState: UiState?
+) {
+
+ val context = LocalContext.current
+
+ val filterTypeList = listOf(
+ ArticleFilterType.Battery,
+ ArticleFilterType.Cable,
+ ArticleFilterType.CrtTv,
+ ArticleFilterType.EKettle,
+ ArticleFilterType.Refrigerator,
+ ArticleFilterType.Keyboard,
+ ArticleFilterType.Laptop,
+ ArticleFilterType.LightBulb,
+ ArticleFilterType.Monitor,
+ ArticleFilterType.Mouse,
+ ArticleFilterType.PCB,
+ ArticleFilterType.Printer,
+ ArticleFilterType.RiceCooker,
+ ArticleFilterType.WashingMachine,
+ ArticleFilterType.Phone
+ )
+
+ var selectedFilter by remember {
+ mutableStateOf(filterTypeList.first())
+ }
+
+ val scrollState = rememberScrollState()
+
+ val galleryLauncher = rememberLauncherForActivityResult(
+ contract = ActivityResultContracts.GetContent(),
+ onResult = { storageImageUri ->
+ if (storageImageUri != null) {
+ updateImageUri(storageImageUri)
+ }
+ }
+ )
+
+
+ LaunchedEffect(key1 = selectedFilter) {
+ selectedFilter.type.let {
+ forumToCreateInfo.copy(
+ category = it
+ )
+ }.let { updateForumToCreateInfo(it) }
+ }
+
+ LaunchedEffect(key1 = imageUri) {
+ if (imageUri != null) {
+ uploadAndGetImageUrl(context)
+ }
+ }
+
+
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ ) {
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .verticalScroll(scrollState)
+ ) {
+ Column(
+ modifier = Modifier
+ .padding(top = 88.dp)
+ .padding(horizontal = 16.dp)
+ ) {
+ Text(
+ text = "E-waste picture",
+ style = MaterialTheme.typography.labelLarge.copy(
+ fontWeight = FontWeight.SemiBold
+ )
+ )
+
+ Spacer(modifier = Modifier.height(8.dp))
+
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(240.dp)
+ .padding(8.dp)
+ .clip(RoundedCornerShape(20.dp))
+ .background(Color.LightGray),
+ contentAlignment = Alignment.Center
+ ) {
+ if (uploadAndGetImageUrlState != null) {
+ when(uploadAndGetImageUrlState) {
+ is UiState.Error -> {
+ Box(
+ modifier = Modifier
+ .fillMaxSize(),
+ contentAlignment = Alignment.Center
+ ) {
+ Box(
+ modifier = Modifier
+ .border(
+ width = 1.dp,
+ color = Color.Red,
+ shape = RoundedCornerShape(20.dp)
+ )
+ .padding(8.dp)
+
+ ) {
+ Text(
+ text = "Fail to fetch forum",
+ style = MaterialTheme.typography.labelSmall,
+ color = Color.Red
+ )
+ }
+ }
+ }
+ is UiState.Loading -> {
+ Box(
+ modifier = Modifier
+ .fillMaxSize(),
+ contentAlignment = Alignment.Center
+ ) {
+ CircularProgressIndicator()
+ }
+ }
+ is UiState.Success -> {
+ if (imageUri != null) {
+ AsyncImage(
+ model = imageUri,
+ contentDescription = null,
+ modifier = Modifier
+ .fillMaxSize(),
+ contentScale = ContentScale.Crop
+ )
+ }
+ uploadAndGetImageUrlState.data?.imgURL?.let {
+ forumToCreateInfo.copy(
+ imageUrl = it
+ )
+ }?.let { updateForumToCreateInfo(it) }
+ }
+ }
+ }
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(end = 10.dp, bottom = 10.dp),
+ contentAlignment = Alignment.BottomEnd
+ ) {
+ Box(
+ modifier = Modifier
+ .size(40.dp)
+ .clip(CircleShape)
+ .background(MaterialTheme.colorScheme.tertiary)
+ .clickable {
+ galleryLauncher.launch("image/*")
+ }
+ .padding(8.dp)
+ ) {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_gallery_big),
+ contentDescription = null,
+ tint = Color.Black.copy(alpha = 0.7f)
+ )
+ }
+ }
+ }
+
+ Spacer(modifier = Modifier.height(16.dp))
+
+ Text(
+ text = "E-waste category",
+ style = MaterialTheme.typography.labelLarge.copy(
+ fontWeight = FontWeight.SemiBold
+ ),
+ )
+ }
+
+ LazyRow(
+ modifier = Modifier.height(48.dp),
+ horizontalArrangement = Arrangement.spacedBy(8.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ contentPadding = PaddingValues(horizontal = 16.dp)
+ ) {
+ items(
+ items = filterTypeList,
+ ) { item ->
+ SelectableText(
+ filterType = item,
+ selected = item == selectedFilter,
+ modifier = Modifier,
+ onClick = {
+ selectedFilter = item
+ }
+ )
+ }
+ }
+
+ Column(
+ modifier = Modifier
+ .padding(horizontal = 16.dp)
+ .padding(bottom = 80.dp)
+ ) {
+ Spacer(modifier = Modifier.height(16.dp))
+
+ Text(
+ text = "Forum title",
+ style = MaterialTheme.typography.labelLarge.copy(
+ fontWeight = FontWeight.SemiBold
+ )
+ )
+
+ TextField(
+ value = forumToCreateInfo.title,
+ onValueChange = { newValue ->
+ updateForumToCreateInfo(forumToCreateInfo.copy(
+ title = newValue
+ ))
+ },
+ modifier = Modifier
+ .fillMaxWidth(),
+ colors = TextFieldDefaults.textFieldColors(
+ containerColor = Color.Transparent,
+ )
+ )
+
+ Spacer(modifier = Modifier.height(16.dp))
+
+ Text(
+ text = "Location",
+ style = MaterialTheme.typography.labelLarge.copy(
+ fontWeight = FontWeight.SemiBold
+ )
+ )
+
+ TextField(
+ value = forumToCreateInfo.location,
+ onValueChange = { newValue ->
+ updateForumToCreateInfo(forumToCreateInfo.copy(
+ location = newValue
+ ))
+ },
+ modifier = Modifier
+ .fillMaxWidth(),
+ colors = TextFieldDefaults.textFieldColors(
+ containerColor = Color.Transparent,
+ )
+ )
+
+ Spacer(modifier = Modifier.height(16.dp))
+
+ Text(
+ text = "Forum Description",
+ style = MaterialTheme.typography.labelLarge.copy(
+ fontWeight = FontWeight.SemiBold
+ )
+ )
+
+ TextField(
+ value = forumToCreateInfo.content,
+ onValueChange = { newValue ->
+ updateForumToCreateInfo(forumToCreateInfo.copy(
+ content = newValue
+ ))
+ },
+ modifier = Modifier
+ .fillMaxWidth(),
+ colors = TextFieldDefaults.textFieldColors(
+ containerColor = Color.Transparent,
+ )
+ )
+ }
+ }
+
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(bottom = 8.dp),
+ verticalArrangement = Arrangement.SpaceBetween
+ ) {
+ DefaultTopBar(onClickNavigationIcon = navigateBackToForum, pageTitle = "Create New Forum")
+
+ Column(
+ modifier = Modifier
+ .padding(horizontal = 16.dp)
+ .fillMaxWidth()
+ ) {
+ val context = LocalContext.current
+
+ if (createForumState != null) {
+
+ when(createForumState) {
+ is UiState.Error -> {
+ Toast.makeText(context, "Fail to create new forum", Toast.LENGTH_SHORT).show()
+ }
+ is UiState.Loading -> {
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(56.dp),
+ contentAlignment = Alignment.Center
+ ) {
+ CircularProgressIndicator()
+ }
+ }
+ is UiState.Success -> {
+ navigateBackToForum()
+ }
+ }
+ } else {
+ ElevatedButton(
+ onClick = createNewForum ,
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(56.dp),
+ colors = ButtonDefaults.buttonColors(
+ containerColor = MaterialTheme.colorScheme.primary,
+ contentColor = MaterialTheme.colorScheme.onPrimary
+ ),
+ ) {
+ Text(
+ text = "Create Forum",
+ style = MaterialTheme.typography.labelLarge
+ )
+ }
+ }
+ }
+ }
+ }
+}
+
+@Preview (showBackground = true)
+@Composable
+fun ForumCreateScreenPreview() {
+ TechwasMark02Theme {
+ ForumCreateContent(
+ navigateBackToForum = {},
+ forumToCreateInfo = ForumToCreateInfo(
+ category = "",
+ content = "",
+ imageUrl = "",
+ location = "",
+ title = ""
+ ),
+ updateForumToCreateInfo = {},
+ createForumState = null,
+ createNewForum = {},
+ imageUri = null,
+ updateImageUri = {},
+ uploadAndGetImageUrlState = null,
+ uploadAndGetImageUrl = {}
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/capstone/techwasmark02/ui/screen/forumCreate/ForumCreateScreenViewModel.kt b/app/src/main/java/com/capstone/techwasmark02/ui/screen/forumCreate/ForumCreateScreenViewModel.kt
new file mode 100644
index 0000000..dcb797e
--- /dev/null
+++ b/app/src/main/java/com/capstone/techwasmark02/ui/screen/forumCreate/ForumCreateScreenViewModel.kt
@@ -0,0 +1,131 @@
+package com.capstone.techwasmark02.ui.screen.forumCreate
+
+import android.content.Context
+import android.net.Uri
+import android.webkit.MimeTypeMap
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.capstone.techwasmark02.common.Resource
+import com.capstone.techwasmark02.data.model.ForumToCreateInfo
+import com.capstone.techwasmark02.data.model.UserSession
+import com.capstone.techwasmark02.data.remote.response.CreateForumResponse
+import com.capstone.techwasmark02.data.remote.response.ImageUrlResponse
+import com.capstone.techwasmark02.data.remote.response.Token
+import com.capstone.techwasmark02.data.remote.response.UserId
+import com.capstone.techwasmark02.repository.PreferencesRepository
+import com.capstone.techwasmark02.repository.TechwasForumApiRepository
+import com.capstone.techwasmark02.ui.common.UiState
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.launch
+import java.io.File
+import java.io.FileOutputStream
+import javax.inject.Inject
+
+@HiltViewModel
+class ForumCreateScreenViewModel @Inject constructor(
+ private val forumApiRepository: TechwasForumApiRepository,
+ private val preferencesRepository: PreferencesRepository
+): ViewModel() {
+
+ private val _userSessionState: MutableStateFlow = MutableStateFlow(null)
+ val userSessionState = _userSessionState.asStateFlow()
+
+ private val _createForumState: MutableStateFlow?> = MutableStateFlow(null)
+ val createForumState = _createForumState.asStateFlow()
+
+ private val _forumToCreateInfo: MutableStateFlow = MutableStateFlow(
+ ForumToCreateInfo(
+ category = "Mouse",
+ content = "",
+ imageUrl = "",
+ location = "",
+ title = ""
+ )
+ )
+ val forumToCreateInfo = _forumToCreateInfo.asStateFlow()
+
+ private val _imageUri: MutableStateFlow = MutableStateFlow(null)
+ val imageUri = _imageUri.asStateFlow()
+
+ private val _uploadAndGetImageUrlState: MutableStateFlow?> = MutableStateFlow(null)
+ val uploadAndGetImageUrlState = _uploadAndGetImageUrlState.asStateFlow()
+
+ fun updateForumToCreateInfo(forumToCreateInfo: ForumToCreateInfo) {
+ _forumToCreateInfo.value = forumToCreateInfo
+ }
+
+ fun updateImageUri(newUri: Uri) {
+ _imageUri.value = newUri
+ }
+
+ fun createNewForum() {
+ _createForumState.value = UiState.Loading()
+ viewModelScope.launch {
+ _createForumState.value = _userSessionState.value?.userLoginToken?.accessToken?.let {
+ forumApiRepository.createNewForum(
+ forumToCreateInfo = _forumToCreateInfo.value,
+ userToken = it
+ )
+ }
+ }
+ }
+
+ fun uploadAndGetImageUrl(context: Context) {
+ val fileToUpload = _imageUri.value?.let { convertUriToFile(context, it) }
+
+ _uploadAndGetImageUrlState.value = UiState.Loading()
+ viewModelScope.launch {
+ _uploadAndGetImageUrlState.value = fileToUpload?.let {
+ _userSessionState.value?.userLoginToken?.accessToken?.let { it1 ->
+ forumApiRepository.uploadAndGetImageUrl(
+ file = it,
+ userToken = it1
+ )
+ }
+ }
+ }
+ }
+
+ init {
+ viewModelScope.launch {
+ val result = preferencesRepository.getActiveSession()
+ when(result) {
+ is Resource.Error -> {
+ _userSessionState.value = UserSession(
+ userLoginToken = Token(accessToken = ""),
+ userNameId = UserId(username = "", id = 0)
+ )
+ }
+ is Resource.Success -> {
+ _userSessionState.value = result.data
+ }
+ }
+ }
+ }
+
+ private fun convertUriToFile(context: Context, uri: Uri): File? {
+ val inputStream = context.contentResolver.openInputStream(uri)
+ inputStream?.let {
+ val file = createTempFile(context, getFileExtension(uri))
+ val outputStream = FileOutputStream(file)
+ inputStream.copyTo(outputStream)
+ outputStream.close()
+ inputStream.close()
+ return file
+ }
+ return null
+ }
+
+ private fun createTempFile(context: Context, fileExtension: String): File {
+ val directory = context.cacheDir
+ return File.createTempFile("temp", ".$fileExtension", directory)
+ }
+
+ private fun getFileExtension(uri: Uri): String {
+ val extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(uri.toString())
+ return extension ?: "jpg" // Default to "jpg" if extension is null
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/capstone/techwasmark02/ui/screen/forumSingle/ForumSingleScreen.kt b/app/src/main/java/com/capstone/techwasmark02/ui/screen/forumSingle/ForumSingleScreen.kt
new file mode 100644
index 0000000..d43fd99
--- /dev/null
+++ b/app/src/main/java/com/capstone/techwasmark02/ui/screen/forumSingle/ForumSingleScreen.kt
@@ -0,0 +1,658 @@
+package com.capstone.techwasmark02.ui.screen.forumSingle
+
+import android.widget.Toast
+import androidx.activity.compose.BackHandler
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.IntrinsicSize
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+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.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.widthIn
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.Icon
+import androidx.compose.material3.CircularProgressIndicator
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextField
+import androidx.compose.material3.TextFieldDefaults
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+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.focus.onFocusChanged
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.navigation.NavHostController
+import coil.compose.AsyncImage
+import com.capstone.techwasmark02.R
+import com.capstone.techwasmark02.data.model.UserSession
+import com.capstone.techwasmark02.data.remote.response.ForumCommentResponse
+import com.capstone.techwasmark02.data.remote.response.ForumResponse
+import com.capstone.techwasmark02.data.remote.response.PostForumCommentResponse
+import com.capstone.techwasmark02.data.remote.response.Token
+import com.capstone.techwasmark02.data.remote.response.UserId
+import com.capstone.techwasmark02.ui.common.UiState
+import com.capstone.techwasmark02.ui.component.TransparentTopBar
+import com.capstone.techwasmark02.ui.navigation.Screen
+import com.capstone.techwasmark02.ui.theme.Mist97
+import com.capstone.techwasmark02.ui.theme.TechwasMark02Theme
+
+@Composable
+fun ForumSingleScreen(
+ viewModel: ForumSingleScreenViewModel = hiltViewModel(),
+ navController: NavHostController,
+ forumId: Int
+) {
+ val userSession by viewModel.userSessionState.collectAsState()
+ val forumState by viewModel.forumState.collectAsState()
+ val forumCommentState by viewModel.forumCommentState.collectAsState()
+ val postingCommentState by viewModel.postingCommentState.collectAsState()
+
+ LaunchedEffect(Unit) {
+ viewModel.fetchForumById(id = forumId,)
+ viewModel.fetchForumCommentByForumId(forumId)
+ }
+
+ BackHandler(true) {
+ navController.navigate("${Screen.Main.route}/1")
+ }
+
+ ForumSingleContent(
+ userSession = userSession,
+ navigateBackToForum = { navController.navigate("${Screen.Main.route}/1") },
+ forumState = forumState,
+ forumCommentState = forumCommentState,
+ fetchForumComment = { viewModel.fetchForumCommentByForumId(it)},
+ forumId = forumId,
+ postForumComment = { comment, currentForumId -> viewModel.postForumComment(comment, currentForumId)},
+ postingCommentState = postingCommentState,
+ clearPostingCommentState = { viewModel.clearPostingCommentState()}
+ )
+}
+
+@Composable
+fun ForumSingleContent(
+ userSession: UserSession?,
+ navigateBackToForum: () -> Unit,
+ forumState: UiState?,
+ forumCommentState: UiState?,
+ fetchForumComment: (Int) -> Unit,
+ forumId: Int,
+ postForumComment: (comment: String, currentForumId: Int) -> Unit,
+ postingCommentState: UiState?,
+ clearPostingCommentState: () -> Unit
+) {
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(Mist97)
+ ) {
+
+ val scrollState = rememberScrollState()
+
+ val coroutineScope = rememberCoroutineScope()
+
+ var commentText by remember {
+ mutableStateOf("")
+ }
+
+ var firstRender by remember {
+ mutableStateOf(true)
+ }
+
+
+ LaunchedEffect(key1 = forumCommentState) {
+ if (!firstRender) {
+ scrollState.animateScrollTo(scrollState.maxValue)
+ }
+ }
+
+ if (forumState != null) {
+ when(forumState) {
+ is UiState.Loading -> {
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(360.dp),
+ contentAlignment = Alignment.Center
+ ) {
+ CircularProgressIndicator()
+ }
+ }
+ is UiState.Error -> {
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(360.dp),
+ contentAlignment = Alignment.Center
+ ) {
+ Box(
+ modifier = Modifier
+ .border(
+ width = 1.dp,
+ color = Color.Red,
+ shape = RoundedCornerShape(20.dp)
+ )
+ .padding(8.dp)
+
+ ) {
+ Text(
+ text = "Failed to fetch forum",
+ style = MaterialTheme.typography.labelSmall,
+ color = Color.Red
+ )
+ }
+ }
+ }
+ is UiState.Success -> {
+ val currentForum = forumState.data?.forum?.get(0)
+
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .verticalScroll(scrollState)
+ ) {
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(360.dp)
+// .height(100.dp)
+ .clip(RoundedCornerShape(bottomEnd = 20.dp, bottomStart = 20.dp))
+ .background(MaterialTheme.colorScheme.primary)
+ ) {
+ AsyncImage(
+ model = currentForum?.imageURL,
+ contentDescription = null,
+ contentScale = ContentScale.Crop,
+ modifier = Modifier
+ .fillMaxSize()
+ .background(Color.LightGray)
+ )
+// Image(
+// painter = painterResource(id = R.drawable.img_forum_laptop_bekas),
+// contentDescription = null,
+// contentScale = ContentScale.Crop,
+// modifier = Modifier
+// .fillMaxSize()
+// )
+ }
+
+ Column(
+ modifier = Modifier
+ .padding(horizontal = 16.dp)
+ .padding(top = 24.dp, bottom = 8.dp)
+ ) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth(),
+ horizontalArrangement = Arrangement.SpaceBetween,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ currentForum?.location?.let {
+ Text(
+ text = it,
+ style = MaterialTheme.typography.labelLarge,
+ color = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.8f)
+ )
+ }
+ currentForum?.category?.let {
+ Text(
+ text = it,
+ style = MaterialTheme.typography.labelLarge,
+ color = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.8f)
+ )
+ }
+ }
+
+ Spacer(modifier = Modifier.height(4.dp))
+
+ currentForum?.title?.let {
+ Text(
+ text = it,
+ style = MaterialTheme.typography.titleMedium
+ )
+ }
+
+ Spacer(modifier = Modifier.height(10.dp))
+
+ currentForum?.content?.let {
+ Text(
+ text = it,
+ style = MaterialTheme.typography.bodyMedium.copy(
+ fontWeight = FontWeight.Medium
+ )
+ )
+ }
+
+ }
+
+ if (forumCommentState != null) {
+ when(forumCommentState) {
+ is UiState.Loading -> {
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .weight(1f),
+ contentAlignment = Alignment.Center
+ ) {
+ CircularProgressIndicator()
+ }
+ }
+ is UiState.Error -> {
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .weight(1f),
+ contentAlignment = Alignment.Center
+ ) {
+ Box(
+ modifier = Modifier
+ .border(
+ width = 1.dp,
+ color = Color.Red,
+ shape = RoundedCornerShape(20.dp)
+ )
+ .padding(8.dp)
+
+ ) {
+ Text(
+ text = "Failed to fetch comment",
+ style = MaterialTheme.typography.labelSmall,
+ color = Color.Red
+ )
+ }
+ }
+ }
+ is UiState.Success -> {
+ val commentList = forumCommentState.data?.article
+
+ if (commentList != null) {
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .background(MaterialTheme.colorScheme.tertiary)
+ .padding(top = 8.dp, bottom = 72.dp)
+ ) {
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = 16.dp)
+ ) {
+ Text(
+ text = "Comment",
+ style = MaterialTheme.typography.labelLarge
+ )
+
+ Spacer(modifier = Modifier.height(10.dp))
+
+ if (commentList != null) {
+ Column(
+ modifier = Modifier
+ .padding(bottom = 16.dp)
+ .widthIn(),
+ verticalArrangement = Arrangement.spacedBy(8.dp)
+ ) {
+ if (commentList.isNotEmpty()) {
+ commentList.forEach { comment ->
+ UserInComment(
+ username = comment.username,
+ comment = comment.comment,
+ photoUrl = R.drawable.img_user_2
+ )
+
+ Text(
+ text = "Reply",
+ style = MaterialTheme.typography.bodySmall.copy(
+ fontWeight = FontWeight.Bold
+ ),
+ color = MaterialTheme.colorScheme.primary,
+ modifier = Modifier
+ .padding(start = 30.dp)
+ )
+ }
+ }
+ }
+ }
+ }
+ }
+ } else {
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .weight(1f)
+ .background(MaterialTheme.colorScheme.tertiary)
+ ) {
+ Text(
+ text = "Comment",
+ style = MaterialTheme.typography.labelLarge,
+ modifier = Modifier
+ .padding(top = 8.dp)
+ .padding(horizontal = 16.dp)
+ )
+ }
+ }
+ }
+ }
+ }
+
+
+ }
+
+ Column(
+ modifier = Modifier
+ .fillMaxSize(),
+ verticalArrangement = Arrangement.Bottom
+ ) {
+
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(2.dp)
+ .background(MaterialTheme.colorScheme.primary.copy(alpha = 0.5f))
+ )
+
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .background(MaterialTheme.colorScheme.tertiary)
+ .padding(top = 10.dp, bottom = 10.dp, start = 16.dp, end = 20.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+
+ if (postingCommentState != null) {
+ when(postingCommentState) {
+ is UiState.Success -> {
+ clearPostingCommentState()
+ firstRender = false
+ LaunchedEffect(Unit) {
+ fetchForumComment(forumId)
+ }
+ }
+ is UiState.Loading -> {
+ Box(
+ modifier = Modifier
+ .weight(1f)
+ .height(56.dp)
+ .border(
+ width = 2.dp,
+ color = MaterialTheme.colorScheme.onTertiary.copy(
+ alpha = 0.6f
+ ),
+ shape = RoundedCornerShape(20.dp)
+ ),
+ contentAlignment = Alignment.Center
+ ) {
+ CircularProgressIndicator(
+ modifier = Modifier
+ .size(24.dp)
+ )
+ }
+ }
+ is UiState.Error -> {
+ val context = LocalContext.current
+ Toast.makeText(context, "Fail to post comment", Toast.LENGTH_SHORT).show()
+ clearPostingCommentState()
+ }
+ }
+ } else {
+ CommentTextField(
+ value = commentText,
+ onValueChange = { newValue ->
+ commentText = newValue
+ },
+ modifier = Modifier
+ .weight(1f)
+ )
+ }
+
+// if (commentPosted) {
+// Box(
+// modifier = Modifier
+// .weight(1f)
+// .height(56.dp)
+// .border(
+// width = 2.dp,
+// color = MaterialTheme.colorScheme.onTertiary.copy(alpha = 0.6f),
+// shape = RoundedCornerShape(20.dp)
+// ),
+// contentAlignment = Alignment.Center
+// ) {
+// CircularProgressIndicator(
+// modifier = Modifier
+// .size(24.dp)
+// )
+// }
+// } else {
+// CommentTextField(
+// value = commentText,
+// onValueChange = { newValue ->
+// commentText = newValue
+// },
+// modifier = Modifier
+// .weight(1f)
+// )
+// }
+
+ Spacer(modifier = Modifier.width(10.dp))
+
+ IconButton(
+ onClick = {
+ postForumComment( commentText, forumId)
+ commentText = ""
+ },
+ modifier = Modifier
+ .size(36.dp)
+ .clip(CircleShape)
+ .background(MaterialTheme.colorScheme.primary)
+ ) {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_create),
+ contentDescription = null,
+ tint = MaterialTheme.colorScheme.onPrimary
+ )
+ }
+ }
+ }
+ }
+ }
+ }
+
+ Box(
+ modifier = Modifier
+ .matchParentSize()
+ .background(
+ Brush.verticalGradient(
+ colors = listOf(
+ Color.Black.copy(alpha = 0.7f),
+ Color.Transparent
+ ),
+ startY = 0f,
+ endY = 600f
+ )
+ )
+ ) {
+ TransparentTopBar(onClickNavigationIcon = { navigateBackToForum() }, pageTitle = "Detail Forum")
+ }
+ }
+}
+
+@Preview (showBackground = true)
+@Composable
+fun ForumSingleScreenPreview() {
+ TechwasMark02Theme {
+ ForumSingleContent(
+ userSession = UserSession(
+ userNameId = UserId(
+ username = "Ghina",
+ id = 1
+ ),
+ userLoginToken = Token(
+ accessToken = ""
+ )
+ ),
+ navigateBackToForum = {},
+ forumState = null,
+ forumCommentState = null,
+ fetchForumComment = {},
+ forumId = 0,
+ postingCommentState = null,
+ postForumComment = {comment: String, currentForumId: Int -> {}},
+ clearPostingCommentState = {}
+ )
+ }
+}
+
+@Composable
+fun UserInComment(
+ username: String,
+ comment: String,
+ photoUrl: Int,
+ modifier: Modifier = Modifier
+) {
+ Row(
+ modifier = modifier
+ .height(IntrinsicSize.Max),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Image(
+ painter = painterResource(id = photoUrl),
+ contentDescription = null,
+ modifier = Modifier
+ .clip(CircleShape)
+ .size(60.dp)
+ )
+
+ Spacer(modifier = Modifier.width(16.dp))
+
+ Column(
+ modifier = Modifier
+ .fillMaxHeight(),
+ verticalArrangement = Arrangement.Center
+ ) {
+ Text(
+ text = username,
+ style = MaterialTheme.typography.labelMedium,
+ color = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.8f)
+ )
+
+ Text(
+ text = comment,
+ style = MaterialTheme.typography.bodySmall.copy(
+ fontWeight = FontWeight.Medium
+ ),
+ color = MaterialTheme.colorScheme.onBackground.copy(alpha = 0.9f)
+ )
+ }
+ }
+}
+
+@Preview (showBackground = true)
+@Composable
+fun UserInCommentPreview() {
+ TechwasMark02Theme {
+ Box(
+ modifier = Modifier
+ .padding(10.dp)
+ ) {
+ UserInComment(username = "Ghori", comment = "Woow such a great insight", photoUrl = R.drawable.img_user_1)
+ }
+ }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun CommentTextField(
+ value: String,
+ onValueChange: (String) -> Unit,
+ modifier: Modifier = Modifier
+) {
+
+ var hasFocus by remember {
+ mutableStateOf(false)
+ }
+
+ val focusColor = if (hasFocus) MaterialTheme.colorScheme.onTertiary else MaterialTheme.colorScheme.onTertiary.copy(alpha = 0.6f)
+
+ TextField(
+ value = value,
+ onValueChange = onValueChange,
+ colors = TextFieldDefaults.textFieldColors(
+ textColor = MaterialTheme.colorScheme.onTertiary,
+ containerColor = Color.Transparent,
+ unfocusedIndicatorColor = Color.Transparent,
+ focusedIndicatorColor = Color.Transparent,
+ cursorColor = MaterialTheme.colorScheme.onTertiary,
+ placeholderColor = MaterialTheme.colorScheme.onTertiary.copy(alpha = 0.7f)
+ ),
+ shape = MaterialTheme.shapes.large,
+ placeholder = {
+ Text(
+ text = "Comments...",
+ style = MaterialTheme.typography.labelMedium.copy(
+ fontWeight = FontWeight.SemiBold
+ )
+ )
+ },
+ modifier = modifier
+ .border(
+ width = if (hasFocus) 2.dp else 1.dp,
+ color = focusColor,
+ shape = MaterialTheme.shapes.large
+ )
+ .height(56.dp)
+ .onFocusChanged { focusState -> hasFocus = focusState.hasFocus },
+ textStyle = MaterialTheme.typography.labelMedium.copy(
+ fontWeight = FontWeight.Medium
+ )
+ )
+}
+
+@Preview (showBackground = true)
+@Composable
+fun CommentTextFieldPreview() {
+ TechwasMark02Theme {
+ var value by remember {
+ mutableStateOf("")
+ }
+
+ Box(
+ modifier = Modifier
+ .padding(10.dp)
+ ) {
+ CommentTextField(
+ value = value,
+ onValueChange = { newValue -> value = newValue}
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/capstone/techwasmark02/ui/screen/forumSingle/ForumSingleScreenViewModel.kt b/app/src/main/java/com/capstone/techwasmark02/ui/screen/forumSingle/ForumSingleScreenViewModel.kt
new file mode 100644
index 0000000..2c79914
--- /dev/null
+++ b/app/src/main/java/com/capstone/techwasmark02/ui/screen/forumSingle/ForumSingleScreenViewModel.kt
@@ -0,0 +1,96 @@
+package com.capstone.techwasmark02.ui.screen.forumSingle
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.capstone.techwasmark02.common.Resource
+import com.capstone.techwasmark02.data.model.ForumCommentInfo
+import com.capstone.techwasmark02.data.model.UserSession
+import com.capstone.techwasmark02.data.remote.response.ForumCommentResponse
+import com.capstone.techwasmark02.data.remote.response.ForumResponse
+import com.capstone.techwasmark02.data.remote.response.PostForumCommentResponse
+import com.capstone.techwasmark02.data.remote.response.Token
+import com.capstone.techwasmark02.data.remote.response.UserId
+import com.capstone.techwasmark02.repository.PreferencesRepository
+import com.capstone.techwasmark02.repository.TechwasForumApiRepository
+import com.capstone.techwasmark02.ui.common.UiState
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class ForumSingleScreenViewModel @Inject constructor(
+ private val preferencesRepository: PreferencesRepository,
+ private val forumApiRepository: TechwasForumApiRepository
+): ViewModel() {
+
+ private val _userSessionState: MutableStateFlow = MutableStateFlow(null)
+ val userSessionState = _userSessionState.asStateFlow()
+
+ private val _forumState: MutableStateFlow?> = MutableStateFlow(null)
+ val forumState = _forumState.asStateFlow()
+
+ private val _forumCommentState: MutableStateFlow?> = MutableStateFlow(null)
+ val forumCommentState = _forumCommentState.asStateFlow()
+
+ private val _postingCommentState: MutableStateFlow?> = MutableStateFlow(null)
+ val postingCommentState = _postingCommentState.asStateFlow()
+
+ fun fetchForumById(id: Int) {
+ _forumState.value = UiState.Loading()
+ viewModelScope.launch {
+ _forumState.value = _userSessionState.value?.userLoginToken?.accessToken?.let {
+ forumApiRepository.fetchForumById(
+ id = id,
+ userToken = it
+ )
+ }
+ }
+ }
+
+ fun fetchForumCommentByForumId(forumId: Int) {
+ _forumCommentState.value = UiState.Loading()
+ viewModelScope.launch {
+ _forumCommentState.value = forumApiRepository.fetchForumCommentByForumId(forumId)
+ }
+ }
+
+ fun postForumComment(comment: String, forumId: Int) {
+ _postingCommentState.value = UiState.Loading()
+ val forumCommentInfo = ForumCommentInfo(
+ comment = comment,
+ forumID = forumId
+ )
+ viewModelScope.launch {
+ _userSessionState.value?.userLoginToken?.accessToken?.let {
+ _postingCommentState.value = forumApiRepository.postForumComment(
+ forumCommentInfo = forumCommentInfo,
+ userToken = it
+ )
+ }
+ }
+ }
+
+ fun clearPostingCommentState() {
+ _postingCommentState.value = null
+ }
+
+ init {
+ viewModelScope.launch {
+ val result = preferencesRepository.getActiveSession()
+ when(result) {
+ is Resource.Error -> {
+ _userSessionState.value = UserSession(
+ userLoginToken = Token(accessToken = ""),
+ userNameId = UserId(username = "", id = 0)
+ )
+ }
+ is Resource.Success -> {
+ _userSessionState.value = result.data
+ }
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/capstone/techwasmark02/ui/screen/home/HomeScreen.kt b/app/src/main/java/com/capstone/techwasmark02/ui/screen/home/HomeScreen.kt
index b980c26..8037249 100644
--- a/app/src/main/java/com/capstone/techwasmark02/ui/screen/home/HomeScreen.kt
+++ b/app/src/main/java/com/capstone/techwasmark02/ui/screen/home/HomeScreen.kt
@@ -1,84 +1,187 @@
package com.capstone.techwasmark02.ui.screen.home
+import android.Manifest
+import android.content.Context
+import android.content.pm.PackageManager
+import android.util.Log
+import androidx.activity.compose.ManagedActivityResultLauncher
+import androidx.activity.compose.rememberLauncherForActivityResult
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.Image
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.PaddingValues
+import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
+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.offset
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
-import androidx.compose.foundation.lazy.LazyRow
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.foundation.pager.HorizontalPager
+import androidx.compose.foundation.pager.rememberPagerState
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+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.graphics.Color
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
+import androidx.compose.ui.util.lerp
+import androidx.core.content.ContextCompat
+import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.navigation.NavHostController
+import com.capstone.techwasmark02.R
+import com.capstone.techwasmark02.data.remote.response.ArticleResultResponse
+import com.capstone.techwasmark02.data.remote.response.ForumResponse
+import com.capstone.techwasmark02.ui.common.UiState
import com.capstone.techwasmark02.ui.component.ArticleCardBig
-import com.capstone.techwasmark02.ui.component.DefaultBottomBar
-import com.capstone.techwasmark02.ui.component.DetectionsFab
-import com.capstone.techwasmark02.ui.component.DropPointBanner
-import com.capstone.techwasmark02.ui.component.UserGreet
-import com.capstone.techwasmark02.ui.componentType.BottomBarItemType
+import com.capstone.techwasmark02.ui.component.FeatureBox
+import com.capstone.techwasmark02.ui.component.FeatureBoxLarge
+import com.capstone.techwasmark02.ui.component.ForumBox
+import com.capstone.techwasmark02.ui.componentType.FeatureBoxType
+import com.capstone.techwasmark02.ui.navigation.Screen
+import com.capstone.techwasmark02.ui.navigation.Screen.Camera
+import com.capstone.techwasmark02.ui.navigation.Screen.Catalog
+import com.capstone.techwasmark02.ui.navigation.Screen.Forum
+import com.capstone.techwasmark02.ui.navigation.Screen.Maps
import com.capstone.techwasmark02.ui.theme.TechwasMark02Theme
+import com.capstone.techwasmark02.ui.theme.yellow
+import kotlin.math.absoluteValue
@Composable
-fun HomeScreen() {
- HomeContent()
+fun HomeScreen(
+ viewModel: HomeScreenViewModel = hiltViewModel(),
+ navController: NavHostController
+) {
+
+ val latestArticleState by viewModel.latestArticleState.collectAsState()
+ val forumList by viewModel.forumList.collectAsState()
+
+ HomeContent(
+ navigateToCamera = { navController.navigate(Camera.route) },
+ navigateToArticle = { navController.navigate("${Screen.Main.route}/2") },
+ navigateToForum = { navController.navigate(Forum.route) },
+ navigateToCatalog = { navController.navigate(Catalog.route) },
+ navigateToMaps = { navController.navigate(Maps.route) },
+ navigateToSingleArticle = { navController.navigate("${Screen.SingleArticle.route}/$it") },
+ latestArticleState = latestArticleState,
+ forumList = forumList,
+ navigateToSingleForum = { navController.navigate("${Screen.SingleForum.route}/$it")},
+ )
}
-@OptIn(ExperimentalMaterial3Api::class)
+@OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
@Composable
-fun HomeContent() {
- Scaffold(
+fun HomeContent(
+ navigateToCamera: () -> Unit,
+ navigateToForum: () -> Unit,
+ navigateToArticle: () -> Unit,
+ navigateToCatalog: () -> Unit,
+ navigateToMaps: () -> Unit,
+ navigateToSingleArticle: (idArticle: Int) -> Unit,
+ latestArticleState: UiState?,
+ forumList: UiState?,
+ navigateToSingleForum: (Int) -> Unit,
+) {
+
+ val context = LocalContext.current
+
+ val cameraPermissionLauncher = rememberLauncherForActivityResult(
+ contract = ActivityResultContracts.RequestPermission(),
+ onResult = { granted ->
+ if(granted) {
+ navigateToCamera()
+ }
+ }
+ )
+
+ Scaffold(
) { innerPadding ->
+ val scrollState = rememberScrollState()
+
+ val titlePaddingBottom = 10.dp
+ val titlePaddingTop = 24.dp
+
Box(
modifier = Modifier
- .fillMaxSize()
- .background(MaterialTheme.colorScheme.background)
- .padding(innerPadding),
- contentAlignment = Alignment.BottomCenter
+ .fillMaxSize(),
) {
Column(
modifier = Modifier
- .fillMaxSize()
- .padding(bottom = 60.dp, top = 20.dp),
- horizontalAlignment = Alignment.CenterHorizontally
+ .verticalScroll(scrollState)
+ .padding(innerPadding)
+ .background(MaterialTheme.colorScheme.primary)
+ .padding(top = 20.dp)
) {
- Column(
+ Row(
modifier = Modifier
- .padding(horizontal = 16.dp)
+ .fillMaxWidth()
+ .padding(horizontal = 16.dp),
+ verticalAlignment = Alignment.CenterVertically
) {
- UserGreet(userName = "Zhahrany")
-
- Spacer(modifier = Modifier.height(20.dp))
+ Row(
+ modifier = Modifier,
+ ) {
+ Column(
+ modifier = Modifier
+ .fillMaxHeight(),
- DropPointBanner()
+ ) {
+ Image(
+ modifier = Modifier.size(24.dp, 27.dp),
+ painter = painterResource(id = R.drawable.img_logo_onboarding_nooutline),
+ contentDescription = null
+ )
+ }
+ Text(
+ text = "Techwaste",
+ modifier = Modifier
+ .padding(start = 4.dp)
+ .offset(y = 5.dp),
+ style = MaterialTheme.typography.titleMedium,
+ color = Color.White
+ )
+ }
- Spacer(modifier = Modifier.height(20.dp))
+ Spacer(modifier = Modifier.weight(1f))
- Text(
- text = "What's New?",
- style = MaterialTheme.typography.labelLarge,
- modifier = Modifier.padding(start = 8.dp)
- )
- }
- LazyRow(
- contentPadding = PaddingValues(horizontal = 16.dp),
- horizontalArrangement = Arrangement.spacedBy(16.dp)
- ) {
- items(
- count = 10,
+ IconButton(
+ onClick = { },
+// modifier = Modifier.size(21.dp, 24.dp)
) {
- ArticleCardBig(
- modifier = Modifier.width(240.dp)
+ Icon(
+ painter = painterResource(id = R.drawable.ic_nofitications),
+ contentDescription = null,
+ tint = Color.White
)
}
}
@@ -87,19 +190,300 @@ fun HomeContent() {
Column(
modifier = Modifier
- .padding(horizontal = 16.dp)
+ .fillMaxSize()
+ .clip(
+ RoundedCornerShape(
+ topStart = 20.dp,
+ topEnd = 20.dp
+ )
+ )
+ .background(Color.White)
+ .padding(bottom = 80.dp)
) {
- DropPointBanner()
+ Spacer(modifier = Modifier.height(18.dp))
+
+ Column(
+ Modifier
+ .fillMaxWidth()
+ .padding(horizontal = 20.dp)
+ ) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ ) {
+ Text(
+ text = "Features",
+ style = MaterialTheme.typography.titleMedium,
+ )
+ }
+
+ Spacer(modifier = Modifier.height(titlePaddingBottom))
+
+ Row(
+ modifier = Modifier
+ .fillMaxWidth(),
+ ) {
+
+ FeatureBoxLarge(
+ featureBoxType = FeatureBoxType.Detection,
+ onClick = {
+ checkAndRequestCameraPermission(
+ context = context,
+ cameraPermissionLauncher = cameraPermissionLauncher,
+ onAlreadyGranted = navigateToCamera
+ )
+ }
+ )
+
+// DetectBox1()
+// DetectBox2()
+ }
+
+ Spacer(modifier = Modifier.height(20.dp))
+
+ Row(
+ modifier = Modifier
+ .fillMaxWidth(),
+ ) {
+ FeatureBox(featureBoxType = FeatureBoxType.DropPoint, onClick = navigateToMaps, modifier = Modifier.weight(1f))
+ Spacer(modifier = Modifier.width(24.dp))
+ FeatureBox(featureBoxType = FeatureBoxType.Catalog, onClick = navigateToCatalog, modifier = Modifier.weight(1f))
+ }
+ Spacer(modifier = Modifier.height(titlePaddingTop))
+
+ Row(
+ modifier = Modifier
+ .fillMaxWidth(),
+ horizontalArrangement = Arrangement.Center
+ ) {
+ Text(
+ text = "Articles",
+ style = MaterialTheme.typography.titleMedium,
+ )
+
+ Spacer(modifier = Modifier.weight(1f))
+
+ Text(
+ text = "See all",
+ style = MaterialTheme.typography.labelMedium,
+ fontWeight = FontWeight.Bold,
+ color = yellow,
+ modifier = Modifier
+ .clickable {
+ navigateToArticle()
+ }
+ )
+ }
+ }
+
+ Spacer(modifier = Modifier.height(titlePaddingBottom))
+
+ if (latestArticleState != null) {
+
+ when(latestArticleState) {
+ is UiState.Loading -> {
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(100.dp),
+ contentAlignment = Alignment.Center
+ ) {
+ CircularProgressIndicator()
+ }
+ }
+ is UiState.Error -> {
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(100.dp),
+ contentAlignment = Alignment.Center
+ ) {
+ Text(text = "No article to view")
+ }
+ }
+ is UiState.Success -> {
+ latestArticleState.data?.articleList?.size?.let {
+
+ val pagerState = rememberPagerState(
+ initialPage = 0,
+ initialPageOffsetFraction = 0f,
+ )
+
+ HorizontalPager(
+ pageCount = it,
+ state = pagerState,
+ contentPadding = PaddingValues(horizontal = 48.dp),
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(180.dp)
+ ) { page ->
+ val articleList = latestArticleState.data.articleList
+
+ articleList[page]?.let { it1 ->
+ ArticleCardBig(
+ modifier = Modifier
+ .width(340.dp)
+ .height(180.dp)
+ .graphicsLayer {
+ val pageOffset = (
+ (pagerState.currentPage - page) + pagerState
+ .currentPageOffsetFraction
+ ).absoluteValue
+
+ lerp(
+ start = 0.85f,
+ stop = 1f,
+ fraction = 1f - pageOffset.coerceIn(
+ 0f,
+ 1f
+ )
+ ).also { scale ->
+ scaleX = scale
+ scaleY = scale
+ }
+ alpha = lerp(
+ start = 0.8f,
+ stop = 1f,
+ fraction = 1f - pageOffset.coerceIn(
+ 0f,
+ 1f
+ )
+ )
+ }
+ .clickable {
+ it1.id?.let { it2 ->
+ navigateToSingleArticle(
+ it2
+ )
+ }
+ },
+ article = it1
+ )
+ }
+ }
+ }
+ }
+ }
+ }
+
+ Spacer(modifier = Modifier.height(titlePaddingTop))
+
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(400.dp)
+ .padding(horizontal = 20.dp)
+ ) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth(),
+ horizontalArrangement = Arrangement.Center
+ ) {
+ Text(
+ text = "Forums",
+ style = MaterialTheme.typography.titleMedium,
+ )
+
+ Spacer(modifier = Modifier.weight(1f))
+
+ Text(
+ text = "See all",
+ style = MaterialTheme.typography.labelMedium,
+ fontWeight = FontWeight.Bold,
+ color = yellow,
+ modifier = Modifier
+ .clickable {
+ navigateToForum()
+ }
+ )
+ }
+
+ Spacer(modifier = Modifier.height(titlePaddingBottom))
+
+ if (forumList != null) {
+ when(forumList) {
+ is UiState.Loading -> {
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(200.dp),
+ contentAlignment = Alignment.Center
+ ) {
+ CircularProgressIndicator()
+ }
+ }
+ is UiState.Error -> {
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(200.dp),
+ contentAlignment = Alignment.Center
+ ) {
+ Box(
+ modifier = Modifier
+ .border(
+ width = 1.dp,
+ color = Color.Red,
+ shape = RoundedCornerShape(20.dp)
+ )
+ .padding(8.dp)
+
+ ) {
+ Text(
+ text = "Fail to fetch forum",
+ style = MaterialTheme.typography.labelSmall,
+ color = Color.Red
+ )
+ }
+ }
+ }
+ is UiState.Success -> {
+ val latestForumList = forumList.data?.forum
+
+ LazyColumn(
+ modifier = Modifier
+ .fillMaxSize()
+ .height(400.dp)
+ .padding(horizontal = 16.dp),
+ verticalArrangement = Arrangement.spacedBy(10.dp),
+ contentPadding = PaddingValues( bottom = 16.dp)
+ ) {
+ if (latestForumList != null) {
+ items(latestForumList) { forum ->
+ ForumBox(
+ modifier = Modifier.fillMaxWidth(),
+ title = forum.title,
+ desc = forum.content,
+ place = forum.location,
+ photoUrl = forum.imageURL,
+ onClick = { navigateToSingleForum(forum.id) }
+ )
+ }
+ }
+ }
+ }
+ }
+ }
+ }
}
}
+ }
+ }
+}
- DetectionsFab(
- modifier = Modifier.padding(bottom = 17.dp)
- )
-
- DefaultBottomBar(selectedType = BottomBarItemType.Home)
+private fun checkAndRequestCameraPermission(context: Context, cameraPermissionLauncher: ManagedActivityResultLauncher, onAlreadyGranted: () -> Unit) {
+ when (PackageManager.PERMISSION_GRANTED) {
+ ContextCompat.checkSelfPermission(
+ context,
+ Manifest.permission.CAMERA
+ ) -> {
+ Log.d("Zhahrany", "Permission has already granted")
+ onAlreadyGranted()
+ }
+ else -> {
+ cameraPermissionLauncher.launch(Manifest.permission.CAMERA)
}
}
}
@@ -108,6 +492,16 @@ fun HomeContent() {
@Composable
fun HomeContentPreview() {
TechwasMark02Theme {
- HomeContent()
+ HomeContent(
+ navigateToCamera = {},
+ navigateToArticle = {},
+ navigateToForum = {},
+ navigateToCatalog = {},
+ navigateToMaps = {},
+ navigateToSingleArticle = {},
+ latestArticleState = UiState.Loading(),
+ forumList = null,
+ navigateToSingleForum = {},
+ )
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/capstone/techwasmark02/ui/screen/home/HomeScreenViewModel.kt b/app/src/main/java/com/capstone/techwasmark02/ui/screen/home/HomeScreenViewModel.kt
new file mode 100644
index 0000000..9fe4eac
--- /dev/null
+++ b/app/src/main/java/com/capstone/techwasmark02/ui/screen/home/HomeScreenViewModel.kt
@@ -0,0 +1,38 @@
+package com.capstone.techwasmark02.ui.screen.home
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.capstone.techwasmark02.data.remote.response.ArticleResultResponse
+import com.capstone.techwasmark02.data.remote.response.ForumResponse
+import com.capstone.techwasmark02.repository.TechwasArticleRepository
+import com.capstone.techwasmark02.repository.TechwasForumApiRepository
+import com.capstone.techwasmark02.ui.common.UiState
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class HomeScreenViewModel @Inject constructor(
+ private val articleApiRepository: TechwasArticleRepository,
+ private val forumApiRepository: TechwasForumApiRepository
+): ViewModel() {
+
+ private val _latestArticleState: MutableStateFlow?> = MutableStateFlow(null)
+ val latestArticleState = _latestArticleState.asStateFlow()
+
+ private val _forumList: MutableStateFlow?> = MutableStateFlow(null)
+ val forumList = _forumList.asStateFlow()
+
+ init {
+ _latestArticleState.value = UiState.Loading()
+ _forumList.value = UiState.Loading()
+
+ viewModelScope.launch {
+ _latestArticleState.value = articleApiRepository.getAllArticle()
+ _forumList.value = forumApiRepository.fetchAllForum()
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/capstone/techwasmark02/ui/screen/imageDetection/ImageDetectionScreen.kt b/app/src/main/java/com/capstone/techwasmark02/ui/screen/imageDetection/ImageDetectionScreen.kt
index 589f85d..1a191b1 100644
--- a/app/src/main/java/com/capstone/techwasmark02/ui/screen/imageDetection/ImageDetectionScreen.kt
+++ b/app/src/main/java/com/capstone/techwasmark02/ui/screen/imageDetection/ImageDetectionScreen.kt
@@ -44,7 +44,6 @@ import androidx.compose.ui.unit.dp
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.hilt.navigation.compose.hiltViewModel
-import androidx.lifecycle.viewmodel.compose.viewModel
import coil.compose.AsyncImage
import com.capstone.techwasmark02.R
import com.capstone.techwasmark02.ui.common.UiState
@@ -71,7 +70,7 @@ fun ImageDetectionScreen(viewModel: ImageDetectionScreenViewModel = hiltViewMode
imageUri = imageUri,
predictImageState = predictImageState,
updateImageUri = { viewModel.updateImageUri(it) },
- predictImage = { viewModel.predictImage(it) }
+ predictImage = { }
)
}
diff --git a/app/src/main/java/com/capstone/techwasmark02/ui/screen/imageDetection/ImageDetectionScreenViewModel.kt b/app/src/main/java/com/capstone/techwasmark02/ui/screen/imageDetection/ImageDetectionScreenViewModel.kt
index 828c13f..ace2263 100644
--- a/app/src/main/java/com/capstone/techwasmark02/ui/screen/imageDetection/ImageDetectionScreenViewModel.kt
+++ b/app/src/main/java/com/capstone/techwasmark02/ui/screen/imageDetection/ImageDetectionScreenViewModel.kt
@@ -65,16 +65,16 @@ class ImageDetectionScreenViewModel @Inject constructor(
_imageUri.value = newUri
}
- fun predictImage(context: Context) {
- _predictImageState.value = UiState.Loading()
-
- val imageFileToUpload = _imageUri.value?.let { convertUriToFile(context = context, uri = it) }
- viewModelScope.launch {
- _predictImageState.value = imageFileToUpload?.let {
- predictionApiRepository.predictWaste(it)
- }
- }
- }
+// fun predictImage(context: Context) {
+// _predictImageState.value = UiState.Loading()
+//
+// val imageFileToUpload = _imageUri.value?.let { convertUriToFile(context = context, uri = it) }
+// viewModelScope.launch {
+// _predictImageState.value = imageFileToUpload?.let {
+// predictionApiRepository.predictWaste(it)
+// }
+// }
+// }
// private fun convertUriToFile(context: Context, uri: Uri): File? {
// val inputStream = context.contentResolver.openInputStream(uri)
diff --git a/app/src/main/java/com/capstone/techwasmark02/ui/screen/main/MainScreen.kt b/app/src/main/java/com/capstone/techwasmark02/ui/screen/main/MainScreen.kt
new file mode 100644
index 0000000..9814eeb
--- /dev/null
+++ b/app/src/main/java/com/capstone/techwasmark02/ui/screen/main/MainScreen.kt
@@ -0,0 +1,90 @@
+package com.capstone.techwasmark02.ui.screen.main
+
+import android.app.Activity
+import androidx.activity.compose.BackHandler
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.lifecycle.viewmodel.compose.viewModel
+import androidx.navigation.NavHostController
+import com.capstone.techwasmark02.ui.component.DefaultBottomBar
+import com.capstone.techwasmark02.ui.componentType.BottomBarItemType
+import com.capstone.techwasmark02.ui.screen.article.ArticleScreen
+import com.capstone.techwasmark02.ui.screen.forum.ForumScreen
+import com.capstone.techwasmark02.ui.screen.home.HomeScreen
+import com.capstone.techwasmark02.ui.screen.profileUser.ProfileUserScreen
+
+@Composable
+fun MainScreen(
+ navController: NavHostController,
+ viewModel: MainScreenViewModel = viewModel(),
+ page: Int
+) {
+
+ val selectedBottomBarType by viewModel.selectedBottomBarType.collectAsState()
+ val activity = LocalContext.current as Activity
+
+
+ BackHandler(
+ enabled = true,
+ onBack = {
+ if(selectedBottomBarType.pageIndex != 0) {
+ viewModel.updateSelectedBottomBartype(BottomBarItemType.Home)
+ } else {
+ activity.finish()
+ }
+ }
+ )
+
+ Box(
+ modifier = Modifier
+ .fillMaxSize(),
+ contentAlignment = Alignment.BottomCenter
+ ) {
+
+ LaunchedEffect(Unit) {
+ when(page) {
+ 0 -> {
+ viewModel.updateSelectedBottomBartype(BottomBarItemType.Home)
+ }
+ 1 -> {
+ viewModel.updateSelectedBottomBartype(BottomBarItemType.Forum)
+ }
+ 2 -> {
+ viewModel.updateSelectedBottomBartype(BottomBarItemType.Article)
+ }
+ 3 -> {
+ viewModel.updateSelectedBottomBartype(BottomBarItemType.Profile)
+ }
+ }
+ }
+
+ when(selectedBottomBarType) {
+ is BottomBarItemType.Home -> {
+ HomeScreen(navController = navController)
+ }
+ is BottomBarItemType.Forum -> {
+ ForumScreen(navController = navController)
+ }
+ is BottomBarItemType.Article -> {
+ ArticleScreen(navController = navController)
+ }
+ is BottomBarItemType.Profile -> {
+ ProfileUserScreen(navController = navController)
+ }
+ }
+
+ DefaultBottomBar(
+ selectedType = selectedBottomBarType,
+ onClickBottomNavType = {
+ viewModel.updateSelectedBottomBartype(it)
+ },
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/capstone/techwasmark02/ui/screen/main/MainScreenViewModel.kt b/app/src/main/java/com/capstone/techwasmark02/ui/screen/main/MainScreenViewModel.kt
new file mode 100644
index 0000000..6691882
--- /dev/null
+++ b/app/src/main/java/com/capstone/techwasmark02/ui/screen/main/MainScreenViewModel.kt
@@ -0,0 +1,19 @@
+package com.capstone.techwasmark02.ui.screen.main
+
+import androidx.lifecycle.ViewModel
+import com.capstone.techwasmark02.ui.componentType.BottomBarItemType
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class MainScreenViewModel: ViewModel() {
+
+ private val _selectedBottomBarType: MutableStateFlow = MutableStateFlow(
+ BottomBarItemType.Home
+ )
+ val selectedBottomBarType = _selectedBottomBarType.asStateFlow()
+
+ fun updateSelectedBottomBartype(newItemType: BottomBarItemType) {
+ _selectedBottomBarType.value = newItemType
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/capstone/techwasmark02/ui/screen/maps/MapsScreen.kt b/app/src/main/java/com/capstone/techwasmark02/ui/screen/maps/MapsScreen.kt
new file mode 100644
index 0000000..b630213
--- /dev/null
+++ b/app/src/main/java/com/capstone/techwasmark02/ui/screen/maps/MapsScreen.kt
@@ -0,0 +1,195 @@
+package com.capstone.techwasmark02.ui.screen.maps
+
+import android.Manifest
+import android.content.pm.PackageManager
+import android.location.Location
+import androidx.activity.compose.BackHandler
+import androidx.activity.compose.rememberLauncherForActivityResult
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.compose.material3.AlertDialog
+import androidx.compose.material3.Button
+import androidx.compose.material3.MaterialTheme
+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.setValue
+import androidx.compose.ui.platform.LocalContext
+import androidx.core.content.ContextCompat
+import androidx.navigation.NavHostController
+import com.capstone.techwasmark02.ui.navigation.Screen
+import com.google.android.gms.location.LocationServices
+import com.google.android.gms.maps.model.BitmapDescriptorFactory
+import com.google.android.gms.maps.model.CameraPosition
+import com.google.android.gms.maps.model.LatLng
+import com.google.maps.android.compose.GoogleMap
+import com.google.maps.android.compose.Marker
+import com.google.maps.android.compose.MarkerState
+import com.google.maps.android.compose.rememberCameraPositionState
+import kotlinx.coroutines.tasks.await
+
+data class MapMarkerInfo(
+ val position: LatLng,
+ val title: String,
+ val snippet: String
+)
+@Composable
+fun MapsScreen(navController: NavHostController) {
+
+ BackHandler(true) {
+ navController.navigate("${Screen.Main.route}/0")
+ }
+
+ val diy = LatLng(-7.782275587997325, 110.36709993087182)
+
+ // posisi camera
+ val cameraPositionState = rememberCameraPositionState {
+ position = CameraPosition.fromLatLngZoom(diy, 12f)
+ }
+
+ // dummy marker
+ val markerList = listOf(
+ MapMarkerInfo(
+ LatLng(-7.782589124314163, 110.38006889168956),
+ "Drop Point SKE 1",
+ "throw your e-waste here"
+ ),
+ MapMarkerInfo(
+ LatLng(-7.8041473764088085, 110.39441386554735),
+ "Drop Point SKE 2",
+ "throw your e-waste here"
+ ),
+ MapMarkerInfo(
+ LatLng(-7.790733483350418, 110.35766494375434),
+ "Drop Point SKE 3",
+ "throw your e-waste here"
+ ),
+ MapMarkerInfo(
+ LatLng(-7.767737238844195, 110.35476371308648),
+ "Drop Point SKE 4",
+ "throw your e-waste here"
+ ),
+ MapMarkerInfo(
+ LatLng(-7.774923700677788, 110.41182124955456),
+ "Drop Point SKE 5",
+ "throw your e-waste here"
+ ),
+ MapMarkerInfo(
+ LatLng(-7.7798743027989685, 110.33864576493167),
+ "Drop Point SKE 6",
+ "throw your e-waste here"
+ ),
+ MapMarkerInfo(
+ LatLng(-7.783068208639076, 110.3805524301342),
+ "Drop Point SKE 7",
+ "throw your e-waste here"
+ )
+ )
+
+ val context = LocalContext.current
+ val fusedLocationClient = LocationServices.getFusedLocationProviderClient(context)
+
+ var userLatLng by remember { mutableStateOf(null) }
+ var permissionGranted by remember { mutableStateOf(false) }
+
+ val launcher = rememberLauncherForActivityResult(
+ contract = ActivityResultContracts.RequestPermission()
+ ) { isGranted ->
+ permissionGranted = isGranted
+ }
+
+ LaunchedEffect(permissionGranted) {
+ if (permissionGranted) {
+ val location = fusedLocationClient.lastLocation.await()
+ if (location != null) {
+ userLatLng = LatLng(location.latitude, location.longitude)
+ }
+ }
+ }
+
+ LaunchedEffect(Unit) {
+ when {
+ ContextCompat.checkSelfPermission(context, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED -> {
+ // Permission already granted
+ permissionGranted = true
+ }
+ else -> {
+ // Request permission
+ launcher.launch(Manifest.permission.ACCESS_FINE_LOCATION)
+ }
+ }
+ }
+
+ GoogleMap(
+ // posisi camera
+ cameraPositionState = cameraPositionState,
+ ) {
+ // cari marker dengan jarak terdekat
+ var closestMarker: MapMarkerInfo? = null
+ var closestDistance = Float.MAX_VALUE
+ userLatLng?.let { userLocation ->
+ for (marker in markerList) {
+ val distanceResults = FloatArray(1)
+ Location.distanceBetween(userLocation.latitude, userLocation.longitude, marker.position.latitude, marker.position.longitude, distanceResults)
+ val distance = distanceResults[0]
+ if (distance < closestDistance) {
+ closestDistance = distance
+ closestMarker = marker
+ }
+ }
+ }
+
+ // kasih cat marker nya bg
+ markerList.forEach { marker ->
+ val markerIcon = if (marker == closestMarker) {
+ BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_GREEN)
+ } else {
+ BitmapDescriptorFactory.defaultMarker(BitmapDescriptorFactory.HUE_AZURE)
+ }
+
+ Marker(
+ state = MarkerState(
+ position = marker.position,
+ ),
+ icon = markerIcon,
+ title = marker.title,
+ snippet = marker.snippet
+ )
+ }
+
+ userLatLng?.let { latLng ->
+ val maxDistance = 5000f // jarak maksimum user ke lokasi terdekat (meter)
+ if (closestDistance > maxDistance) {
+ AlertDialog(
+ onDismissRequest = { },
+ title = {
+ Text(
+ text = "oh no, your location is too far",
+ style = MaterialTheme.typography.labelLarge
+ ) },
+ text = {
+ Text(
+ text = "you are too far from the drop point",
+ style = MaterialTheme.typography.bodyMedium
+ ) },
+ confirmButton = {
+ Button(
+ onClick = {
+ navController.popBackStack()
+ }
+ ) {
+ Text("Back")
+ }
+ },
+ )
+ }
+
+ Marker(
+ state = MarkerState(position = latLng),
+ title = "Your Location",
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/capstone/techwasmark02/ui/screen/onBoarding/OnBoardingScreen.kt b/app/src/main/java/com/capstone/techwasmark02/ui/screen/onBoarding/OnBoardingScreen.kt
new file mode 100644
index 0000000..8bfb589
--- /dev/null
+++ b/app/src/main/java/com/capstone/techwasmark02/ui/screen/onBoarding/OnBoardingScreen.kt
@@ -0,0 +1,766 @@
+package com.capstone.techwasmark02.ui.screen.onBoarding
+
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.core.animateDpAsState
+import androidx.compose.animation.core.tween
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+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.IntrinsicSize
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+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.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material3.Button
+import androidx.compose.material3.ButtonDefaults
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.*
+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.res.painterResource
+import androidx.compose.ui.text.font.FontFamily
+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.dp
+import androidx.compose.ui.unit.sp
+import androidx.navigation.NavHostController
+import com.capstone.techwasmark02.R
+import com.capstone.techwasmark02.ui.component.DefaultButton
+import com.capstone.techwasmark02.ui.navigation.Screen
+import com.capstone.techwasmark02.ui.theme.TechwasMark02Theme
+import com.google.accompanist.pager.ExperimentalPagerApi
+import com.google.accompanist.pager.HorizontalPager
+import com.google.accompanist.pager.rememberPagerState
+import kotlinx.coroutines.launch
+
+@Composable
+fun OnBoardingScreen(
+ navController: NavHostController
+) {
+ OnBoardingContent(
+ navigateToHome = { navController.navigate(Screen.Home.route) },
+ navigateToSignUp = { navController.navigate(Screen.SignUp.route) },
+ navigateToSignIn = { navController.navigate(Screen.SignIn.route) },
+ navigateToMain = { navController.navigate("${Screen.Main.route}/0") }
+ )
+}
+
+@OptIn(ExperimentalPagerApi::class)
+@Composable
+fun OnBoardingContent(
+ navigateToHome: () -> Unit,
+ navigateToSignIn: () -> Unit,
+ navigateToSignUp: () -> Unit,
+ navigateToMain: () -> Unit
+) {
+ val pageCount = 3
+ val pagerState = rememberPagerState()
+ val coroutineScope = rememberCoroutineScope()
+
+
+
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ ) {
+ HorizontalPager(
+ count = pageCount,
+ state = pagerState
+ ) { page ->
+ when(page) {
+ 0 -> {
+ OnBoardingContentFirst()
+ }
+ 1 -> {
+ OnBoardingContentSecond()
+ }
+ 2 -> {
+ OnBoardingContentThird(
+ navigateToHome = navigateToHome,
+ navigateToSignIn = navigateToSignIn,
+ navigateToSignUp = navigateToSignUp,
+ navigateToMain = navigateToMain
+ )
+ }
+ }
+ }
+
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ ) {
+ AnimatedVisibility(
+ visible = pagerState.currentPage != 2,
+ enter = fadeIn(),
+ exit = fadeOut()
+ ) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(60.dp)
+ .padding(top = 28.dp)
+ .padding(horizontal = 28.dp),
+ horizontalArrangement = Arrangement.End,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Box(
+ modifier = Modifier,
+ contentAlignment = Alignment.Center
+ ) {
+ Button(
+ onClick = {
+ coroutineScope.launch {
+ pagerState.animateScrollToPage(2)
+ }
+ },
+ modifier = Modifier
+ .height(28.dp)
+ .width(58.dp),
+ colors = ButtonDefaults.buttonColors(
+ containerColor = Color.LightGray
+ )
+ ) {
+ Text(
+ text = "",
+ style = MaterialTheme.typography.bodySmall.copy(
+ fontFamily = FontFamily.Default,
+ fontSize = 10.sp,
+ fontWeight = FontWeight.Medium
+ )
+ )
+ }
+
+ Text(
+ text = "Skip",
+ style = MaterialTheme.typography.bodySmall.copy(
+ fontSize = 10.sp,
+ fontWeight = FontWeight.Medium
+ ),
+ color = Color.Black.copy(alpha = 0.6f),
+ modifier = Modifier
+ .padding(top = 2.dp)
+ .clickable {
+ coroutineScope.launch {
+ pagerState.animateScrollToPage(2)
+ }
+ }
+ )
+ }
+
+ }
+ }
+
+ Spacer(modifier = Modifier.weight(1f))
+
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(60.dp)
+ .padding(bottom = 28.dp)
+ .padding(horizontal = 28.dp),
+ horizontalArrangement = Arrangement.Center,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Spacer(modifier = Modifier.weight(1f))
+
+ Row(
+ modifier = Modifier
+ .width(170.dp),
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ val bulletColor = Color.LightGray.copy(alpha = 0.8f)
+
+ Row(
+ modifier = Modifier
+ .weight(1f),
+ horizontalArrangement = Arrangement.Start
+ ) {
+
+ AnimatedVisibility(
+ visible = pagerState.currentPage != 0,
+ enter = fadeIn(),
+ exit = fadeOut()
+ ) {
+ IconButton(
+ onClick = {
+ coroutineScope.launch {
+ pagerState.animateScrollToPage(pagerState.currentPage - 1)
+ }
+ },
+ modifier = Modifier
+ .background(Color.Transparent)
+ ) {
+ Box(
+ modifier = Modifier
+ .size(30.dp)
+ .clip(CircleShape)
+ .background(bulletColor),
+ contentAlignment = Alignment.Center
+ ) {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_arrow_left),
+ contentDescription = null,
+ tint = Color.Black.copy(alpha = 0.4f)
+ )
+ }
+ }
+ }
+ }
+
+ repeat(pageCount) { iteration ->
+ val color = if (pagerState.currentPage == iteration) Color(0xffFFDE59) else bulletColor
+
+// val size = if (pagerState.currentPage == iteration) 14.dp else 10.dp
+
+ val sidDp: Dp by animateDpAsState(
+ targetValue = if (pagerState.currentPage == iteration) 14.dp else 10.dp,
+ animationSpec = tween(durationMillis = 500)
+ )
+
+ Box(
+ modifier = Modifier
+ .padding(4.dp)
+ .clip(CircleShape)
+ .background(color)
+ .size(sidDp)
+ )
+ }
+
+ Row(
+ modifier = Modifier
+ .weight(1f),
+ horizontalArrangement = Arrangement.End
+ ) {
+ AnimatedVisibility(
+ visible = pagerState.currentPage != 2,
+ enter = fadeIn(),
+ exit = fadeOut()
+ ) {
+ IconButton(
+ onClick = {
+ coroutineScope.launch {
+ pagerState.animateScrollToPage(pagerState.currentPage + 1)
+ }
+ },
+ modifier = Modifier
+ .background(Color.Transparent)
+ ) {
+ Box(
+ modifier = Modifier
+ .size(30.dp)
+ .clip(CircleShape)
+ .background(bulletColor),
+ contentAlignment = Alignment.Center
+ ) {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_arrow_right),
+ contentDescription = null,
+ tint = Color.Black.copy(alpha = 0.4f)
+ )
+ }
+ }
+ }
+ }
+ }
+
+ Spacer(modifier = Modifier.weight(1f))
+
+ }
+ }
+ }
+}
+
+@Composable
+fun OnBoardingContentFirst() {
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+
+ ) {
+ Column(
+ modifier = Modifier
+ .fillMaxSize(),
+ verticalArrangement = Arrangement.SpaceBetween
+ ) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ ) {
+ Image(
+ painter = painterResource( id = R.drawable.img_onboarding_ripple_peach_left, ),
+ contentDescription = null,
+ modifier = Modifier
+ .height(200.dp)
+ .width(240.dp)
+ )
+ }
+
+ Row(
+ modifier = Modifier
+ .fillMaxWidth(),
+ horizontalArrangement = Arrangement.End
+ ) {
+ Image(
+ painter = painterResource(id = R.drawable.img_onboarding_ripple_purple_right),
+ contentDescription = null,
+ modifier = Modifier
+ .height(200.dp)
+ .width(240.dp)
+ )
+ }
+ }
+
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(top = 120.dp),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+
+ Spacer(modifier = Modifier.height(40.dp))
+
+ Image(
+ painter = painterResource(id = R.drawable.img_logo_onboarding),
+ contentDescription = null,
+ modifier = Modifier
+ .size(182.dp)
+ )
+
+ Spacer(modifier = Modifier.height(28.dp))
+
+ Text(
+ text = "Techwaste",
+ style = MaterialTheme.typography.headlineMedium,
+ color = MaterialTheme.colorScheme.primary
+ )
+
+ Spacer(modifier = Modifier.height(20.dp))
+
+ Text(
+ text = "It's an app developed by a\nteam of six students, aiming to\nenhance e-waste disposal!",
+ style = MaterialTheme.typography.bodyLarge,
+ textAlign = TextAlign.Center,
+ color = MaterialTheme.colorScheme.onBackground.copy(
+ alpha = 0.7f
+ )
+ )
+
+ Spacer(modifier = Modifier.weight(1f))
+ }
+ }
+}
+
+@Composable
+fun OnBoardingContentSecond() {
+
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ ) {
+
+ Column(
+ modifier = Modifier
+ .fillMaxSize(),
+ verticalArrangement = Arrangement.SpaceBetween
+ ) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth(),
+ horizontalArrangement = Arrangement.End
+ ) {
+ Image(
+ painter = painterResource(id = R.drawable.img_onboarding_ripple_peach_right),
+ contentDescription = null,
+ modifier = Modifier
+ .height(200.dp)
+ .width(240.dp)
+ )
+ }
+
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ ) {
+ Image(
+ painter = painterResource(id = R.drawable.img_onboarding_ripple_purple_left),
+ contentDescription = null,
+ modifier = Modifier
+ .height(200.dp)
+ .width(240.dp)
+ )
+ }
+ }
+
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .height(IntrinsicSize.Max)
+ .padding(top = 80.dp),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+
+ val boxHeight = 98.dp
+
+ Box(
+ modifier = Modifier,
+ contentAlignment = Alignment.Center
+ ) {
+ Image(
+ painter = painterResource(id = R.drawable.img_feature_detect_box),
+ contentDescription = null,
+ modifier = Modifier
+ .width(92.dp)
+ .height(boxHeight)
+ )
+ Image(
+ painter = painterResource(id = R.drawable.img_feature_detect_illustration),
+ contentDescription = null,
+ modifier = Modifier
+ .width(134.dp)
+ .height(127.dp)
+ )
+ Image(
+ painter = painterResource(id = R.drawable.img_feature_detect_frame),
+ contentDescription = null,
+ modifier = Modifier
+ .size(100.dp)
+ )
+
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(top = (boxHeight + 40.dp)),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Text(
+ text = "E-waste Classification",
+ style = MaterialTheme.typography.labelLarge.copy(
+ fontWeight = FontWeight.SemiBold
+ )
+ )
+ }
+ }
+
+ Text(
+ text = "Upload an image of your e-waste and\nTechwaste will determine where it belongs",
+ style = MaterialTheme.typography.bodySmall.copy(
+ fontSize = 10.sp,
+ color = MaterialTheme.colorScheme.onBackground.copy(
+ alpha = 0.75f
+ )
+ ),
+ textAlign = TextAlign.Center
+ )
+
+// Spacer(modifier = Modifier.height(20.dp))
+
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(IntrinsicSize.Max)
+ .padding(horizontal = 20.dp)
+ ) {
+ Column(
+ modifier = Modifier
+ .width(170.dp),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Box(
+ modifier = Modifier
+ .height(180.dp)
+ .padding(bottom = 8.dp),
+ contentAlignment = Alignment.BottomCenter,
+ ) {
+ Image(
+ painter = painterResource(id = R.drawable.img_feature_article_box),
+ contentDescription = null,
+ modifier = Modifier
+ .height(98.dp)
+ .width(92.dp)
+ )
+
+ Image(
+ painter = painterResource(id = R.drawable.img_feature_article_illustration),
+ contentDescription = null,
+ modifier = Modifier
+ .size(145.dp)
+ )
+ }
+
+ Text(
+ text = "Articles",
+ style = MaterialTheme.typography.labelLarge.copy(
+ fontWeight = FontWeight.SemiBold
+ ),
+ )
+
+ Text(
+ text = "Know more about your\ne-waste and find out how to\nget fid of it properly",
+ style = MaterialTheme.typography.bodySmall.copy(
+ fontSize = 10.sp,
+ color = MaterialTheme.colorScheme.onBackground.copy(
+ alpha = 0.75f
+ )
+ ),
+ textAlign = TextAlign.Center
+ )
+ }
+
+ Spacer(modifier = Modifier.weight(1f))
+
+ Box(
+ modifier = Modifier
+ .fillMaxHeight()
+ .width(170.dp),
+ contentAlignment = Alignment.TopCenter
+ ) {
+ Box(
+ modifier = Modifier
+ .height(180.dp)
+ .fillMaxWidth()
+ .padding(bottom = 8.dp),
+ contentAlignment = Alignment.BottomCenter,
+ ) {
+ Image(
+ painter = painterResource(id = R.drawable.img_feature_forum_box),
+ contentDescription = null,
+ modifier = Modifier
+ .height(98.dp)
+ .width(92.dp)
+ )
+
+ }
+
+ Column(
+ modifier = Modifier
+ .fillMaxWidth(),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Spacer(modifier = Modifier.height(48.dp))
+ Image(
+ painter = painterResource(id = R.drawable.img_feature_forum_illustration),
+ contentDescription = null,
+ modifier = Modifier
+ .height(166.dp)
+ .width(166.dp)
+ )
+ }
+
+
+ Column(
+ modifier = Modifier
+ .fillMaxHeight()
+ .fillMaxWidth(),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Spacer(modifier = Modifier.weight(1f))
+
+ Text(
+ text = "Forum",
+ style = MaterialTheme.typography.labelLarge.copy(
+ fontWeight = FontWeight.SemiBold
+ ),
+ )
+
+ Text(
+ text = "Ask questions, share your\nthoughts, drop any interesting\nfacts about e-waste",
+ style = MaterialTheme.typography.bodySmall.copy(
+ fontSize = 10.sp,
+ color = MaterialTheme.colorScheme.onBackground.copy(
+ alpha = 0.75f
+ )
+ ),
+ textAlign = TextAlign.Center
+ )
+ }
+ }
+ }
+ }
+ }
+}
+
+@Composable
+fun OnBoardingContentThird(
+ navigateToHome: () -> Unit,
+ navigateToSignIn: () -> Unit,
+ navigateToSignUp: () -> Unit,
+ navigateToMain: () -> Unit
+) {
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ ) {
+
+ Column(
+ modifier = Modifier
+ .fillMaxSize(),
+ verticalArrangement = Arrangement.SpaceBetween
+ ) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth(),
+ horizontalArrangement = Arrangement.Start
+ ) {
+ Image(
+ painter = painterResource(id = R.drawable.img_onboarding_ripple_peach_left),
+ contentDescription = null,
+ modifier = Modifier
+ .height(200.dp)
+ .width(240.dp)
+ )
+ }
+
+ Row(
+ modifier = Modifier
+ .fillMaxWidth(),
+ horizontalArrangement = Arrangement.End
+ ) {
+ Image(
+ painter = painterResource(id = R.drawable.img_onboarding_ripple_purple_right),
+ contentDescription = null,
+ modifier = Modifier
+ .height(200.dp)
+ .width(240.dp)
+ )
+ }
+ }
+
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(top = 120.dp, bottom = 100.dp),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.SpaceBetween
+ ) {
+
+ Column(
+ modifier = Modifier
+ .fillMaxWidth(),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Image(
+ painter = painterResource(id = R.drawable.img_logo_onboarding_nooutline_green),
+ contentDescription = null,
+ modifier = Modifier
+ .height(164.dp)
+ .width(170.dp)
+ )
+
+ Spacer(modifier = Modifier.height(18.dp))
+
+ Text(
+ text = "Help us take care of\nelectronics waste.",
+ style = MaterialTheme.typography.headlineMedium,
+ color = MaterialTheme.colorScheme.primary,
+ textAlign = TextAlign.Center
+ )
+
+ Text(
+ text = "What would you like to do?",
+ style = MaterialTheme.typography.bodyMedium,
+ color = MaterialTheme.colorScheme.onBackground.copy(
+ alpha = 0.7f
+ ),
+ textAlign = TextAlign.Center
+ )
+ }
+
+ Spacer(modifier = Modifier.height(48.dp))
+
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = 28.dp),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ DefaultButton(
+ contentText = "Let's get started",
+ modifier = Modifier
+ .height(50.dp)
+ .fillMaxWidth(),
+ buttonColors = ButtonDefaults.buttonColors(
+ containerColor = Color(0xffF7B595)
+ ),
+ onClick = navigateToSignUp
+ )
+
+ Spacer(modifier = Modifier.height(16.dp))
+
+ DefaultButton(
+ contentText = "Already have account",
+ modifier = Modifier
+ .height(50.dp)
+ .fillMaxWidth(),
+ buttonColors = ButtonDefaults.buttonColors(
+ containerColor = MaterialTheme.colorScheme.tertiary,
+ contentColor = Color.Black.copy(
+ alpha = 0.5f
+ )
+ ),
+ onClick = navigateToSignIn
+ )
+
+ }
+ }
+ }
+}
+
+@Preview(showBackground = true)
+@Composable
+fun OnBoardingScreenPreview() {
+ TechwasMark02Theme {
+ OnBoardingContent(
+ navigateToSignIn = {},
+ navigateToSignUp = {},
+ navigateToHome = {},
+ navigateToMain = {}
+ )
+ }
+}
+
+@Preview(showBackground = true)
+@Composable
+fun OnBoardingContentFirstPreview() {
+ TechwasMark02Theme {
+ OnBoardingContentFirst()
+ }
+}
+
+@Preview( showBackground = true)
+@Composable
+fun OnBoardingContentSecondPreview() {
+ TechwasMark02Theme {
+ OnBoardingContentSecond()
+ }
+}
+
+@Preview(showBackground = true)
+@Composable
+fun OnBoardingContentThirdPreview() {
+ TechwasMark02Theme {
+ OnBoardingContentThird(
+ navigateToSignUp = {},
+ navigateToHome = {},
+ navigateToSignIn = {},
+ navigateToMain = {}
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/capstone/techwasmark02/ui/screen/profileUser/ProfileUserScreen.kt b/app/src/main/java/com/capstone/techwasmark02/ui/screen/profileUser/ProfileUserScreen.kt
new file mode 100644
index 0000000..a4b3365
--- /dev/null
+++ b/app/src/main/java/com/capstone/techwasmark02/ui/screen/profileUser/ProfileUserScreen.kt
@@ -0,0 +1,408 @@
+package com.capstone.techwasmark02.ui.screen.profileUser
+
+import androidx.compose.foundation.Image
+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.PaddingValues
+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.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.LazyRow
+import androidx.compose.foundation.lazy.items
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material3.CircularProgressIndicator
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBar
+import androidx.compose.material3.TopAppBarDefaults
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+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.graphics.Color
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.navigation.NavHostController
+import coil.compose.rememberAsyncImagePainter
+import com.capstone.techwasmark02.R
+import com.capstone.techwasmark02.data.local.database.entity.FavoriteArticleEntity
+import com.capstone.techwasmark02.data.model.UserSession
+import com.capstone.techwasmark02.data.remote.response.ArticleResultResponse
+import com.capstone.techwasmark02.data.remote.response.ForumResponse
+import com.capstone.techwasmark02.data.remote.response.Token
+import com.capstone.techwasmark02.data.remote.response.UserId
+import com.capstone.techwasmark02.ui.common.UiState
+import com.capstone.techwasmark02.ui.component.ArticleCardSmall
+import com.capstone.techwasmark02.ui.component.ForumBox
+import com.capstone.techwasmark02.ui.navigation.Screen
+import com.capstone.techwasmark02.ui.theme.TechwasMark02Theme
+
+@Composable
+fun ProfileUserScreen(
+ viewModel: ProfileUserScreenViewModel = hiltViewModel(),
+ navController: NavHostController
+) {
+ val userSession by viewModel.userSessionState.collectAsState()
+ val bookmarkedArticleState by viewModel.bookmarkedArticleState.collectAsState()
+ val favoriteArticlesList by viewModel.favoriteArticlesFlow.collectAsState(initial = null)
+ val forumList by viewModel.forumList.collectAsState()
+
+ ProfileUserContent(
+ navigateToSetting = { navController.navigate(Screen.Setting.route) },
+ userSession = userSession,
+ bookmarkedArticleState = bookmarkedArticleState,
+ favoriteArticleList = favoriteArticlesList,
+ navigateToSingleArticle = { navController.navigate("${Screen.SingleArticle.route}/$it") },
+ forumList = forumList,
+ navigateToSingleForum = { navController.navigate("${Screen.SingleForum.route}/$it")}
+ )
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun ProfileUserContent(
+ navigateToSetting: () -> Unit,
+ userSession: UserSession?,
+ bookmarkedArticleState: UiState?,
+ favoriteArticleList: List?,
+ navigateToSingleArticle: (idArticle: Int) -> Unit,
+ forumList: UiState?,
+ navigateToSingleForum: (Int) -> Unit
+) {
+
+ Scaffold(
+ topBar = {
+ TopAppBar(
+ colors = TopAppBarDefaults.smallTopAppBarColors(
+ containerColor = MaterialTheme.colorScheme.primary,
+ ),
+ title = {},
+ actions = {
+ IconButton(onClick = { navigateToSetting() }) {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_settings),
+ contentDescription = null,
+ tint = Color.White
+ )
+ }
+ }
+ )
+ },
+ ) { innerPadding ->
+ val scrollState = rememberScrollState()
+
+ Column(
+ modifier = Modifier
+ .verticalScroll(scrollState)
+ .padding(innerPadding)
+ .padding(bottom = 80.dp)
+ ) {
+ Box(
+ modifier = Modifier
+ ) {
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(244.dp)
+ .clip(
+ RoundedCornerShape(
+ bottomStart = 20.dp,
+ bottomEnd = 20.dp
+ )
+ )
+ .background(MaterialTheme.colorScheme.primary)
+ .padding(top = 20.dp),
+ ) {
+ Column(
+ modifier = Modifier.align(Alignment.TopCenter),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Image(
+ modifier = Modifier
+ .size(120.dp)
+ .clip(CircleShape),
+ painter = rememberAsyncImagePainter(
+ model = R.drawable.img_user_1,
+ placeholder = painterResource(id = R.drawable.place_holder),
+ ),
+ contentScale = ContentScale.FillHeight,
+ contentDescription = null
+ )
+
+ Spacer(modifier = Modifier.height(24.dp))
+
+ if (userSession != null) {
+ Text(
+ text = userSession.userNameId.username,
+ style = MaterialTheme.typography.titleMedium.copy(
+ fontWeight = FontWeight.SemiBold
+ ),
+ color = Color.White
+ )
+ } else {
+ Text(
+ text = "User Full Name",
+ style = MaterialTheme.typography.labelLarge,
+ color = Color.White
+ )
+ }
+// Text(
+// text = "user@gmail.com",
+// style = MaterialTheme.typography.bodyMedium,
+// color = Color.White
+// )
+ }
+ }
+
+// Column(
+// modifier = Modifier
+// .fillMaxWidth()
+// .padding(top = 200.dp),
+// horizontalAlignment = Alignment.CenterHorizontally
+// ) {
+// ProfileBox(
+// modifier = Modifier
+// .padding(horizontal = 20.dp),
+// navigateToSetting = navigateToSetting
+// )
+// }
+ }
+
+ Spacer(modifier = Modifier.height(16.dp))
+
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(700.dp)
+ ) {
+ Text(
+ text = "Bookmarks",
+ style = MaterialTheme.typography.labelLarge,
+ color = MaterialTheme.colorScheme.onBackground,
+ modifier = Modifier.padding(horizontal = 16.dp)
+ )
+
+ Spacer(modifier = Modifier.height(8.dp))
+
+ if (!favoriteArticleList.isNullOrEmpty() && favoriteArticleList.isNotEmpty()) {
+ favoriteArticleList.let {
+ LazyRow(
+ contentPadding = PaddingValues(horizontal = 16.dp),
+ horizontalArrangement = Arrangement.spacedBy(16.dp)
+ ) {
+ items(
+ count = favoriteArticleList.size,
+ ) { index ->
+ val article = favoriteArticleList[index]
+
+ ArticleCardSmall(
+ modifier = Modifier
+ .width(150.dp)
+ .clickable {
+ navigateToSingleArticle(article.id)
+ },
+ imgUrl = article.articleImageURL,
+ title = article.name,
+ description = article.desc
+ )
+ }
+ }
+ }
+ } else {
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(175.dp),
+ contentAlignment = Alignment.Center
+ ) {
+ Box(
+ modifier = Modifier
+ .border(
+ width = 1.dp,
+ color = Color.Red,
+ shape = RoundedCornerShape(20.dp)
+ )
+ .padding(8.dp)
+
+ ) {
+ Text(
+ text = "There's no related article",
+ style = MaterialTheme.typography.labelSmall,
+ color = Color.Red
+ )
+ }
+ }
+ }
+
+// if (bookmarkedArticleState != null) {
+// when(bookmarkedArticleState) {
+// is UiState.Loading -> {
+// Box(
+// modifier = Modifier
+// .fillMaxWidth()
+// .height(100.dp),
+// contentAlignment = Alignment.Center
+// ) {
+// CircularProgressIndicator()
+// }
+// }
+// is UiState.Error -> {
+// Box(
+// modifier = Modifier
+// .fillMaxWidth()
+// .height(100.dp),
+// contentAlignment = Alignment.Center
+// ) {
+// Text(text = "No article to view")
+// }
+// }
+// is UiState.Success -> {
+// bookmarkedArticleState.data?.articleList?.size?.let {
+// LazyRow(
+// contentPadding = PaddingValues(horizontal = 16.dp),
+// horizontalArrangement = Arrangement.spacedBy(16.dp)
+// ) {
+// items(
+// count = it,
+// ) { page ->
+// val article = bookmarkedArticleState.data.articleList[page]
+//
+// ArticleCardSmall(
+// modifier = Modifier.width(150.dp),
+// imgUrl = article?.articleImageURL,
+// title = article?.name,
+// description = article?.desc
+// )
+// }
+// }
+// }
+// }
+// }
+// }
+
+ Spacer(modifier = Modifier.height(16.dp))
+
+ Text(
+ text = "Forum Histories",
+ style = MaterialTheme.typography.labelLarge,
+ color = MaterialTheme.colorScheme.onBackground,
+ modifier = Modifier.padding(horizontal = 16.dp)
+ )
+
+ Spacer(modifier = Modifier.height(8.dp))
+
+ if (forumList != null) {
+ when(forumList) {
+ is UiState.Loading -> {
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(200.dp),
+ contentAlignment = Alignment.Center
+ ) {
+ CircularProgressIndicator()
+ }
+ }
+ is UiState.Error -> {
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(200.dp),
+ contentAlignment = Alignment.Center
+ ) {
+ Box(
+ modifier = Modifier
+ .border(
+ width = 1.dp,
+ color = Color.Red,
+ shape = RoundedCornerShape(20.dp)
+ )
+ .padding(8.dp)
+
+ ) {
+ Text(
+ text = "Fail to fetch forum",
+ style = MaterialTheme.typography.labelSmall,
+ color = Color.Red
+ )
+ }
+ }
+ }
+ is UiState.Success -> {
+ val latestForumList = forumList.data?.forum
+
+ LazyColumn(
+ modifier = Modifier
+ .fillMaxSize()
+ .height(400.dp)
+ .padding(horizontal = 16.dp),
+ verticalArrangement = Arrangement.spacedBy(10.dp),
+ contentPadding = PaddingValues( vertical = 10.dp)
+ ) {
+ if (latestForumList != null) {
+ items(latestForumList) { forum ->
+ ForumBox(
+ modifier = Modifier.fillMaxWidth(),
+ title = forum.title,
+ desc = forum.content,
+ place = forum.location,
+ photoUrl = forum.imageURL,
+ onClick = { navigateToSingleForum(forum.id) }
+ )
+
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+@Preview
+@Composable
+fun ProfileUserScreenPreview() {
+ TechwasMark02Theme {
+ ProfileUserContent(
+ navigateToSetting = {},
+ userSession = UserSession(
+ userNameId = UserId(
+ username = "Ghina",
+ id = 1
+ ),
+ userLoginToken = Token(
+ accessToken = ""
+ )
+ ),
+ bookmarkedArticleState = null,
+ favoriteArticleList = null,
+ navigateToSingleArticle = {},
+ forumList = null,
+ navigateToSingleForum = {}
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/capstone/techwasmark02/ui/screen/profileUser/ProfileUserScreenViewModel.kt b/app/src/main/java/com/capstone/techwasmark02/ui/screen/profileUser/ProfileUserScreenViewModel.kt
new file mode 100644
index 0000000..f67791f
--- /dev/null
+++ b/app/src/main/java/com/capstone/techwasmark02/ui/screen/profileUser/ProfileUserScreenViewModel.kt
@@ -0,0 +1,64 @@
+package com.capstone.techwasmark02.ui.screen.profileUser
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.capstone.techwasmark02.common.Resource
+import com.capstone.techwasmark02.data.model.UserSession
+import com.capstone.techwasmark02.data.remote.response.ArticleResultResponse
+import com.capstone.techwasmark02.data.remote.response.ForumResponse
+import com.capstone.techwasmark02.data.remote.response.Token
+import com.capstone.techwasmark02.data.remote.response.UserId
+import com.capstone.techwasmark02.repository.FavoriteArticleRepository
+import com.capstone.techwasmark02.repository.PreferencesRepository
+import com.capstone.techwasmark02.repository.TechwasArticleRepository
+import com.capstone.techwasmark02.repository.TechwasForumApiRepository
+import com.capstone.techwasmark02.ui.common.UiState
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class ProfileUserScreenViewModel @Inject constructor(
+ private val preferencesRepository: PreferencesRepository,
+ private val articleApiRepository: TechwasArticleRepository,
+ private val favoriteArticleRepository: FavoriteArticleRepository,
+ private val forumApiRepository: TechwasForumApiRepository
+): ViewModel() {
+
+ private val _userSessionState: MutableStateFlow = MutableStateFlow(null)
+ val userSessionState = _userSessionState.asStateFlow()
+
+ private val _bookmarkedArticleState: MutableStateFlow?> = MutableStateFlow(null)
+ val bookmarkedArticleState = _bookmarkedArticleState.asStateFlow()
+
+ val favoriteArticlesFlow = favoriteArticleRepository.getFavArticles()
+
+ private val _forumList: MutableStateFlow?> = MutableStateFlow(null)
+ val forumList = _forumList.asStateFlow()
+
+ init {
+ _bookmarkedArticleState.value = UiState.Loading()
+ _forumList.value = UiState.Loading()
+
+ viewModelScope.launch {
+ _bookmarkedArticleState.value = articleApiRepository.getAllArticle()
+ _forumList.value = forumApiRepository.fetchAllForum()
+
+ val result = preferencesRepository.getActiveSession()
+ when(result) {
+ is Resource.Error -> {
+ _userSessionState.value = UserSession(
+ userLoginToken = Token(accessToken = ""),
+ userNameId = UserId(username = "", id = 0)
+ )
+ }
+ is Resource.Success -> {
+ _userSessionState.value = result.data
+ }
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/capstone/techwasmark02/ui/screen/setting/SettingScreen.kt b/app/src/main/java/com/capstone/techwasmark02/ui/screen/setting/SettingScreen.kt
new file mode 100644
index 0000000..a546c35
--- /dev/null
+++ b/app/src/main/java/com/capstone/techwasmark02/ui/screen/setting/SettingScreen.kt
@@ -0,0 +1,298 @@
+package com.capstone.techwasmark02.ui.screen.setting
+
+import android.content.Intent
+import android.provider.Settings
+import androidx.activity.compose.BackHandler
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+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.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.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Edit
+import androidx.compose.material3.Button
+import androidx.compose.material3.ButtonDefaults
+import androidx.compose.material3.Icon
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.rememberCoroutineScope
+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.layout.ContentScale
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.core.content.ContextCompat.startActivity
+import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.navigation.NavHostController
+import coil.compose.rememberAsyncImagePainter
+import com.capstone.techwasmark02.R
+import com.capstone.techwasmark02.data.model.UserSession
+import com.capstone.techwasmark02.data.remote.response.Token
+import com.capstone.techwasmark02.data.remote.response.UserId
+import com.capstone.techwasmark02.ui.component.InverseTopBar
+import com.capstone.techwasmark02.ui.component.SettingItem
+import com.capstone.techwasmark02.ui.componentType.SettingItemType
+import com.capstone.techwasmark02.ui.navigation.Screen
+import com.capstone.techwasmark02.ui.theme.TechwasMark02Theme
+import com.capstone.techwasmark02.ui.theme.red
+import kotlinx.coroutines.launch
+
+@Composable
+fun SettingScreen(
+ navController: NavHostController,
+ viewModel: SettingScreenViewModel = hiltViewModel()
+) {
+
+ val userSession by viewModel.userSessionState.collectAsState()
+
+ SettingContent(
+ navigateToProfile = { navController.popBackStack() },
+ userSession = userSession,
+ navigateToOnBoarding = { navController.navigate(Screen.OnBoarding.route)},
+ logOutUser = { viewModel.clearUserSession() },
+ navigateBackToMain = { navController.navigate("${Screen.Main.route}/3") }
+ )
+}
+
+@Composable
+fun SettingContent(
+ navigateToProfile: () -> Unit,
+ userSession: UserSession?,
+ navigateToOnBoarding: () -> Unit,
+ logOutUser: () -> Unit,
+ navigateBackToMain: () -> Unit
+) {
+ BackHandler(true) {
+ navigateBackToMain()
+ }
+
+ val settingItemList = listOf(
+ SettingItemType.Password,
+ SettingItemType.Comment,
+ SettingItemType.Notification,
+ SettingItemType.Language
+ )
+
+ LaunchedEffect(userSession) {
+ if (userSession?.userNameId?.username == "-") {
+ navigateToOnBoarding()
+ }
+ }
+
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ ) {
+ Column(
+ modifier = Modifier
+ .fillMaxSize(),
+ verticalArrangement = Arrangement.SpaceBetween
+ ) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth(),
+ horizontalArrangement = Arrangement.End
+ ) {
+ Image(
+ painter = painterResource(id = R.drawable.img_profile_ripple_green_reverse),
+ contentDescription = null
+ )
+ }
+
+ Row(
+ modifier = Modifier
+ .fillMaxWidth(),
+ horizontalArrangement = Arrangement.Start
+ ) {
+ Image(
+ painter = painterResource(id = R.drawable.img_profile_ripple_green),
+ contentDescription = null
+ )
+ }
+ }
+
+
+ Column(
+ modifier = Modifier
+ .padding(top = 100.dp)
+ ) {
+ Box(
+ modifier = Modifier.fillMaxHeight()
+ ) {
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+
+ ) {
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .align(Alignment.TopCenter),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ Box {
+ Image(
+ modifier = Modifier
+ .size(120.dp)
+ .clip(CircleShape),
+ painter = rememberAsyncImagePainter(
+ model = R.drawable.img_user_1,
+ placeholder = painterResource(id = R.drawable.place_holder),
+ ),
+ contentScale = ContentScale.FillHeight,
+ contentDescription = null
+ )
+ Box(
+ modifier = Modifier
+ .matchParentSize()
+ .clip(CircleShape)
+ .background(Color.Black.copy(alpha = 0.3f))
+ ) {
+ Icon(
+ painter = painterResource(id = R.drawable.ic_camera_fill),
+ contentDescription = "Camera",
+ tint = Color.White,
+ modifier = Modifier
+ .align(Alignment.Center)
+ .size(32.dp)
+ )
+ }
+ }
+
+ Spacer(modifier = Modifier.height(16.dp))
+
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ Text(
+ text = userSession?.userNameId?.username ?: "User Full Name",
+ style = MaterialTheme.typography.labelLarge,
+ color = MaterialTheme.colorScheme.onTertiary
+ )
+ Icon(
+ imageVector = Icons.Default.Edit,
+ contentDescription = "Edit",
+ tint = MaterialTheme.colorScheme.onTertiary,
+ modifier = Modifier
+ .padding(start = 8.dp)
+ .size(24.dp)
+ )
+ }
+
+// Text(
+// text = "user@gmail.com",
+// style = MaterialTheme.typography.bodyMedium,
+// color = Color.White
+// )
+
+ Spacer(modifier = Modifier.height(40.dp))
+
+ Box(modifier = Modifier
+ .fillMaxSize()
+ ) {
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(horizontal = 16.dp),
+ verticalArrangement = Arrangement.spacedBy(10.dp),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ SettingItem(
+ icon = settingItemList[0].icon,
+ title = settingItemList[0].title,
+ )
+
+ SettingItem(
+ icon = settingItemList[1].icon,
+ title = settingItemList[1].title,
+ )
+
+ SettingItem(
+ icon = settingItemList[2].icon,
+ title = settingItemList[2].title,
+ )
+
+ val context = LocalContext.current
+ val scope = rememberCoroutineScope()
+ SettingItem(
+ icon = settingItemList[3].icon,
+ title = settingItemList[3].title,
+ modifier = Modifier.clickable {
+ scope.launch {
+ val intent = Intent(Settings.ACTION_LOCALE_SETTINGS)
+ context.startActivity(intent)
+ }
+ }
+ )
+
+ Spacer(modifier = Modifier.height(10.dp))
+
+ Button(
+ onClick = logOutUser,
+ modifier = Modifier
+ .width(122.dp)
+ .height(41.dp)
+ .align(Alignment.End),
+ colors = ButtonDefaults.buttonColors(
+ containerColor = red.copy(
+ alpha = 0.9f
+ )
+ ),
+ shape = RoundedCornerShape(10.dp)
+ ) {
+ Text(text = "Log out")
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ InverseTopBar(
+ onClickNavigationIcon = {
+ navigateBackToMain()
+ }
+ )
+ }
+}
+
+@Preview(showBackground = true)
+@Composable
+fun SettingScreenPreview() {
+ TechwasMark02Theme {
+ SettingContent(
+ navigateToProfile = {},
+ userSession = UserSession(
+ userNameId = UserId(
+ username = "Ghina",
+ id = 1
+ ),
+ userLoginToken = Token(
+ accessToken = ""
+ )
+ ),
+ navigateToOnBoarding = {},
+ logOutUser = {},
+ navigateBackToMain = {}
+ )
+ }
+}
diff --git a/app/src/main/java/com/capstone/techwasmark02/ui/screen/setting/SettingScreenViewModel.kt b/app/src/main/java/com/capstone/techwasmark02/ui/screen/setting/SettingScreenViewModel.kt
new file mode 100644
index 0000000..a1eb95f
--- /dev/null
+++ b/app/src/main/java/com/capstone/techwasmark02/ui/screen/setting/SettingScreenViewModel.kt
@@ -0,0 +1,58 @@
+package com.capstone.techwasmark02.ui.screen.setting
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.capstone.techwasmark02.common.Resource
+import com.capstone.techwasmark02.data.model.UserSession
+import com.capstone.techwasmark02.data.remote.response.Token
+import com.capstone.techwasmark02.data.remote.response.UserId
+import com.capstone.techwasmark02.repository.PreferencesRepository
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class SettingScreenViewModel @Inject constructor(
+ private val preferencesRepository: PreferencesRepository
+): ViewModel() {
+
+ private val _userSessionState: MutableStateFlow = MutableStateFlow(null)
+ val userSessionState = _userSessionState.asStateFlow()
+
+ init {
+ viewModelScope.launch {
+ val result = preferencesRepository.getActiveSession()
+ when(result) {
+ is Resource.Error -> {
+ _userSessionState.value = UserSession(
+ userLoginToken = Token(accessToken = ""),
+ userNameId = UserId(username = "", id = 0)
+ )
+ }
+ is Resource.Success -> {
+ _userSessionState.value = result.data
+ }
+ }
+ }
+ }
+
+ fun clearUserSession() {
+ viewModelScope.launch {
+ val result = preferencesRepository.clearSession()
+ when(result) {
+ is Resource.Error -> {
+ // do nothing
+ }
+ is Resource.Success -> {
+ _userSessionState.value = UserSession(
+ userLoginToken = Token(accessToken = ""),
+ userNameId = UserId(username = "-", id = 0)
+ )
+ }
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/capstone/techwasmark02/ui/screen/signIn/SignInScreen.kt b/app/src/main/java/com/capstone/techwasmark02/ui/screen/signIn/SignInScreen.kt
index faf1b82..34f8375 100644
--- a/app/src/main/java/com/capstone/techwasmark02/ui/screen/signIn/SignInScreen.kt
+++ b/app/src/main/java/com/capstone/techwasmark02/ui/screen/signIn/SignInScreen.kt
@@ -1,5 +1,6 @@
package com.capstone.techwasmark02.ui.screen.signIn
+import android.widget.Toast
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
@@ -15,41 +16,51 @@ import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
-import androidx.compose.runtime.*
+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.draw.shadow
+import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.navigation.NavHostController
import com.capstone.techwasmark02.data.model.UserLoginInfo
-import com.capstone.techwasmark02.data.model.UserSession
import com.capstone.techwasmark02.data.remote.response.UserLoginResponse
import com.capstone.techwasmark02.ui.common.UiState
import com.capstone.techwasmark02.ui.component.DefaultButton
import com.capstone.techwasmark02.ui.component.DefaultTextField
import com.capstone.techwasmark02.ui.component.PasswordTextField
import com.capstone.techwasmark02.ui.component.SignInBanner
+import com.capstone.techwasmark02.ui.navigation.Screen
import com.capstone.techwasmark02.ui.theme.TechwasMark02Theme
@Composable
fun SignInScreen(
viewModel: SignInScreenViewModel = hiltViewModel(),
+ navController: NavHostController
) {
val userToSignInState by viewModel.userToSignInState.collectAsState()
val userToSignInInfo by viewModel.userToSignInInfo.collectAsState()
- val userSessionState by viewModel.userSessionState.collectAsState()
+ val savedUsername by viewModel.savedUsername.collectAsState()
+// val userSessionState by viewModel.userSessionState.collectAsState()
SignInContent(
userToSignInInfo = userToSignInInfo,
updateUserLoginInfo = { viewModel.updateUserSignInInfo(it) },
userToSignInState = userToSignInState,
signInUser = { viewModel.signInUser() },
- userSessionState = userSessionState
+ saveUserSession = { viewModel.saveUserSession() },
+ navigateToMain = { navController.navigate("${Screen.Main.route}/0") },
+ savedUsername = savedUsername
+// userSessionState = userSessionState,
)
}
@@ -59,13 +70,18 @@ fun SignInContent(
updateUserLoginInfo: (UserLoginInfo) -> Unit,
userToSignInState: UiState?,
signInUser: () -> Unit,
- userSessionState: UserSession?
+ saveUserSession: () -> Unit,
+ navigateToMain: () -> Unit,
+ savedUsername: String?
+// userSessionState: UserSession?
) {
var showPassword by remember {
mutableStateOf(false)
}
+ val context = LocalContext.current
+
Column(
modifier = Modifier
.fillMaxSize()
@@ -74,7 +90,6 @@ fun SignInContent(
) {
SignInBanner(
modifier = Modifier
-
)
Spacer(modifier = Modifier.height(16.dp))
@@ -93,15 +108,19 @@ fun SignInContent(
) {
Text(
text = "Sign In",
- style = MaterialTheme.typography.headlineSmall
+ style = MaterialTheme.typography.headlineSmall,
+ color = MaterialTheme.colorScheme.onTertiary.copy(alpha = 0.8f)
)
Text(
- text = "Please sign in to continue",
- style = MaterialTheme.typography.bodySmall
+ text = "Please sign in! We're excited to have you on board!",
+ style = MaterialTheme.typography.bodyMedium.copy(
+ fontWeight = FontWeight.Medium
+ ),
+ color = MaterialTheme.colorScheme.onTertiary.copy(alpha = 0.8f)
)
- Spacer(modifier = Modifier.height(20.dp))
+ Spacer(modifier = Modifier.height(28.dp))
DefaultTextField(
value = userToSignInInfo.email,
@@ -137,7 +156,10 @@ fun SignInContent(
) {
Text(
text = "Forgot your password?",
- style = MaterialTheme.typography.bodySmall
+ style = MaterialTheme.typography.bodySmall.copy(
+ fontWeight = FontWeight.Medium
+ ),
+ color = MaterialTheme.colorScheme.onTertiary.copy(alpha = 0.8f)
)
}
@@ -158,8 +180,14 @@ fun SignInContent(
}
}
is UiState.Success -> {
- userToSignInState.data?.loginResult?.token?.accessToken?.let {
- Text(text = it)
+ saveUserSession()
+
+ if (savedUsername != null && savedUsername != "") {
+
+ LaunchedEffect(Unit) {
+ Toast.makeText(context, "Welcome $savedUsername", Toast.LENGTH_SHORT).show()
+ navigateToMain()
+ }
}
}
}
@@ -168,13 +196,6 @@ fun SignInContent(
Spacer(modifier = Modifier.weight(1f))
}
- if (userSessionState != null) {
- Text(
- text = userSessionState.userLoginToken.accessToken,
- modifier = Modifier.padding(vertical = 10.dp)
- )
- }
-
DefaultButton(
contentText = "Sign In",
modifier = Modifier
@@ -192,7 +213,8 @@ fun SignInContent(
) {
Text(
text = "Don't have an account?",
- style = MaterialTheme.typography.bodySmall
+ style = MaterialTheme.typography.bodySmall,
+ color = MaterialTheme.colorScheme.onTertiary.copy(alpha = 0.8f)
)
Spacer(modifier = Modifier.width(2.dp))
@@ -220,7 +242,10 @@ fun SingInContentPreview() {
userToSignInInfo = UserLoginInfo("", ""),
updateUserLoginInfo = {},
signInUser = {},
- userSessionState = null
+ saveUserSession = {},
+ navigateToMain = {},
+ savedUsername = null
+// userSessionState = null
)
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/capstone/techwasmark02/ui/screen/signIn/SignInScreenViewModel.kt b/app/src/main/java/com/capstone/techwasmark02/ui/screen/signIn/SignInScreenViewModel.kt
index 0492c01..c4722d5 100644
--- a/app/src/main/java/com/capstone/techwasmark02/ui/screen/signIn/SignInScreenViewModel.kt
+++ b/app/src/main/java/com/capstone/techwasmark02/ui/screen/signIn/SignInScreenViewModel.kt
@@ -5,7 +5,6 @@ import androidx.lifecycle.viewModelScope
import com.capstone.techwasmark02.common.Resource
import com.capstone.techwasmark02.data.mappers.toUserSession
import com.capstone.techwasmark02.data.model.UserLoginInfo
-import com.capstone.techwasmark02.data.model.UserSession
import com.capstone.techwasmark02.data.remote.response.UserLoginResponse
import com.capstone.techwasmark02.repository.PreferencesRepository
import com.capstone.techwasmark02.repository.TechwasUserApiRepository
@@ -32,38 +31,28 @@ class SignInScreenViewModel @Inject constructor(
))
val userToSignInInfo = _userToSignInInfo.asStateFlow()
- private val _userSessionState: MutableStateFlow = MutableStateFlow(null)
- val userSessionState = _userSessionState.asStateFlow()
+ private val _savedUsername: MutableStateFlow = MutableStateFlow(null)
+ val savedUsername = _savedUsername.asStateFlow()
- init {
+ fun signInUser() {
+ _userToSignInState.value = UiState.Loading()
viewModelScope.launch {
- val result = preferencesRepository.getActiveSession()
- when(result) {
- is Resource.Error -> {
- _userSessionState.value = null
- }
- is Resource.Success -> {
- _userSessionState.value = result.data
- }
- }
+ _userToSignInState.value = userApiRepository.userLogin(_userToSignInInfo.value)
}
}
- fun signInUser() {
- _userToSignInState.value = UiState.Loading()
+ fun saveUserSession() {
viewModelScope.launch {
- val result = userApiRepository.userLogin(_userToSignInInfo.value)
- when(result) {
- is UiState.Success -> {
- _userToSignInState.value = result
- result.data?.loginResult?.toUserSession()
- ?.let { preferencesRepository.saveSession(it) }
- }
- is UiState.Error -> {
- _userToSignInState.value = result
- }
- else -> {
- // do nothing
+ val userSession = _userToSignInState.value?.data?.loginResult?.toUserSession()
+ if (userSession != null) {
+ val result = preferencesRepository.saveSession(userSession)
+ when(result) {
+ is Resource.Error -> {
+ _savedUsername.value = ""
+ }
+ is Resource.Success -> {
+ _savedUsername.value = result.data?.userNameId?.username
+ }
}
}
}
diff --git a/app/src/main/java/com/capstone/techwasmark02/ui/screen/signUp/SignUpScreen.kt b/app/src/main/java/com/capstone/techwasmark02/ui/screen/signUp/SignUpScreen.kt
index e2d2b39..416c455 100644
--- a/app/src/main/java/com/capstone/techwasmark02/ui/screen/signUp/SignUpScreen.kt
+++ b/app/src/main/java/com/capstone/techwasmark02/ui/screen/signUp/SignUpScreen.kt
@@ -1,134 +1,189 @@
package com.capstone.techwasmark02.ui.screen.signUp
+import android.widget.Toast
import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
-import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.draw.shadow
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
+import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.navigation.NavHostController
+import com.capstone.techwasmark02.data.model.UserRegisterInfo
+import com.capstone.techwasmark02.data.remote.response.UserRegisterResponse
+import com.capstone.techwasmark02.ui.common.UiState
import com.capstone.techwasmark02.ui.component.DefaultButton
import com.capstone.techwasmark02.ui.component.DefaultTextField
-import com.capstone.techwasmark02.ui.component.InverseTopBar
import com.capstone.techwasmark02.ui.component.PasswordTextField
import com.capstone.techwasmark02.ui.component.SignUpBanner
+import com.capstone.techwasmark02.ui.navigation.Screen
import com.capstone.techwasmark02.ui.theme.TechwasMark02Theme
@Composable
-fun SignUpScreen() {
- SignUpContent()
+fun SignUpScreen(
+ viewModel: SignUpScreenViewModel = hiltViewModel(),
+ navController: NavHostController
+) {
+ val userToSignUpState by viewModel.userToSignUpState.collectAsState()
+ val userToSignUpInfo by viewModel.userToSignUpInfo.collectAsState()
+
+ SignUpContent(
+ userToSignUpInfo = userToSignUpInfo,
+ updateUserRegisterInfo = { viewModel.updateUserSignUpInfo(it) },
+ userToSignUpState = userToSignUpState,
+ signUpUser = { viewModel.signUpUser() },
+ navigateToSignIn = { navController.navigate(Screen.SignIn.route)}
+ )
}
-@OptIn(ExperimentalMaterial3Api::class)
@Composable
-fun SignUpContent() {
- var email by remember {
- mutableStateOf("")
- }
-
- var password by remember {
- mutableStateOf("")
- }
+fun SignUpContent(
+ userToSignUpInfo: UserRegisterInfo,
+ updateUserRegisterInfo: (UserRegisterInfo) -> Unit,
+ userToSignUpState: UiState?,
+ signUpUser: () -> Unit,
+ navigateToSignIn: () -> Unit
+) {
var showPassword by remember {
mutableStateOf(false)
}
- var fullName by remember {
- mutableStateOf("")
- }
+ val context = LocalContext.current
- Scaffold(
- topBar = {
- InverseTopBar(
- onClickNavigationIcon = {}
- )
- }
- ) { innerPadding ->
- Column(
- modifier = Modifier
- .padding(innerPadding)
- .padding(top = 26.dp, bottom = 30.dp)
- .padding(horizontal = 20.dp)
- ) {
- SignUpBanner()
+ Column(
+ modifier = Modifier
+ .padding(top = 26.dp, bottom = 30.dp)
+ .padding(horizontal = 20.dp)
+ ) {
+ SignUpBanner()
- Spacer(modifier = Modifier.height(16.dp))
+ Spacer(modifier = Modifier.height(16.dp))
- Column(
- modifier = Modifier
- .fillMaxSize()
- .shadow(
- elevation = 6.dp,
- shape = MaterialTheme.shapes.large,
- clip = true
- )
- .clip(MaterialTheme.shapes.large)
- .background(MaterialTheme.colorScheme.tertiary)
- .padding(horizontal = 20.dp, vertical = 30.dp)
- ) {
- Text(
- text = "Sign Up",
- style = MaterialTheme.typography.headlineSmall
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .shadow(
+ elevation = 6.dp,
+ shape = MaterialTheme.shapes.large,
+ clip = true
)
+ .clip(MaterialTheme.shapes.large)
+ .background(MaterialTheme.colorScheme.tertiary)
+ .padding(horizontal = 20.dp, vertical = 30.dp)
+ ) {
+ Text(
+ text = "Sign Up",
+ style = MaterialTheme.typography.headlineSmall
+ )
- Text(
- text = "Just a few steps to help the earth managing e-waste!",
- style = MaterialTheme.typography.bodySmall
+ Text(
+ text = "Just a few steps to help the earth managing e-waste!",
+ style = MaterialTheme.typography.bodySmall.copy(
+ fontWeight = FontWeight.Medium
)
+ )
- Spacer(modifier = Modifier.height(20.dp))
-
- DefaultTextField(
- value = fullName,
- onValueChange = { newValue -> fullName = newValue},
- labelText = "Full Name",
- placeHolderText = "user full name",
- modifier = Modifier.fillMaxWidth()
- )
+ Spacer(modifier = Modifier.height(20.dp))
+
+ DefaultTextField(
+ value = userToSignUpInfo.fullname,
+ onValueChange = { newValue ->
+ updateUserRegisterInfo(userToSignUpInfo.copy(
+ fullname = newValue
+ ))
+ },
+ labelText = "Full Name",
+ placeHolderText = "user full name",
+ modifier = Modifier.fillMaxWidth()
+ )
- Spacer(modifier = Modifier.height(16.dp))
+ Spacer(modifier = Modifier.height(16.dp))
- DefaultTextField(
- value = email,
- onValueChange = { newValue -> email = newValue},
- labelText = "Email",
- placeHolderText = "user email",
- modifier = Modifier.fillMaxWidth()
- )
+ DefaultTextField(
+ value = userToSignUpInfo.email,
+ onValueChange = { newValue ->
+ updateUserRegisterInfo(userToSignUpInfo.copy(
+ email = newValue
+ ))
+ },
+ labelText = "Email",
+ placeHolderText = "user email",
+ modifier = Modifier.fillMaxWidth()
+ )
- Spacer(modifier = Modifier.height(16.dp))
+ Spacer(modifier = Modifier.height(16.dp))
- PasswordTextField(
- value = password,
- onValueChange = { newValue -> password = newValue},
- showPassword = showPassword,
- toggleShowPassword = { showPassword = !showPassword },
- modifier = Modifier.fillMaxWidth()
- )
+ PasswordTextField(
+ value = userToSignUpInfo.password,
+ onValueChange = { newValue ->
+ updateUserRegisterInfo(userToSignUpInfo.copy(
+ password = newValue
+ ))
+ },
+ showPassword = showPassword,
+ toggleShowPassword = { showPassword = !showPassword },
+ modifier = Modifier.fillMaxWidth()
+ )
+ if(userToSignUpState != null) {
+ Box(
+ modifier = Modifier
+ .weight(1f)
+ .fillMaxWidth(),
+ contentAlignment = Alignment.Center
+ ) {
+ when(userToSignUpState) {
+ is UiState.Loading -> {
+ CircularProgressIndicator()
+ }
+ is UiState.Error -> {
+ userToSignUpState.message?.let {
+ Text(text = it)
+ }
+ }
+ is UiState.Success -> {
+
+ Toast.makeText(context, userToSignUpState.data?.message, Toast.LENGTH_SHORT).show()
+
+ LaunchedEffect(Unit) {
+ navigateToSignIn()
+ }
+ }
+ }
+ }
+ } else {
Spacer(modifier = Modifier.weight(1f))
+ }
- DefaultButton(contentText = "Sign Up", modifier = Modifier
+ DefaultButton(
+ contentText = "Sign Up",
+ modifier = Modifier
.fillMaxWidth()
- .height(50.dp))
-
- }
+ .height(50.dp),
+ onClick = signUpUser
+ )
}
}
}
@@ -137,6 +192,12 @@ fun SignUpContent() {
@Composable
fun SingUpContentPreview() {
TechwasMark02Theme {
- SignUpContent()
+ SignUpContent(
+ userToSignUpState = null,
+ userToSignUpInfo = UserRegisterInfo("", "", ""),
+ updateUserRegisterInfo = {},
+ signUpUser = {},
+ navigateToSignIn = {}
+ )
}
}
\ No newline at end of file
diff --git a/app/src/main/java/com/capstone/techwasmark02/ui/screen/signUp/SignUpScreenViewModel.kt b/app/src/main/java/com/capstone/techwasmark02/ui/screen/signUp/SignUpScreenViewModel.kt
index a1dd5f2..053ef28 100644
--- a/app/src/main/java/com/capstone/techwasmark02/ui/screen/signUp/SignUpScreenViewModel.kt
+++ b/app/src/main/java/com/capstone/techwasmark02/ui/screen/signUp/SignUpScreenViewModel.kt
@@ -1,15 +1,45 @@
package com.capstone.techwasmark02.ui.screen.signUp
import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.capstone.techwasmark02.data.model.UserRegisterInfo
+import com.capstone.techwasmark02.data.remote.response.UserRegisterResponse
import com.capstone.techwasmark02.repository.PreferencesRepository
import com.capstone.techwasmark02.repository.TechwasUserApiRepository
+import com.capstone.techwasmark02.ui.common.UiState
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.launch
import javax.inject.Inject
+@HiltViewModel
class SignUpScreenViewModel @Inject constructor(
private val userApiRepository: TechwasUserApiRepository,
- private val preferencesRepository: PreferencesRepository
): ViewModel() {
-// private val _userToSignUpState
+ private val _userToSignUpState: MutableStateFlow?> =
+ MutableStateFlow(null)
+ val userToSignUpState = _userToSignUpState.asStateFlow()
+ private val _userToSignUpInfo: MutableStateFlow =
+ MutableStateFlow(
+ UserRegisterInfo(
+ fullname = "",
+ email = "",
+ password = ""
+ )
+ )
+ val userToSignUpInfo = _userToSignUpInfo.asStateFlow()
+
+ fun signUpUser() {
+ _userToSignUpState.value = UiState.Loading()
+ viewModelScope.launch {
+ _userToSignUpState.value = userApiRepository.userRegister(_userToSignUpInfo.value)
+ }
+ }
+
+ fun updateUserSignUpInfo(userToSignUpInfo: UserRegisterInfo) {
+ _userToSignUpInfo.value = userToSignUpInfo
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/capstone/techwasmark02/ui/screen/singleArticle/SingleArticleScreen.kt b/app/src/main/java/com/capstone/techwasmark02/ui/screen/singleArticle/SingleArticleScreen.kt
new file mode 100644
index 0000000..7ae09a5
--- /dev/null
+++ b/app/src/main/java/com/capstone/techwasmark02/ui/screen/singleArticle/SingleArticleScreen.kt
@@ -0,0 +1,327 @@
+package com.capstone.techwasmark02.ui.screen.singleArticle
+
+import android.content.Intent
+import android.text.Html
+import androidx.activity.compose.BackHandler
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Favorite
+import androidx.compose.material3.CircularProgressIndicator
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.draw.shadow
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.painterResource
+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 androidx.hilt.navigation.compose.hiltViewModel
+import androidx.lifecycle.viewModelScope
+import androidx.navigation.NavHostController
+import coil.compose.rememberAsyncImagePainter
+import com.capstone.techwasmark02.R
+import com.capstone.techwasmark02.data.local.database.entity.FavoriteArticleEntity
+import com.capstone.techwasmark02.data.model.FavoriteArticle
+import com.capstone.techwasmark02.data.remote.response.SingleArticleResponse
+import com.capstone.techwasmark02.ui.common.UiState
+import com.capstone.techwasmark02.ui.component.DefaultButton
+import com.capstone.techwasmark02.ui.component.HtmlText
+import com.capstone.techwasmark02.ui.component.TransparentTopBar
+import com.capstone.techwasmark02.ui.navigation.Screen
+import com.capstone.techwasmark02.ui.theme.Mist97
+import com.capstone.techwasmark02.ui.theme.TechwasMark02Theme
+import kotlinx.coroutines.launch
+
+@Composable
+fun SingleArticleScreen(
+ idArticle: Int,
+ viewModel: SingleArticleScreenViewModel = hiltViewModel(),
+ navController: NavHostController
+) {
+
+ LaunchedEffect(Unit) {
+ viewModel.viewModelScope.launch {
+ viewModel.getArticleById(idArticle)
+ viewModel.getFavArticleById(idArticle)
+ }
+ }
+
+ val articleResult by viewModel.articleResult.collectAsState()
+
+ val isArticleFavorited by viewModel.isArticleFavorited.collectAsState(initial = null)
+
+ SingleArticleContent(
+ articleResult = articleResult,
+ navigateToArticle = { navController.navigate("${Screen.Main.route}/2") },
+ isArticleFavorited = isArticleFavorited,
+ updateArticleFavorited = { articleGetFavorited, favoriteArticle -> viewModel.updateArticleFavorited(articleGetFavorited, favoriteArticle)}
+ )
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun SingleArticleContent(
+ articleResult: UiState?,
+ navigateToArticle: () -> Unit,
+ isArticleFavorited: FavoriteArticleEntity?,
+ updateArticleFavorited: ((articleGetFavorited: Boolean, favoriteArticle: FavoriteArticle) -> Unit)
+) {
+ val context = LocalContext.current
+
+ BackHandler(true) {
+ navigateToArticle()
+ }
+
+ val result = articleResult?.data?.article
+
+ Scaffold { innerPadding ->
+ val scrollState = rememberScrollState()
+
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .padding(innerPadding)
+ ) {
+ if (articleResult != null) {
+ when(articleResult) {
+ is UiState.Loading -> {
+ Box(
+ modifier = Modifier
+ .fillMaxSize(),
+ contentAlignment = Alignment.Center
+ ) {
+ CircularProgressIndicator()
+ }
+ }
+ is UiState.Error -> {
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ )
+ }
+ is UiState.Success -> {
+ val currentArticle = articleResult.data?.article?.get(0)
+ val isArticleFavorite = currentArticle?.id == isArticleFavorited?.id
+
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ .background(Color.White)
+ .verticalScroll(scrollState)
+ ) {
+
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .background(Mist97)
+ ) {
+ Column(
+ modifier = Modifier
+ .fillMaxSize()
+ ) {
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(300.dp)
+ .clip(
+ RoundedCornerShape(
+ topStart = 0.dp,
+ topEnd = 0.dp,
+ bottomEnd = 16.dp,
+ bottomStart = 16.dp
+ )
+ )
+ ) {
+ Image(
+ modifier = Modifier.matchParentSize(),
+ painter = rememberAsyncImagePainter(
+ model = result?.get(0)?.articleImageURL,
+ placeholder = painterResource(id = R.drawable.place_holder),
+ ),
+ contentScale = ContentScale.Crop,
+ contentDescription = null
+ )
+ }
+
+ Column(modifier = Modifier
+ .padding(bottom = 10.dp, top = 10.dp)
+ .padding(horizontal = 16.dp)
+ ) {
+ result?.get(0)?.componentName?.let {
+ Text(
+ text = it,
+ style = MaterialTheme.typography.bodyMedium
+ )
+ }
+ result?.get(0)?.name?.let {
+ Text(
+ text = it,
+ style = MaterialTheme.typography.titleLarge,
+ fontWeight = FontWeight.Bold,
+ )
+ }
+ Text(
+ text = "source: techwaste",
+ style = MaterialTheme.typography.bodyMedium
+ )
+ }
+ }
+
+ Box(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(end = 16.dp, top = 270.dp),
+ contentAlignment = Alignment.CenterEnd
+ ) {
+
+ IconButton(
+ onClick = {
+ val favoritedArticle = currentArticle?.id?.let {
+ currentArticle.name?.let { it1 ->
+ currentArticle.articleImageURL?.let { it2 ->
+ currentArticle.componentID?.let { it3 ->
+ currentArticle.description?.let { it4 ->
+ FavoriteArticle(
+ id = it,
+ name = it1,
+ imageURL = it2,
+ compId = it3,
+ desc = it4
+ )
+ }
+ }
+ }
+ }
+ }
+ if (isArticleFavorite) {
+ if (favoritedArticle != null) {
+ updateArticleFavorited(false, favoritedArticle)
+ }
+ } else {
+ if (favoritedArticle != null) {
+ updateArticleFavorited(true, favoritedArticle)
+ }
+ }
+ },
+ modifier = Modifier
+ .clip(CircleShape)
+ .shadow(elevation = 4.dp, clip = true)
+ .background(Color.White)
+ .size(50.dp)
+ ) {
+ Icon(
+ Icons.Default.Favorite,
+ tint = if (isArticleFavorite) Color.Red else Color.LightGray,
+ contentDescription = null
+ )
+ }
+ }
+ }
+
+ Spacer(modifier = Modifier.height(16.dp))
+
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = 16.dp)
+ .padding(bottom = 20.dp),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ result?.get(0)?.description?.let {
+ HtmlText(
+ html = it,
+ textStyle = MaterialTheme.typography.bodyMedium.copy(
+ color = Color.Black,
+ fontWeight = FontWeight.Normal,
+ fontSize = 14.sp
+ )
+ )
+ }
+
+ Spacer(modifier = Modifier.height(36.dp))
+
+ val plainText = Html.fromHtml(result?.get(0)?.description).toString()
+
+ DefaultButton(
+ contentText = "Share",
+ onClick = {
+ val intent = Intent(Intent.ACTION_SEND)
+ intent.type = "text/plain"
+ intent.putExtra(Intent.EXTRA_SUBJECT, result?.get(0)?.name)
+ intent.putExtra(Intent.EXTRA_TEXT,
+ "$plainText\nSource: Techwaste"
+ )
+ context.startActivity(intent)
+ },
+ modifier = Modifier.width(150.dp)
+ )
+ }
+ }
+ }
+ }
+ }
+
+ Box(
+ modifier = Modifier
+ .matchParentSize()
+ .background(
+ Brush.verticalGradient(
+ colors = listOf(
+ Color.Black.copy(alpha = 0.7f),
+ Color.Transparent
+ ),
+ startY = 0f,
+ endY = 600f
+ )
+ )
+ ) {
+ TransparentTopBar(onClickNavigationIcon = { navigateToArticle() }, pageTitle = "Detail")
+ }
+ }
+ }
+}
+
+@Preview(showBackground = true)
+@Composable
+fun SingleArticleScreenPreview() {
+ TechwasMark02Theme {
+ SingleArticleContent(
+ articleResult = UiState.Loading(),
+ navigateToArticle = {},
+ isArticleFavorited = null,
+ updateArticleFavorited = fun(articleGetFavorited: Boolean,
+ favoriteArticle: FavoriteArticle) {}
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/capstone/techwasmark02/ui/screen/singleArticle/SingleArticleScreenViewModel.kt b/app/src/main/java/com/capstone/techwasmark02/ui/screen/singleArticle/SingleArticleScreenViewModel.kt
new file mode 100644
index 0000000..546742b
--- /dev/null
+++ b/app/src/main/java/com/capstone/techwasmark02/ui/screen/singleArticle/SingleArticleScreenViewModel.kt
@@ -0,0 +1,54 @@
+package com.capstone.techwasmark02.ui.screen.singleArticle
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.capstone.techwasmark02.data.local.database.entity.FavoriteArticleEntity
+import com.capstone.techwasmark02.data.model.FavoriteArticle
+import com.capstone.techwasmark02.data.remote.response.SingleArticleResponse
+import com.capstone.techwasmark02.repository.FavoriteArticleRepository
+import com.capstone.techwasmark02.repository.TechwasArticleRepository
+import com.capstone.techwasmark02.ui.common.UiState
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class SingleArticleScreenViewModel @Inject constructor(
+ private val articleRepository: TechwasArticleRepository,
+ private val favoriteArticleRepository: FavoriteArticleRepository
+): ViewModel() {
+
+ private val _articleResult: MutableStateFlow?> = MutableStateFlow(null)
+ val articleResult = _articleResult.asStateFlow()
+
+ fun getArticleById(id: Int) {
+ _articleResult.value = UiState.Loading()
+ viewModelScope.launch {
+ _articleResult.value = articleRepository.getArticleById(id)
+ }
+ }
+
+ var isArticleFavorited: Flow = favoriteArticleRepository.getFavArticleById(id = 0)
+
+ fun getFavArticleById(id: Int) {
+ isArticleFavorited = favoriteArticleRepository.getFavArticleById(id = id)
+ }
+
+ fun updateArticleFavorited(
+ articleGetFavorited: Boolean,
+ favoriteArticle: FavoriteArticle
+ ) {
+ if (articleGetFavorited) {
+ viewModelScope.launch {
+ favoriteArticleRepository.upsertFavoriteArticle(favoriteArticle)
+ }
+ } else {
+ viewModelScope.launch {
+ favoriteArticleRepository.deleteFavoriteArticle(favoriteArticle)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/capstone/techwasmark02/ui/screen/splashScreen/SplashScreen.kt b/app/src/main/java/com/capstone/techwasmark02/ui/screen/splashScreen/SplashScreen.kt
new file mode 100644
index 0000000..51d08f8
--- /dev/null
+++ b/app/src/main/java/com/capstone/techwasmark02/ui/screen/splashScreen/SplashScreen.kt
@@ -0,0 +1,134 @@
+package com.capstone.techwasmark02.ui.screen.splashScreen
+
+import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Row
+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.material3.MaterialTheme
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.alpha
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.text.font.FontWeight
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.hilt.navigation.compose.hiltViewModel
+import androidx.navigation.NavHostController
+import com.capstone.techwasmark02.R
+import com.capstone.techwasmark02.ui.navigation.Screen
+import com.capstone.techwasmark02.ui.theme.Green77
+import com.capstone.techwasmark02.ui.theme.TechwasMark02Theme
+import kotlinx.coroutines.delay
+
+@Composable
+fun SplashScreen(
+ viewModel: SplashScreenViewModel = hiltViewModel(),
+ navController: NavHostController
+) {
+
+ val userSession by viewModel.userSessionState.collectAsState()
+
+ var animationState by remember {
+ mutableStateOf(false)
+ }
+
+ val alphaAnim = animateFloatAsState(
+ targetValue = if (animationState) 1f else 0f,
+ animationSpec = tween(
+ durationMillis = 1000 // 3 sec
+ )
+ )
+
+ LaunchedEffect(key1 = true) {
+ animationState = true
+ delay(2000)
+ // navController.navigate(Screen.Home.route)
+ }
+
+ LaunchedEffect(key1 = userSession) {
+ delay(2000)
+
+ if (userSession != null) {
+ if (userSession!!.userLoginToken.accessToken == "") {
+ navController.navigate(Screen.OnBoarding.route)
+ } else {
+ navController.navigate("${Screen.Main.route}/0")
+ }
+ }
+ }
+
+ SplashContent(alpha = alphaAnim.value)
+}
+
+@Composable
+fun SplashContent(alpha: Float) {
+ Box(
+ modifier = Modifier
+ .fillMaxSize()
+ .alpha(alpha)
+ .background(color = Green77),
+ contentAlignment = Alignment.Center
+ ) {
+ Row(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(horizontal = 16.dp),
+ horizontalArrangement = Arrangement.Center,
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ Image(
+ painter = painterResource(id = R.drawable.img_logo_onboarding_nooutline),
+ contentDescription = null,
+ modifier = Modifier
+ .width(57.27.dp)
+ .height(63.9.dp)
+ )
+ Text(
+ text = "Techwaste",
+ style = MaterialTheme.typography.headlineLarge,
+ fontWeight = FontWeight.Bold,
+ color = Color.White,
+ modifier = Modifier
+ .padding(start = 5.24.dp)
+ )
+ }
+ }
+
+ Box(
+ modifier = Modifier.fillMaxSize(),
+ contentAlignment = Alignment.BottomCenter
+ ) {
+ Text(
+ text = "Copyright © 2023",
+ color= Color.White,
+ style = MaterialTheme.typography.labelMedium,
+ modifier = Modifier
+ .padding(bottom = 32.dp)
+ )
+ }
+}
+
+@Preview(showBackground = true)
+@Composable
+fun SplashScreenPreview() {
+ TechwasMark02Theme {
+ SplashContent(alpha = 1f)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/capstone/techwasmark02/ui/screen/splashScreen/SplashScreenViewModel.kt b/app/src/main/java/com/capstone/techwasmark02/ui/screen/splashScreen/SplashScreenViewModel.kt
new file mode 100644
index 0000000..cecc022
--- /dev/null
+++ b/app/src/main/java/com/capstone/techwasmark02/ui/screen/splashScreen/SplashScreenViewModel.kt
@@ -0,0 +1,41 @@
+package com.capstone.techwasmark02.ui.screen.splashScreen
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.capstone.techwasmark02.common.Resource
+import com.capstone.techwasmark02.data.model.UserSession
+import com.capstone.techwasmark02.data.remote.response.Token
+import com.capstone.techwasmark02.data.remote.response.UserId
+import com.capstone.techwasmark02.repository.PreferencesRepository
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class SplashScreenViewModel @Inject constructor(
+ private val preferencesRepository: PreferencesRepository
+): ViewModel() {
+
+ private val _userSessionState: MutableStateFlow = MutableStateFlow(null)
+ val userSessionState = _userSessionState.asStateFlow()
+
+ init {
+ viewModelScope.launch {
+ val result = preferencesRepository.getActiveSession()
+ when(result) {
+ is Resource.Error -> {
+ _userSessionState.value = UserSession(
+ userLoginToken = Token(accessToken = ""),
+ userNameId = UserId(username = "", id = 0)
+ )
+ }
+ is Resource.Success -> {
+ _userSessionState.value = result.data
+ }
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/capstone/techwasmark02/ui/theme/Color.kt b/app/src/main/java/com/capstone/techwasmark02/ui/theme/Color.kt
index 8678a3f..a569a46 100644
--- a/app/src/main/java/com/capstone/techwasmark02/ui/theme/Color.kt
+++ b/app/src/main/java/com/capstone/techwasmark02/ui/theme/Color.kt
@@ -3,10 +3,17 @@ package com.capstone.techwasmark02.ui.theme
import androidx.compose.ui.graphics.Color
val Green35 = Color(0xff5bb915)
+val Green77 = Color(0xFF54B800)
val Black20 = Color(0xff323232)
val Black12 = Color(0xff1F1F1F)
val Lime56 = Color(0xff9ED54A)
-val Mist97 = Color(0xffF1EEFF)
\ No newline at end of file
+val Mist97 = Color(0xffF1EEFF)
+val purple = Color(0xff8385E4)
+val Yellow77 = Color(0xFFFFDF5D)
+val sakura = Color(0xFFF5A37B)
+val gray = Color(0xFFD9D9D9)
+val yellow = Color(0xFFFEBC1F)
+val red = Color(0xFFCD230F)
\ No newline at end of file
diff --git a/app/src/main/java/com/capstone/techwasmark02/ui/theme/Theme.kt b/app/src/main/java/com/capstone/techwasmark02/ui/theme/Theme.kt
index 0f65185..f75438b 100644
--- a/app/src/main/java/com/capstone/techwasmark02/ui/theme/Theme.kt
+++ b/app/src/main/java/com/capstone/techwasmark02/ui/theme/Theme.kt
@@ -4,7 +4,6 @@ import android.app.Activity
import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.dynamicDarkColorScheme
import androidx.compose.material3.dynamicLightColorScheme
import androidx.compose.material3.lightColorScheme
@@ -34,7 +33,7 @@ private val LightColorScheme = lightColorScheme(
inverseSurface = Black12,
inverseOnSurface = Color.White,
- background = Mist97,
+ background = Color.White,
onBackground = Black20
)
diff --git a/app/src/main/java/com/capstone/techwasmark02/ui/theme/Type.kt b/app/src/main/java/com/capstone/techwasmark02/ui/theme/Type.kt
index 3ece5d6..39d1932 100644
--- a/app/src/main/java/com/capstone/techwasmark02/ui/theme/Type.kt
+++ b/app/src/main/java/com/capstone/techwasmark02/ui/theme/Type.kt
@@ -21,6 +21,13 @@ val poppins = FontFamily(
// Set of Material typography styles to start with
val Typography = Typography(
+ headlineMedium = TextStyle(
+ fontFamily = poppins,
+ fontWeight = FontWeight.SemiBold,
+ fontSize = 28.sp,
+ lineHeight = 42.sp,
+ letterSpacing = 0.1.sp
+ ),
headlineSmall = TextStyle(
fontFamily = poppins,
fontWeight = FontWeight.Bold,
@@ -28,13 +35,27 @@ val Typography = Typography(
lineHeight = 32.sp,
letterSpacing = 0.sp
),
- titleSmall = TextStyle(
+ titleLarge = TextStyle(
+ fontFamily = poppins,
+ fontWeight = FontWeight.Medium,
+ fontSize = 22.sp,
+ lineHeight = 28.sp,
+ letterSpacing = 0.sp
+ ),
+ titleMedium = TextStyle(
fontFamily = poppins,
fontWeight = FontWeight.Bold,
fontSize = 20.sp,
- lineHeight = 14.sp,
+ lineHeight = 24.sp,
letterSpacing = 0.1.sp
),
+ titleSmall = TextStyle(
+ fontFamily = poppins,
+ fontWeight = FontWeight.Bold,
+ fontSize = 14.sp,
+ lineHeight = 24.sp,
+ letterSpacing = 0.15.sp
+ ),
labelLarge = TextStyle(
fontFamily = poppins,
fontWeight = FontWeight.Bold,
diff --git a/app/src/main/java/com/capstone/techwasmark02/utils/html/FromHtml.kt b/app/src/main/java/com/capstone/techwasmark02/utils/html/FromHtml.kt
new file mode 100644
index 0000000..ae853fd
--- /dev/null
+++ b/app/src/main/java/com/capstone/techwasmark02/utils/html/FromHtml.kt
@@ -0,0 +1,43 @@
+package com.capstone.techwasmark02.utils.html
+
+import android.content.Context
+import android.graphics.Color
+import android.graphics.Typeface
+import android.text.Spannable
+import android.text.TextPaint
+import android.text.style.ForegroundColorSpan
+import android.text.style.StyleSpan
+import android.text.style.URLSpan
+import android.text.style.UnderlineSpan
+import androidx.core.content.res.ResourcesCompat
+import androidx.core.text.HtmlCompat
+import com.capstone.techwasmark02.R
+
+fun fromHtml(context: Context, html: String): Spannable = parse(html).apply {
+ removeLinksUnderline()
+ styleBold(context)
+}
+
+private fun parse(html: String): Spannable =
+ (HtmlCompat.fromHtml(html, HtmlCompat.FROM_HTML_MODE_COMPACT) as Spannable)
+
+private fun Spannable.removeLinksUnderline() {
+ for (s in getSpans(0, length, URLSpan::class.java)) {
+ setSpan(object : UnderlineSpan() {
+ override fun updateDrawState(tp: TextPaint) {
+ tp.isUnderlineText = false
+ }
+ }, getSpanStart(s), getSpanEnd(s), 0)
+ }
+}
+
+private fun Spannable.styleBold(context: Context) {
+ val bold = ResourcesCompat.getFont(context, R.font.poppins_semi_bold)!!
+ for (s in getSpans(0, length, StyleSpan::class.java)) {
+ if (s.style == Typeface.BOLD) {
+ setSpan(ForegroundColorSpan(Color.BLACK), getSpanStart(s), getSpanEnd(s), 0)
+ setSpan(bold.getTypefaceSpan(), getSpanStart(s), getSpanEnd(s), 0)
+ }
+ }
+}
+
diff --git a/app/src/main/java/com/capstone/techwasmark02/utils/html/TypeFaceSpanCompat.kt b/app/src/main/java/com/capstone/techwasmark02/utils/html/TypeFaceSpanCompat.kt
new file mode 100644
index 0000000..8c6f25c
--- /dev/null
+++ b/app/src/main/java/com/capstone/techwasmark02/utils/html/TypeFaceSpanCompat.kt
@@ -0,0 +1,29 @@
+package com.capstone.techwasmark02.utils.html
+
+import android.annotation.TargetApi
+import android.graphics.Typeface
+import android.os.Build
+import android.text.TextPaint
+import android.text.style.MetricAffectingSpan
+import android.text.style.TypefaceSpan
+
+fun Typeface.getTypefaceSpan(): MetricAffectingSpan =
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
+ typefaceSpanCompatV28(this)
+ } else {
+ CustomTypefaceSpan(this)
+ }
+
+@TargetApi(Build.VERSION_CODES.P)
+private fun typefaceSpanCompatV28(typeface: Typeface) = TypefaceSpan(typeface)
+
+private class CustomTypefaceSpan(private val typeface: Typeface?) : MetricAffectingSpan() {
+
+ override fun updateDrawState(paint: TextPaint) {
+ paint.typeface = typeface
+ }
+
+ override fun updateMeasureState(paint: TextPaint) {
+ paint.typeface = typeface
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/drawable/forum_box.webp b/app/src/main/res/drawable/forum_box.webp
new file mode 100644
index 0000000..369fde8
Binary files /dev/null and b/app/src/main/res/drawable/forum_box.webp differ
diff --git a/app/src/main/res/drawable/forum_chat.png b/app/src/main/res/drawable/forum_chat.png
new file mode 100644
index 0000000..85ac970
Binary files /dev/null and b/app/src/main/res/drawable/forum_chat.png differ
diff --git a/app/src/main/res/drawable/ic_arrow_down.xml b/app/src/main/res/drawable/ic_arrow_down.xml
new file mode 100644
index 0000000..e55596d
--- /dev/null
+++ b/app/src/main/res/drawable/ic_arrow_down.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_arrow_left.xml b/app/src/main/res/drawable/ic_arrow_left.xml
new file mode 100644
index 0000000..7d12a80
--- /dev/null
+++ b/app/src/main/res/drawable/ic_arrow_left.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_arrow_right.xml b/app/src/main/res/drawable/ic_arrow_right.xml
new file mode 100644
index 0000000..e011dbc
--- /dev/null
+++ b/app/src/main/res/drawable/ic_arrow_right.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_arrow_up.xml b/app/src/main/res/drawable/ic_arrow_up.xml
new file mode 100644
index 0000000..0b0eae3
--- /dev/null
+++ b/app/src/main/res/drawable/ic_arrow_up.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_camera_fill.xml b/app/src/main/res/drawable/ic_camera_fill.xml
new file mode 100644
index 0000000..43a4fd5
--- /dev/null
+++ b/app/src/main/res/drawable/ic_camera_fill.xml
@@ -0,0 +1,6 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_center_focus.xml b/app/src/main/res/drawable/ic_center_focus.xml
new file mode 100644
index 0000000..e215d44
--- /dev/null
+++ b/app/src/main/res/drawable/ic_center_focus.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_chat_buble.xml b/app/src/main/res/drawable/ic_chat_buble.xml
new file mode 100644
index 0000000..3cf25bb
--- /dev/null
+++ b/app/src/main/res/drawable/ic_chat_buble.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_create.xml b/app/src/main/res/drawable/ic_create.xml
new file mode 100644
index 0000000..0dfda73
--- /dev/null
+++ b/app/src/main/res/drawable/ic_create.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_fill_notification.xml b/app/src/main/res/drawable/ic_fill_notification.xml
new file mode 100644
index 0000000..1d038a4
--- /dev/null
+++ b/app/src/main/res/drawable/ic_fill_notification.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_gallery_big.xml b/app/src/main/res/drawable/ic_gallery_big.xml
new file mode 100644
index 0000000..6b87b62
--- /dev/null
+++ b/app/src/main/res/drawable/ic_gallery_big.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_language.xml b/app/src/main/res/drawable/ic_language.xml
new file mode 100644
index 0000000..a5061ae
--- /dev/null
+++ b/app/src/main/res/drawable/ic_language.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml
index 07d5da9..ca3826a 100644
--- a/app/src/main/res/drawable/ic_launcher_background.xml
+++ b/app/src/main/res/drawable/ic_launcher_background.xml
@@ -1,170 +1,74 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ xmlns:android="http://schemas.android.com/apk/res/android">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_location_on.xml b/app/src/main/res/drawable/ic_location_on.xml
new file mode 100644
index 0000000..dd12113
--- /dev/null
+++ b/app/src/main/res/drawable/ic_location_on.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_logout.xml b/app/src/main/res/drawable/ic_logout.xml
new file mode 100644
index 0000000..bceb2ec
--- /dev/null
+++ b/app/src/main/res/drawable/ic_logout.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_menu_book.xml b/app/src/main/res/drawable/ic_menu_book.xml
new file mode 100644
index 0000000..21e9852
--- /dev/null
+++ b/app/src/main/res/drawable/ic_menu_book.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_my_location.xml b/app/src/main/res/drawable/ic_my_location.xml
new file mode 100644
index 0000000..11fdbe5
--- /dev/null
+++ b/app/src/main/res/drawable/ic_my_location.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_question_mark.xml b/app/src/main/res/drawable/ic_question_mark.xml
new file mode 100644
index 0000000..1d4bf08
--- /dev/null
+++ b/app/src/main/res/drawable/ic_question_mark.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_search.xml b/app/src/main/res/drawable/ic_search.xml
new file mode 100644
index 0000000..a5687c6
--- /dev/null
+++ b/app/src/main/res/drawable/ic_search.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_sell.xml b/app/src/main/res/drawable/ic_sell.xml
new file mode 100644
index 0000000..2b8e90e
--- /dev/null
+++ b/app/src/main/res/drawable/ic_sell.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_settings.xml b/app/src/main/res/drawable/ic_settings.xml
new file mode 100644
index 0000000..298a5a1
--- /dev/null
+++ b/app/src/main/res/drawable/ic_settings.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_shield.xml b/app/src/main/res/drawable/ic_shield.xml
new file mode 100644
index 0000000..e49a1f2
--- /dev/null
+++ b/app/src/main/res/drawable/ic_shield.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_trash_outline.xml b/app/src/main/res/drawable/ic_trash_outline.xml
new file mode 100644
index 0000000..d18155d
--- /dev/null
+++ b/app/src/main/res/drawable/ic_trash_outline.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/img_bg_catalog_card.png b/app/src/main/res/drawable/img_bg_catalog_card.png
new file mode 100644
index 0000000..c47d450
Binary files /dev/null and b/app/src/main/res/drawable/img_bg_catalog_card.png differ
diff --git a/app/src/main/res/drawable/img_bg_green.png b/app/src/main/res/drawable/img_bg_green.png
new file mode 100644
index 0000000..a3d4711
Binary files /dev/null and b/app/src/main/res/drawable/img_bg_green.png differ
diff --git a/app/src/main/res/drawable/img_bg_green_large.webp b/app/src/main/res/drawable/img_bg_green_large.webp
new file mode 100644
index 0000000..b6084ed
Binary files /dev/null and b/app/src/main/res/drawable/img_bg_green_large.webp differ
diff --git a/app/src/main/res/drawable/img_bg_purple.png b/app/src/main/res/drawable/img_bg_purple.png
new file mode 100644
index 0000000..b01cacb
Binary files /dev/null and b/app/src/main/res/drawable/img_bg_purple.png differ
diff --git a/app/src/main/res/drawable/img_bg_sakura.png b/app/src/main/res/drawable/img_bg_sakura.png
new file mode 100644
index 0000000..b016ffd
Binary files /dev/null and b/app/src/main/res/drawable/img_bg_sakura.png differ
diff --git a/app/src/main/res/drawable/img_bg_signup.webp b/app/src/main/res/drawable/img_bg_signup.webp
new file mode 100644
index 0000000..3a87dcf
Binary files /dev/null and b/app/src/main/res/drawable/img_bg_signup.webp differ
diff --git a/app/src/main/res/drawable/img_bg_singin.webp b/app/src/main/res/drawable/img_bg_singin.webp
new file mode 100644
index 0000000..afb9b7f
Binary files /dev/null and b/app/src/main/res/drawable/img_bg_singin.webp differ
diff --git a/app/src/main/res/drawable/img_feature_article_box.webp b/app/src/main/res/drawable/img_feature_article_box.webp
new file mode 100644
index 0000000..af75c0a
Binary files /dev/null and b/app/src/main/res/drawable/img_feature_article_box.webp differ
diff --git a/app/src/main/res/drawable/img_feature_article_illustration.webp b/app/src/main/res/drawable/img_feature_article_illustration.webp
new file mode 100644
index 0000000..b07b08b
Binary files /dev/null and b/app/src/main/res/drawable/img_feature_article_illustration.webp differ
diff --git a/app/src/main/res/drawable/img_feature_detect_box.webp b/app/src/main/res/drawable/img_feature_detect_box.webp
new file mode 100644
index 0000000..bd102c4
Binary files /dev/null and b/app/src/main/res/drawable/img_feature_detect_box.webp differ
diff --git a/app/src/main/res/drawable/img_feature_detect_frame.webp b/app/src/main/res/drawable/img_feature_detect_frame.webp
new file mode 100644
index 0000000..bb89bc1
Binary files /dev/null and b/app/src/main/res/drawable/img_feature_detect_frame.webp differ
diff --git a/app/src/main/res/drawable/img_feature_detect_illustration.webp b/app/src/main/res/drawable/img_feature_detect_illustration.webp
new file mode 100644
index 0000000..1bd9831
Binary files /dev/null and b/app/src/main/res/drawable/img_feature_detect_illustration.webp differ
diff --git a/app/src/main/res/drawable/img_feature_forum_box.webp b/app/src/main/res/drawable/img_feature_forum_box.webp
new file mode 100644
index 0000000..369fde8
Binary files /dev/null and b/app/src/main/res/drawable/img_feature_forum_box.webp differ
diff --git a/app/src/main/res/drawable/img_feature_forum_illustration.webp b/app/src/main/res/drawable/img_feature_forum_illustration.webp
new file mode 100644
index 0000000..7fd2d63
Binary files /dev/null and b/app/src/main/res/drawable/img_feature_forum_illustration.webp differ
diff --git a/app/src/main/res/drawable/img_forum_hp_nyala.webp b/app/src/main/res/drawable/img_forum_hp_nyala.webp
new file mode 100644
index 0000000..85358aa
Binary files /dev/null and b/app/src/main/res/drawable/img_forum_hp_nyala.webp differ
diff --git a/app/src/main/res/drawable/img_forum_laptop_bekas.webp b/app/src/main/res/drawable/img_forum_laptop_bekas.webp
new file mode 100644
index 0000000..df8e4f7
Binary files /dev/null and b/app/src/main/res/drawable/img_forum_laptop_bekas.webp differ
diff --git a/app/src/main/res/drawable/img_logo_onboarding.webp b/app/src/main/res/drawable/img_logo_onboarding.webp
new file mode 100644
index 0000000..51c97d5
Binary files /dev/null and b/app/src/main/res/drawable/img_logo_onboarding.webp differ
diff --git a/app/src/main/res/drawable/img_logo_onboarding_nooutline.webp b/app/src/main/res/drawable/img_logo_onboarding_nooutline.webp
new file mode 100644
index 0000000..86f926e
Binary files /dev/null and b/app/src/main/res/drawable/img_logo_onboarding_nooutline.webp differ
diff --git a/app/src/main/res/drawable/img_logo_onboarding_nooutline_green.webp b/app/src/main/res/drawable/img_logo_onboarding_nooutline_green.webp
new file mode 100644
index 0000000..3c1036b
Binary files /dev/null and b/app/src/main/res/drawable/img_logo_onboarding_nooutline_green.webp differ
diff --git a/app/src/main/res/drawable/img_onboarding_ripple_peach_left.webp b/app/src/main/res/drawable/img_onboarding_ripple_peach_left.webp
new file mode 100644
index 0000000..27433c9
Binary files /dev/null and b/app/src/main/res/drawable/img_onboarding_ripple_peach_left.webp differ
diff --git a/app/src/main/res/drawable/img_onboarding_ripple_peach_right.webp b/app/src/main/res/drawable/img_onboarding_ripple_peach_right.webp
new file mode 100644
index 0000000..ff148c9
Binary files /dev/null and b/app/src/main/res/drawable/img_onboarding_ripple_peach_right.webp differ
diff --git a/app/src/main/res/drawable/img_onboarding_ripple_purple_left.webp b/app/src/main/res/drawable/img_onboarding_ripple_purple_left.webp
new file mode 100644
index 0000000..819fddd
Binary files /dev/null and b/app/src/main/res/drawable/img_onboarding_ripple_purple_left.webp differ
diff --git a/app/src/main/res/drawable/img_onboarding_ripple_purple_right.webp b/app/src/main/res/drawable/img_onboarding_ripple_purple_right.webp
new file mode 100644
index 0000000..e76bb4b
Binary files /dev/null and b/app/src/main/res/drawable/img_onboarding_ripple_purple_right.webp differ
diff --git a/app/src/main/res/drawable/img_profile_ripple_green.webp b/app/src/main/res/drawable/img_profile_ripple_green.webp
new file mode 100644
index 0000000..6c03f03
Binary files /dev/null and b/app/src/main/res/drawable/img_profile_ripple_green.webp differ
diff --git a/app/src/main/res/drawable/img_profile_ripple_green_reverse.webp b/app/src/main/res/drawable/img_profile_ripple_green_reverse.webp
new file mode 100644
index 0000000..0e7dcd3
Binary files /dev/null and b/app/src/main/res/drawable/img_profile_ripple_green_reverse.webp differ
diff --git a/app/src/main/res/drawable/img_user_1.webp b/app/src/main/res/drawable/img_user_1.webp
new file mode 100644
index 0000000..6cec2ad
Binary files /dev/null and b/app/src/main/res/drawable/img_user_1.webp differ
diff --git a/app/src/main/res/drawable/img_user_2.webp b/app/src/main/res/drawable/img_user_2.webp
new file mode 100644
index 0000000..2258acd
Binary files /dev/null and b/app/src/main/res/drawable/img_user_2.webp differ
diff --git a/app/src/main/res/drawable/img_user_3.webp b/app/src/main/res/drawable/img_user_3.webp
new file mode 100644
index 0000000..a5f1877
Binary files /dev/null and b/app/src/main/res/drawable/img_user_3.webp differ
diff --git a/app/src/main/res/drawable/img_user_4.webp b/app/src/main/res/drawable/img_user_4.webp
new file mode 100644
index 0000000..d9dc169
Binary files /dev/null and b/app/src/main/res/drawable/img_user_4.webp differ
diff --git a/app/src/main/res/drawable/logo_techwase.png b/app/src/main/res/drawable/logo_techwase.png
new file mode 100644
index 0000000..7e42bd9
Binary files /dev/null and b/app/src/main/res/drawable/logo_techwase.png differ
diff --git a/app/src/main/res/drawable/logo_techwaste.png b/app/src/main/res/drawable/logo_techwaste.png
new file mode 100644
index 0000000..ff899fe
Binary files /dev/null and b/app/src/main/res/drawable/logo_techwaste.png differ
diff --git a/app/src/main/res/drawable/trash_bucket.png b/app/src/main/res/drawable/trash_bucket.png
new file mode 100644
index 0000000..d75895d
Binary files /dev/null and b/app/src/main/res/drawable/trash_bucket.png differ
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
index 6f3b755..ac2225b 100644
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -1,6 +1,7 @@
-
-
-
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
index 6f3b755..ac2225b 100644
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -1,6 +1,7 @@
-
-
-
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..4b41879
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp
deleted file mode 100644
index c209e78..0000000
Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher.webp and /dev/null differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..ec560b0
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 0000000..427b360
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp
deleted file mode 100644
index b2dfe3d..0000000
Binary files a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp and /dev/null differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..860be82
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp
deleted file mode 100644
index 4f0f1d6..0000000
Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher.webp and /dev/null differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..1289872
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 0000000..44cf692
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
deleted file mode 100644
index 62b611d..0000000
Binary files a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp and /dev/null differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..d86a7cd
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
deleted file mode 100644
index 948a307..0000000
Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp and /dev/null differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..9fce785
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..373dc13
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp
deleted file mode 100644
index 1b9a695..0000000
Binary files a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp and /dev/null differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..7b8ac6e
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
deleted file mode 100644
index 28d4b77..0000000
Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp and /dev/null differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..86b7665
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..596144b
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
deleted file mode 100644
index 9287f50..0000000
Binary files a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp and /dev/null differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..c03c9f3
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
deleted file mode 100644
index aa7d642..0000000
Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp and /dev/null differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..b40c936
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..b5f51ff
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
deleted file mode 100644
index 9126ae3..0000000
Binary files a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp and /dev/null differ
diff --git a/app/src/main/res/values/ic_launcher_background.xml b/app/src/main/res/values/ic_launcher_background.xml
new file mode 100644
index 0000000..c5d5899
--- /dev/null
+++ b/app/src/main/res/values/ic_launcher_background.xml
@@ -0,0 +1,4 @@
+
+
+ #FFFFFF
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 42d30f5..1826127 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -1,3 +1,4 @@
+
- Techwas Mark02
+ Techwaste
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index 92d9f45..928c336 100644
--- a/build.gradle
+++ b/build.gradle
@@ -4,4 +4,5 @@ plugins {
id 'com.android.library' version '8.0.1' apply false
id 'org.jetbrains.kotlin.android' version '1.7.20' apply false
id 'com.google.dagger.hilt.android' version '2.44' apply false
-}
\ No newline at end of file
+ id 'com.google.android.libraries.mapsplatform.secrets-gradle-plugin' version '2.0.1' apply false
+}