diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 17dd8b0..a665174 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -4,7 +4,7 @@ plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.jetbrains.kotlin.android)
alias(libs.plugins.compose.compiler)
- id("kotlin-kapt")
+ id("com.google.devtools.ksp")
id("com.google.dagger.hilt.android")
}
@@ -21,7 +21,7 @@ android {
minSdk = 24
targetSdk = 34
versionCode = 1
- versionName = "1.0.0"
+ versionName = "1.2.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
@@ -47,23 +47,26 @@ android {
jvmTarget = "1.8"
}
buildFeatures {
+ viewBinding = true
compose = true
buildConfig = true
}
- kapt {
- correctErrorTypes = true
- }
}
dependencies {
// Hilt
- implementation (libs.hilt.android)
- kapt(libs.hilt.android.compiler)
- implementation (libs.androidx.hilt.navigation.compose)
+ implementation(libs.hilt.android)
+ ksp(libs.hilt.android.compiler)
+ implementation(libs.androidx.hilt.navigation.compose)
// Glide
- implementation (libs.glide)
- annotationProcessor (libs.glide.compiler)
+ implementation(libs.glide)
+ annotationProcessor(libs.glide.compiler)
+
+ // Room
+ implementation(libs.androidx.room.runtime)
+ ksp(libs.androidx.room.compiler)
+ implementation(libs.androidx.room.ktx)
// Retrofit
implementation(libs.retrofit)
@@ -80,17 +83,32 @@ dependencies {
implementation(libs.androidx.lifecycle.runtime.ktx)
implementation(libs.androidx.activity.compose)
implementation(platform(libs.androidx.compose.bom))
+ implementation(libs.material)
+ implementation(libs.androidx.ui.viewbinding)
+ implementation(libs.androidx.appcompat)
+ implementation(libs.androidx.activity)
+ implementation(libs.androidx.navigation.fragment)
+ implementation(libs.androidx.fragment)
+ implementation(libs.androidx.constraintlayout)
+ implementation(libs.androidx.recyclerview)
implementation(libs.androidx.ui)
implementation(libs.androidx.ui.graphics)
implementation(libs.androidx.ui.tooling.preview)
implementation(libs.androidx.material3)
implementation(libs.androidx.material.icons.extended)
implementation(libs.androidx.navigation.compose)
+
+ testImplementation(libs.kotlinx.coroutines.test)
testImplementation(libs.junit)
+ testImplementation(libs.mockk)
+
+ androidTestImplementation(libs.hilt.android.testing)
+ kspAndroidTest(libs.hilt.compiler)
androidTestImplementation(libs.androidx.junit)
androidTestImplementation(libs.androidx.espresso.core)
androidTestImplementation(platform(libs.androidx.compose.bom))
androidTestImplementation(libs.androidx.ui.test.junit4)
+
debugImplementation(libs.androidx.ui.tooling)
debugImplementation(libs.androidx.ui.test.manifest)
}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 1e71d2a..5181259 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -5,7 +5,7 @@
+ android:name=".presentation.ui.MainActivity"
+ android:exported="true">
+
+
\ No newline at end of file
diff --git a/app/src/main/java/com/towitty/bookreport/data/database/BookReportDatabase.kt b/app/src/main/java/com/towitty/bookreport/data/database/BookReportDatabase.kt
new file mode 100644
index 0000000..f6e9966
--- /dev/null
+++ b/app/src/main/java/com/towitty/bookreport/data/database/BookReportDatabase.kt
@@ -0,0 +1,12 @@
+package com.towitty.bookreport.data.database
+
+import androidx.room.Database
+import androidx.room.RoomDatabase
+import com.towitty.bookreport.data.database.model.TagEntity
+
+
+@Database(entities = [TagEntity::class], version = 1, exportSchema = false)
+abstract class BookReportDatabase : RoomDatabase() {
+
+ abstract fun tagDao(): TagDao
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/towitty/bookreport/data/database/TagDao.kt b/app/src/main/java/com/towitty/bookreport/data/database/TagDao.kt
new file mode 100644
index 0000000..413e169
--- /dev/null
+++ b/app/src/main/java/com/towitty/bookreport/data/database/TagDao.kt
@@ -0,0 +1,28 @@
+package com.towitty.bookreport.data.database
+
+import androidx.room.Dao
+import androidx.room.Delete
+import androidx.room.Insert
+import androidx.room.Query
+import androidx.room.Update
+import com.towitty.bookreport.data.database.model.TagEntity
+import kotlinx.coroutines.flow.Flow
+
+@Dao
+interface TagDao {
+ @Insert
+ suspend fun insertTag(tagEntity: TagEntity)
+
+ @Update
+ suspend fun updateTag(tagEntity: TagEntity)
+
+ @Delete
+ suspend fun deleteTag(tagEntity: TagEntity)
+
+ @Query("SELECT * FROM tags WHERE id = :id")
+ fun getTag(id: Int): Flow
+
+ @Query("SELECT * FROM tags ORDER BY name ASC")
+ fun getAllTags(): Flow>
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/towitty/bookreport/data/database/di/DaosModule.kt b/app/src/main/java/com/towitty/bookreport/data/database/di/DaosModule.kt
new file mode 100644
index 0000000..6219443
--- /dev/null
+++ b/app/src/main/java/com/towitty/bookreport/data/database/di/DaosModule.kt
@@ -0,0 +1,19 @@
+package com.towitty.bookreport.data.database.di
+
+import com.towitty.bookreport.data.database.BookReportDatabase
+import com.towitty.bookreport.data.database.TagDao
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import javax.inject.Singleton
+
+@InstallIn(SingletonComponent::class)
+@Module
+object DaosModule {
+
+ @Singleton
+ @Provides
+ fun provideTagDao(appDatabase: BookReportDatabase): TagDao = appDatabase.tagDao()
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/towitty/bookreport/data/database/di/DatabaseModule.kt b/app/src/main/java/com/towitty/bookreport/data/database/di/DatabaseModule.kt
new file mode 100644
index 0000000..4502567
--- /dev/null
+++ b/app/src/main/java/com/towitty/bookreport/data/database/di/DatabaseModule.kt
@@ -0,0 +1,27 @@
+package com.towitty.bookreport.data.database.di
+
+import android.content.Context
+import androidx.room.Room
+import com.towitty.bookreport.data.database.BookReportDatabase
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.android.qualifiers.ApplicationContext
+import dagger.hilt.components.SingletonComponent
+import javax.inject.Singleton
+
+@InstallIn(SingletonComponent::class)
+@Module
+object DatabaseModule {
+
+ @Singleton
+ @Provides
+ fun provideBookReportDatabase(
+ @ApplicationContext context: Context
+ ): BookReportDatabase = Room.databaseBuilder(
+ context,
+ BookReportDatabase::class.java,
+ "book_report_db"
+ ).build()
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/towitty/bookreport/data/database/model/TagEntity.kt b/app/src/main/java/com/towitty/bookreport/data/database/model/TagEntity.kt
new file mode 100644
index 0000000..f19a0b0
--- /dev/null
+++ b/app/src/main/java/com/towitty/bookreport/data/database/model/TagEntity.kt
@@ -0,0 +1,14 @@
+package com.towitty.bookreport.data.database.model
+
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+
+@Entity(tableName = "tags")
+data class TagEntity(
+ @PrimaryKey(autoGenerate = true)
+ val id:Int,
+ val name:String,
+ val color: Int
+)
+
+val emptyTagEntity = TagEntity(0, "", 0)
diff --git a/app/src/main/java/com/towitty/bookreport/data/network/ApiService.kt b/app/src/main/java/com/towitty/bookreport/data/network/ApiService.kt
index 09c20ca..e706970 100644
--- a/app/src/main/java/com/towitty/bookreport/data/network/ApiService.kt
+++ b/app/src/main/java/com/towitty/bookreport/data/network/ApiService.kt
@@ -1,7 +1,7 @@
package com.towitty.bookreport.data.network
import com.towitty.bookreport.BuildConfig
-import com.towitty.bookreport.model.Book
+import com.towitty.bookreport.data.network.model.Book
import retrofit2.Response
import retrofit2.http.GET
import retrofit2.http.Header
diff --git a/app/src/main/java/com/towitty/bookreport/data/network/BookRemoteDataSource.kt b/app/src/main/java/com/towitty/bookreport/data/network/BookRemoteDataSource.kt
index 5066fdc..e455dea 100644
--- a/app/src/main/java/com/towitty/bookreport/data/network/BookRemoteDataSource.kt
+++ b/app/src/main/java/com/towitty/bookreport/data/network/BookRemoteDataSource.kt
@@ -1,7 +1,10 @@
package com.towitty.bookreport.data.network
import android.util.Log
-import com.towitty.bookreport.model.Book
+import com.towitty.bookreport.data.network.model.Book
+import com.towitty.bookreport.data.network.model.emptyBook
+import com.towitty.bookreport.di.BookReportDispatchers
+import com.towitty.bookreport.di.Dispatcher
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.withContext
import javax.inject.Inject
@@ -11,21 +14,21 @@ import javax.inject.Singleton
@Singleton
class BookRemoteDataSource @Inject constructor(
private val bookApi: ApiService,
- private val ioDispatcher: CoroutineDispatcher
-) {
- suspend fun getBooks(query: String): Book {
+ @Dispatcher(BookReportDispatchers.IO) private val ioDispatcher: CoroutineDispatcher
+) : IBookDataSource {
+ override suspend fun getBook(query: String): Book {
return withContext(ioDispatcher) {
try {
val response = bookApi.getSearchBook(query)
if (response.isSuccessful) {
- response.body() ?: Book(lastBuildDate = "", total = 0, start = 0, display = 0, bookList = emptyList())
+ response.body() ?: emptyBook
} else {
Log.e("BookRemoteDataSource", "code: ${response.code()}")
- Book(lastBuildDate = "", total = 0, start = 0, display = 0, bookList = emptyList())
+ emptyBook
}
} catch (e: Exception) {
Log.e("BookRemoteDataSource", "NetworkException: ${e.message}")
- Book(lastBuildDate = "", total = 0, start = 0, display = 0, bookList = emptyList())
+ emptyBook
}
}
}
diff --git a/app/src/main/java/com/towitty/bookreport/data/network/BookRemoteRepository.kt b/app/src/main/java/com/towitty/bookreport/data/network/BookRemoteRepository.kt
deleted file mode 100644
index 04511ef..0000000
--- a/app/src/main/java/com/towitty/bookreport/data/network/BookRemoteRepository.kt
+++ /dev/null
@@ -1,14 +0,0 @@
-package com.towitty.bookreport.data.network
-
-import com.towitty.bookreport.model.Book
-import javax.inject.Inject
-import javax.inject.Singleton
-
-@Singleton
-class BookRemoteRepository @Inject constructor(
- private val bookRemoteDataSource: BookRemoteDataSource
-) {
- suspend fun searchBooks(query: String): Book {
- return bookRemoteDataSource.getBooks(query)
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/towitty/bookreport/data/network/IBookDataSource.kt b/app/src/main/java/com/towitty/bookreport/data/network/IBookDataSource.kt
new file mode 100644
index 0000000..be79e8c
--- /dev/null
+++ b/app/src/main/java/com/towitty/bookreport/data/network/IBookDataSource.kt
@@ -0,0 +1,7 @@
+package com.towitty.bookreport.data.network
+
+import com.towitty.bookreport.data.network.model.Book
+
+interface IBookDataSource {
+ suspend fun getBook(query: String): Book
+}
diff --git a/app/src/main/java/com/towitty/bookreport/data/network/di/DataSourceModule.kt b/app/src/main/java/com/towitty/bookreport/data/network/di/DataSourceModule.kt
new file mode 100644
index 0000000..e57e58d
--- /dev/null
+++ b/app/src/main/java/com/towitty/bookreport/data/network/di/DataSourceModule.kt
@@ -0,0 +1,16 @@
+package com.towitty.bookreport.data.network.di
+
+import com.towitty.bookreport.data.network.BookRemoteDataSource
+import com.towitty.bookreport.data.network.IBookDataSource
+import dagger.Binds
+import dagger.Module
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+
+@InstallIn(SingletonComponent::class)
+@Module
+abstract class DataSourceModule {
+
+ @Binds
+ abstract fun bindsBookDataSource(bookRemoteDataSource: BookRemoteDataSource): IBookDataSource
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/towitty/bookreport/di/AppModules.kt b/app/src/main/java/com/towitty/bookreport/data/network/di/NetworkModule.kt
similarity index 83%
rename from app/src/main/java/com/towitty/bookreport/di/AppModules.kt
rename to app/src/main/java/com/towitty/bookreport/data/network/di/NetworkModule.kt
index 52b80fb..ba98ea3 100644
--- a/app/src/main/java/com/towitty/bookreport/di/AppModules.kt
+++ b/app/src/main/java/com/towitty/bookreport/data/network/di/NetworkModule.kt
@@ -1,11 +1,10 @@
-package com.towitty.bookreport.di
+package com.towitty.bookreport.data.network.di
import com.towitty.bookreport.data.network.ApiService
import dagger.Module
import dagger.Provides
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
-import kotlinx.coroutines.Dispatchers
import retrofit2.Converter
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
@@ -13,11 +12,7 @@ import javax.inject.Singleton
@InstallIn(SingletonComponent::class)
@Module
-class AppModules {
-
- @Singleton
- @Provides
- fun provideDispatcherIO() = Dispatchers.IO
+object NetworkModule {
@Singleton
@Provides
@@ -37,4 +32,4 @@ class AppModules {
fun provideApiService(
retrofit: Retrofit
): ApiService = retrofit.create(ApiService::class.java)
-}
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/towitty/bookreport/model/Book.kt b/app/src/main/java/com/towitty/bookreport/data/network/model/Book.kt
similarity index 84%
rename from app/src/main/java/com/towitty/bookreport/model/Book.kt
rename to app/src/main/java/com/towitty/bookreport/data/network/model/Book.kt
index 2c035b0..7092128 100644
--- a/app/src/main/java/com/towitty/bookreport/model/Book.kt
+++ b/app/src/main/java/com/towitty/bookreport/data/network/model/Book.kt
@@ -1,4 +1,4 @@
-package com.towitty.bookreport.model
+package com.towitty.bookreport.data.network.model
import com.google.gson.annotations.SerializedName
@@ -33,6 +33,14 @@ data class BookItem(
val description: String,
)
+val emptyBook = Book(
+ lastBuildDate = "",
+ total = 0,
+ start = 0,
+ display = 0,
+ bookList = emptyList()
+)
+
val emptyBookItem = BookItem(
title = "",
link = "",
diff --git a/app/src/main/java/com/towitty/bookreport/data/repository/BookRemoteRepository.kt b/app/src/main/java/com/towitty/bookreport/data/repository/BookRemoteRepository.kt
new file mode 100644
index 0000000..9ec1f80
--- /dev/null
+++ b/app/src/main/java/com/towitty/bookreport/data/repository/BookRemoteRepository.kt
@@ -0,0 +1,19 @@
+package com.towitty.bookreport.data.repository
+
+import com.towitty.bookreport.data.network.IBookDataSource
+import com.towitty.bookreport.data.network.model.BookItem
+import com.towitty.bookreport.data.network.model.emptyBookItem
+import javax.inject.Inject
+
+class BookRemoteRepository @Inject constructor(
+ private val bookRemoteDataSource: IBookDataSource
+) : IBookRepository {
+ private var bookList: List = emptyList()
+
+ override suspend fun searchBooks(query: String): List {
+ bookList = bookRemoteDataSource.getBook(query).bookList
+ return bookList
+ }
+
+ override fun findBookByIsbn(isbn: String): BookItem = bookList.find { it.isbn == isbn } ?: emptyBookItem
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/towitty/bookreport/data/repository/IBookRepository.kt b/app/src/main/java/com/towitty/bookreport/data/repository/IBookRepository.kt
new file mode 100644
index 0000000..0979f3b
--- /dev/null
+++ b/app/src/main/java/com/towitty/bookreport/data/repository/IBookRepository.kt
@@ -0,0 +1,8 @@
+package com.towitty.bookreport.data.repository
+
+import com.towitty.bookreport.data.network.model.BookItem
+
+interface IBookRepository {
+ suspend fun searchBooks(query: String): List
+ fun findBookByIsbn(isbn: String): BookItem
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/towitty/bookreport/data/repository/ITagRepository.kt b/app/src/main/java/com/towitty/bookreport/data/repository/ITagRepository.kt
new file mode 100644
index 0000000..5d6df12
--- /dev/null
+++ b/app/src/main/java/com/towitty/bookreport/data/repository/ITagRepository.kt
@@ -0,0 +1,12 @@
+package com.towitty.bookreport.data.repository
+
+import com.towitty.bookreport.data.database.model.TagEntity
+import kotlinx.coroutines.flow.Flow
+
+interface ITagRepository {
+ suspend fun insertTag(tagEntity: TagEntity)
+ suspend fun updateTag(tagEntity: TagEntity)
+ suspend fun deleteTag(tagEntity: TagEntity)
+ fun getTag(id: Int): Flow
+ fun getAllTags(): Flow>
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/towitty/bookreport/data/repository/TagLocalRepository.kt b/app/src/main/java/com/towitty/bookreport/data/repository/TagLocalRepository.kt
new file mode 100644
index 0000000..71dac3c
--- /dev/null
+++ b/app/src/main/java/com/towitty/bookreport/data/repository/TagLocalRepository.kt
@@ -0,0 +1,19 @@
+package com.towitty.bookreport.data.repository
+
+import com.towitty.bookreport.data.database.TagDao
+import com.towitty.bookreport.data.database.model.TagEntity
+import kotlinx.coroutines.flow.Flow
+import javax.inject.Inject
+
+class TagLocalRepository @Inject constructor(private val tagDao: TagDao) : ITagRepository {
+
+ override suspend fun insertTag(tagEntity: TagEntity) = tagDao.insertTag(tagEntity)
+
+ override suspend fun updateTag(tagEntity: TagEntity) = tagDao.updateTag(tagEntity)
+
+ override suspend fun deleteTag(tagEntity: TagEntity) = tagDao.deleteTag(tagEntity)
+
+ override fun getTag(id: Int): Flow = tagDao.getTag(id)
+
+ override fun getAllTags(): Flow> = tagDao.getAllTags()
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/towitty/bookreport/data/repository/di/RepositoryModule.kt b/app/src/main/java/com/towitty/bookreport/data/repository/di/RepositoryModule.kt
new file mode 100644
index 0000000..aa690ad
--- /dev/null
+++ b/app/src/main/java/com/towitty/bookreport/data/repository/di/RepositoryModule.kt
@@ -0,0 +1,21 @@
+package com.towitty.bookreport.data.repository.di
+
+import com.towitty.bookreport.data.repository.BookRemoteRepository
+import com.towitty.bookreport.data.repository.IBookRepository
+import com.towitty.bookreport.data.repository.ITagRepository
+import com.towitty.bookreport.data.repository.TagLocalRepository
+import dagger.Binds
+import dagger.Module
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+
+@InstallIn(SingletonComponent::class)
+@Module
+abstract class RepositoryModule {
+
+ @Binds
+ abstract fun bindsTagRepository(tagLocalRepository: TagLocalRepository): ITagRepository
+
+ @Binds
+ abstract fun bindsBookRepository(bookRemoteRepository: BookRemoteRepository): IBookRepository
+}
diff --git a/app/src/main/java/com/towitty/bookreport/di/Dispatcher.kt b/app/src/main/java/com/towitty/bookreport/di/Dispatcher.kt
new file mode 100644
index 0000000..5089894
--- /dev/null
+++ b/app/src/main/java/com/towitty/bookreport/di/Dispatcher.kt
@@ -0,0 +1,12 @@
+package com.towitty.bookreport.di
+
+import jakarta.inject.Qualifier
+
+@Qualifier
+@Retention(AnnotationRetention.RUNTIME)
+annotation class Dispatcher(val bookReportDispatcher: BookReportDispatchers)
+
+enum class BookReportDispatchers {
+ Default,
+ IO,
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/towitty/bookreport/di/DispatchersModule.kt b/app/src/main/java/com/towitty/bookreport/di/DispatchersModule.kt
new file mode 100644
index 0000000..3b61076
--- /dev/null
+++ b/app/src/main/java/com/towitty/bookreport/di/DispatchersModule.kt
@@ -0,0 +1,22 @@
+package com.towitty.bookreport.di
+
+import dagger.Module
+import dagger.Provides
+import dagger.hilt.InstallIn
+import dagger.hilt.components.SingletonComponent
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.Dispatchers
+
+@InstallIn(SingletonComponent::class)
+@Module
+object DispatchersModule {
+
+ @Dispatcher(BookReportDispatchers.IO)
+ @Provides
+ fun providesIODispatcher(): CoroutineDispatcher = Dispatchers.IO
+
+ @Dispatcher(BookReportDispatchers.Default)
+ @Provides
+ fun providesDefaultDispatcher(): CoroutineDispatcher = Dispatchers.Default
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/towitty/bookreport/presentation/model/ColorItem.kt b/app/src/main/java/com/towitty/bookreport/presentation/model/ColorItem.kt
new file mode 100644
index 0000000..994469a
--- /dev/null
+++ b/app/src/main/java/com/towitty/bookreport/presentation/model/ColorItem.kt
@@ -0,0 +1,13 @@
+package com.towitty.bookreport.presentation.model
+
+import com.towitty.bookreport.R
+
+enum class ColorItem(val color: Int, var selected: Boolean = false) {
+ RED(R.color.red, true),
+ ORANGE(R.color.orange),
+ YELLOW(R.color.yellow),
+ GREEN(R.color.green),
+ BLUE(R.color.blue),
+ INDIGO(R.color.indigo),
+ VIOLET(R.color.violet)
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/towitty/bookreport/ui/navigation/BottomNavigation.kt b/app/src/main/java/com/towitty/bookreport/presentation/navigation/BottomNavigation.kt
similarity index 65%
rename from app/src/main/java/com/towitty/bookreport/ui/navigation/BottomNavigation.kt
rename to app/src/main/java/com/towitty/bookreport/presentation/navigation/BottomNavigation.kt
index c064bb5..282eac0 100644
--- a/app/src/main/java/com/towitty/bookreport/ui/navigation/BottomNavigation.kt
+++ b/app/src/main/java/com/towitty/bookreport/presentation/navigation/BottomNavigation.kt
@@ -1,35 +1,29 @@
-package com.towitty.bookreport.ui.navigation
+package com.towitty.bookreport.presentation.navigation
import androidx.annotation.StringRes
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.CalendarMonth
-import androidx.compose.material.icons.filled.Home
-import androidx.compose.material.icons.filled.Search
-import androidx.compose.material.icons.filled.Settings
import androidx.compose.material3.Icon
import androidx.compose.material3.NavigationBar
import androidx.compose.material3.NavigationBarItem
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
-import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
+import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavGraph.Companion.findStartDestination
import androidx.navigation.NavHostController
-import androidx.navigation.compose.currentBackStackEntryAsState
import com.towitty.bookreport.R
+import com.towitty.bookreport.presentation.ui.common.BookReportIcons
enum class BottomNavItem(@StringRes val label: Int, val icon: ImageVector) {
- HOME(R.string.label_home, Icons.Default.Home),
- CALENDAR(R.string.label_calender, Icons.Default.CalendarMonth),
- SEARCH(R.string.label_search, Icons.Default.Search),
- SETTINGS(R.string.label_settings, Icons.Default.Settings)
+ HOME(R.string.label_home, BookReportIcons.Home),
+ CALENDAR(R.string.label_calender, BookReportIcons.CalendarMonth),
+ SEARCH(R.string.label_search, BookReportIcons.Search),
}
@Composable
-fun AppBottomNavigation(navController: NavHostController, modifier: Modifier = Modifier) {
- val navBackStackEntry by navController.currentBackStackEntryAsState()
+fun AppBottomNavigation(navController: NavHostController, navBackStackEntry: NavBackStackEntry?, modifier: Modifier = Modifier) {
+
val currentRoute = navBackStackEntry?.destination?.route
NavigationBar(modifier = modifier) {
diff --git a/app/src/main/java/com/towitty/bookreport/presentation/navigation/Navigation.kt b/app/src/main/java/com/towitty/bookreport/presentation/navigation/Navigation.kt
new file mode 100644
index 0000000..49005eb
--- /dev/null
+++ b/app/src/main/java/com/towitty/bookreport/presentation/navigation/Navigation.kt
@@ -0,0 +1,101 @@
+package com.towitty.bookreport.presentation.navigation
+
+import android.content.Intent
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.navigation.NavHostController
+import androidx.navigation.compose.NavHost
+import androidx.navigation.compose.composable
+import com.towitty.bookreport.data.database.model.TagEntity
+import com.towitty.bookreport.data.network.model.BookItem
+import com.towitty.bookreport.data.network.model.emptyBookItem
+import com.towitty.bookreport.presentation.ui.SettingsActivity
+import com.towitty.bookreport.presentation.ui.screens.bookreport.AddTagScreen
+import com.towitty.bookreport.presentation.ui.screens.bookreport.BookInfoDetailScreen
+import com.towitty.bookreport.presentation.ui.screens.bookreport.BookReportScreen
+import com.towitty.bookreport.presentation.ui.screens.bookreport.BookSearchScreen
+import com.towitty.bookreport.presentation.ui.screens.calendar.CalendarScreen
+import com.towitty.bookreport.presentation.ui.screens.home.HomeScreen
+import com.towitty.bookreport.presentation.ui.screens.search.SearchScreen
+
+@Composable
+fun Navigation(
+ bookListState: State>,
+ addedTagListState: State>,
+ tagListState: State>,
+ findBookByIsbn: (String) -> BookItem,
+ searchBooks: (String) -> Unit,
+ onSaveBookReport: () -> Unit,
+ onRemoveTag: (Int) -> Unit,
+ onAddSelectedTag: (Int) -> Unit,
+ navController: NavHostController,
+ startDestination: String,
+ modifier: Modifier = Modifier,
+) {
+ val bookList: List by bookListState
+
+ NavHost(
+ navController = navController,
+ startDestination = startDestination,
+ modifier = modifier.fillMaxSize()
+ ) {
+ composable(route = BottomNavItem.HOME.name) {
+ HomeScreen(
+ moveSettings = { context ->
+ context.startActivity(Intent(context, SettingsActivity::class.java))
+ }
+ )
+ }
+ composable(route = BottomNavItem.CALENDAR.name) {
+ CalendarScreen()
+ }
+ composable(route = BottomNavItem.SEARCH.name) {
+ SearchScreen()
+ }
+ composable(route = "${Routes.DIRECTLY_BOOK_REPORT}/{isbn}") {
+ val previousRoute = navController.previousBackStackEntry?.destination?.route
+ val isbn = it.arguments?.getString("isbn") ?: ""
+ BookReportScreen(
+ onCancel = { navController.navigateUp() },
+ addedTagListState = addedTagListState,
+ onAddSelectedTag = onAddSelectedTag,
+ onRemoveTag = onRemoveTag,
+ onSaveBookReport = onSaveBookReport,
+ onNavigateAddTag = { navController.navigate(Routes.ADD_TAG) },
+ bookItem = if (previousRoute == "${Routes.BOOK_INFO_DETAIL}/{isbn}") findBookByIsbn(isbn) else emptyBookItem
+ )
+ }
+ composable(route = Routes.BOOK_SEARCH_FOR_BOOK_REPORT) {
+ BookSearchScreen(
+ onNavigateUp = { navController.navigateUp() },
+ searchBook = searchBooks,
+ bookList = bookList,
+ onItemClicked = { isbn ->
+ navController.navigate("${Routes.BOOK_INFO_DETAIL}/$isbn")
+ },
+ title = {/*미사용*/ }
+ )
+ }
+
+ composable(route = Routes.BOOK_INFO_DETAIL + "/{isbn}") {
+ val isbn = it.arguments?.getString("isbn") ?: ""
+ BookInfoDetailScreen(
+ onNavigateUp = { navController.navigateUp() },
+ onSelection = { navController.navigate("${Routes.DIRECTLY_BOOK_REPORT}/$isbn") },
+ bookItem = findBookByIsbn(isbn)
+ )
+ }
+
+ composable(route = Routes.ADD_TAG) {
+ AddTagScreen(
+ onNavigateUp = { navController.navigateUp() },
+ onAddSelectedTag = onAddSelectedTag,
+ onRemoveTag = onRemoveTag,
+ tagListState = tagListState
+ )
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/towitty/bookreport/ui/navigation/Routes.kt b/app/src/main/java/com/towitty/bookreport/presentation/navigation/Routes.kt
similarity index 66%
rename from app/src/main/java/com/towitty/bookreport/ui/navigation/Routes.kt
rename to app/src/main/java/com/towitty/bookreport/presentation/navigation/Routes.kt
index 263f395..d897921 100644
--- a/app/src/main/java/com/towitty/bookreport/ui/navigation/Routes.kt
+++ b/app/src/main/java/com/towitty/bookreport/presentation/navigation/Routes.kt
@@ -1,6 +1,7 @@
-package com.towitty.bookreport.ui.navigation
+package com.towitty.bookreport.presentation.navigation
object Routes {
+ const val ADD_TAG: String = "book_report_add_tag"
const val BOOK_INFO_DETAIL: String = "book_info_detail"
const val BOOK_SEARCH_FOR_BOOK_REPORT: String = "book_report_book_search"
const val DIRECTLY_BOOK_REPORT = "directly_book_report"
diff --git a/app/src/main/java/com/towitty/bookreport/ui/theme/Color.kt b/app/src/main/java/com/towitty/bookreport/presentation/theme/Color.kt
similarity index 83%
rename from app/src/main/java/com/towitty/bookreport/ui/theme/Color.kt
rename to app/src/main/java/com/towitty/bookreport/presentation/theme/Color.kt
index cde1da7..e5efa5b 100644
--- a/app/src/main/java/com/towitty/bookreport/ui/theme/Color.kt
+++ b/app/src/main/java/com/towitty/bookreport/presentation/theme/Color.kt
@@ -1,4 +1,4 @@
-package com.towitty.bookreport.ui.theme
+package com.towitty.bookreport.presentation.theme
import androidx.compose.ui.graphics.Color
diff --git a/app/src/main/java/com/towitty/bookreport/ui/theme/Theme.kt b/app/src/main/java/com/towitty/bookreport/presentation/theme/Theme.kt
similarity index 97%
rename from app/src/main/java/com/towitty/bookreport/ui/theme/Theme.kt
rename to app/src/main/java/com/towitty/bookreport/presentation/theme/Theme.kt
index 232e4bd..f8fa7b0 100644
--- a/app/src/main/java/com/towitty/bookreport/ui/theme/Theme.kt
+++ b/app/src/main/java/com/towitty/bookreport/presentation/theme/Theme.kt
@@ -1,4 +1,4 @@
-package com.towitty.bookreport.ui.theme
+package com.towitty.bookreport.presentation.theme
import android.os.Build
import androidx.compose.foundation.isSystemInDarkTheme
diff --git a/app/src/main/java/com/towitty/bookreport/ui/theme/Type.kt b/app/src/main/java/com/towitty/bookreport/presentation/theme/Type.kt
similarity index 95%
rename from app/src/main/java/com/towitty/bookreport/ui/theme/Type.kt
rename to app/src/main/java/com/towitty/bookreport/presentation/theme/Type.kt
index b72b377..8a6714e 100644
--- a/app/src/main/java/com/towitty/bookreport/ui/theme/Type.kt
+++ b/app/src/main/java/com/towitty/bookreport/presentation/theme/Type.kt
@@ -1,4 +1,4 @@
-package com.towitty.bookreport.ui.theme
+package com.towitty.bookreport.presentation.theme
import androidx.compose.material3.Typography
import androidx.compose.ui.text.TextStyle
diff --git a/app/src/main/java/com/towitty/bookreport/presentation/ui/BookReportViewModel.kt b/app/src/main/java/com/towitty/bookreport/presentation/ui/BookReportViewModel.kt
new file mode 100644
index 0000000..0b2050c
--- /dev/null
+++ b/app/src/main/java/com/towitty/bookreport/presentation/ui/BookReportViewModel.kt
@@ -0,0 +1,66 @@
+package com.towitty.bookreport.presentation.ui
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.towitty.bookreport.data.database.model.TagEntity
+import com.towitty.bookreport.data.network.model.BookItem
+import com.towitty.bookreport.data.repository.BookRemoteRepository
+import com.towitty.bookreport.data.repository.ITagRepository
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class BookReportViewModel @Inject constructor(
+ private val bookRemoteRepository: BookRemoteRepository,
+ private val tagLocalRepository: ITagRepository
+) : ViewModel() {
+ private val _bookList = MutableStateFlow>(emptyList())
+ val bookList: StateFlow> = _bookList
+
+ private val _tagList = MutableStateFlow>(emptyList())
+ val tagList: StateFlow> = _tagList
+
+ private val _addedTagList = MutableStateFlow>(emptyList())
+ val addedTagList: StateFlow> = _addedTagList
+
+ init {
+ getAllTags()
+ }
+
+ fun searchBooks(query: String) {
+ viewModelScope.launch {
+ _bookList.value = bookRemoteRepository.searchBooks(query)
+ }
+ }
+
+ fun findBookByIsbn(isbn: String): BookItem {
+ return bookRemoteRepository.findBookByIsbn(isbn)
+ }
+
+ private fun getAllTags() {
+ viewModelScope.launch {
+ tagLocalRepository.getAllTags().collect { tags ->
+ _tagList.value = tags
+ }
+ }
+ }
+
+ fun addSelectedTag(id: Int) {
+ viewModelScope.launch {
+ tagLocalRepository.getTag(id).collect { tag ->
+ if (_addedTagList.value.none { it.id == tag.id }) {
+ _addedTagList.value += tag
+ }
+ }
+ }
+ }
+
+ fun removeAddedTag(id: Int) {
+ viewModelScope.launch {
+ _addedTagList.value = _addedTagList.value.filter { it.id != id }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/towitty/bookreport/ui/MainActivity.kt b/app/src/main/java/com/towitty/bookreport/presentation/ui/MainActivity.kt
similarity index 50%
rename from app/src/main/java/com/towitty/bookreport/ui/MainActivity.kt
rename to app/src/main/java/com/towitty/bookreport/presentation/ui/MainActivity.kt
index d885755..07fff02 100644
--- a/app/src/main/java/com/towitty/bookreport/ui/MainActivity.kt
+++ b/app/src/main/java/com/towitty/bookreport/presentation/ui/MainActivity.kt
@@ -1,44 +1,58 @@
-package com.towitty.bookreport.ui
+package com.towitty.bookreport.presentation.ui
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
+import androidx.activity.viewModels
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.shape.CircleShape
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.Book
import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.Icon
import androidx.compose.material3.Scaffold
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
+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.Modifier
import androidx.compose.ui.res.stringResource
-import androidx.hilt.navigation.compose.hiltViewModel
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.compose.rememberNavController
import com.towitty.bookreport.R
-import com.towitty.bookreport.ui.components.FabModal
-import com.towitty.bookreport.ui.navigation.AppBottomNavigation
-import com.towitty.bookreport.ui.navigation.BottomNavItem
-import com.towitty.bookreport.ui.navigation.Navigation
-import com.towitty.bookreport.ui.theme.BookReportTheme
+import com.towitty.bookreport.data.database.model.TagEntity
+import com.towitty.bookreport.data.network.model.BookItem
+import com.towitty.bookreport.presentation.navigation.AppBottomNavigation
+import com.towitty.bookreport.presentation.navigation.BottomNavItem
+import com.towitty.bookreport.presentation.navigation.Navigation
+import com.towitty.bookreport.presentation.theme.BookReportTheme
+import com.towitty.bookreport.presentation.ui.common.BookReportIcons
+import com.towitty.bookreport.presentation.ui.components.FabModal
import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
+ private val viewModel: BookReportViewModel by viewModels()
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
BookReportTheme {
- BookReportApp()
+ BookReportApp(
+ bookListState = viewModel.bookList.collectAsState(),
+ addedTagListState = viewModel.addedTagList.collectAsState(),
+ tagListState = viewModel.tagList.collectAsState(),
+ searchBooks = viewModel::searchBooks,
+ onSaveBookReport = { /*TODO*/ },
+ onRemoveTag = viewModel::removeAddedTag,
+ onAddSelectedTag = viewModel::addSelectedTag,
+ findBookByIsbn = viewModel::findBookByIsbn,
+ )
}
}
}
@@ -46,17 +60,28 @@ class MainActivity : ComponentActivity() {
@Composable
fun BookReportApp(
- viewModel: BookReportViewModel = hiltViewModel()
+ bookListState: State>,
+ addedTagListState: State>,
+ tagListState: State>,
+ searchBooks: (String) -> Unit,
+ onSaveBookReport: () -> Unit,
+ onAddSelectedTag: (Int) -> Unit,
+ onRemoveTag: (Int) -> Unit,
+ findBookByIsbn: (String) -> BookItem,
) {
val navController = rememberNavController()
val navBackStackEntry by navController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.destination?.route
var isShowFabModal by remember { mutableStateOf(false) }
-
Scaffold(
modifier = Modifier.fillMaxSize(),
- bottomBar = { AppBottomNavigation(navController = navController) },
+ bottomBar = {
+ AppBottomNavigation(
+ navController = navController,
+ navBackStackEntry = navBackStackEntry
+ )
+ },
floatingActionButton = {
if (currentRoute == BottomNavItem.HOME.name) {
FloatingActionButton(
@@ -64,25 +89,31 @@ fun BookReportApp(
shape = CircleShape
) {
Icon(
- imageVector = Icons.Default.Book,
- contentDescription = stringResource(R.string.home_fab_description)
+ imageVector = BookReportIcons.Book,
+ contentDescription = stringResource(R.string.description_home_fab)
)
}
}
}
) { innerPadding ->
Navigation(
- viewModel = viewModel,
+ bookListState = bookListState,
+ addedTagListState = addedTagListState,
+ tagListState = tagListState,
+ findBookByIsbn = findBookByIsbn,
+ searchBooks = searchBooks,
+ onSaveBookReport = onSaveBookReport,
+ onRemoveTag = onRemoveTag,
+ onAddSelectedTag = onAddSelectedTag,
navController = navController,
startDestination = BottomNavItem.HOME.name,
- modifier = Modifier.padding(innerPadding)
+ modifier = Modifier.padding(innerPadding),
)
if (isShowFabModal) {
FabModal(
onClicked = { navController.navigate(it) },
onDismissRequest = { isShowFabModal = false },
modifier = Modifier.wrapContentHeight()
-
)
}
}
diff --git a/app/src/main/java/com/towitty/bookreport/presentation/ui/SettingsActivity.kt b/app/src/main/java/com/towitty/bookreport/presentation/ui/SettingsActivity.kt
new file mode 100644
index 0000000..52da70a
--- /dev/null
+++ b/app/src/main/java/com/towitty/bookreport/presentation/ui/SettingsActivity.kt
@@ -0,0 +1,15 @@
+package com.towitty.bookreport.presentation.ui
+
+import android.os.Bundle
+import androidx.appcompat.app.AppCompatActivity
+import com.towitty.bookreport.R
+import dagger.hilt.android.AndroidEntryPoint
+
+@AndroidEntryPoint
+class SettingsActivity : AppCompatActivity() {
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity_settings)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/towitty/bookreport/presentation/ui/common/BookReportIcons.kt b/app/src/main/java/com/towitty/bookreport/presentation/ui/common/BookReportIcons.kt
new file mode 100644
index 0000000..b30076c
--- /dev/null
+++ b/app/src/main/java/com/towitty/bookreport/presentation/ui/common/BookReportIcons.kt
@@ -0,0 +1,24 @@
+package com.towitty.bookreport.presentation.ui.common
+
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.ArrowBackIosNew
+import androidx.compose.material.icons.filled.Book
+import androidx.compose.material.icons.filled.CalendarMonth
+import androidx.compose.material.icons.filled.FilterAlt
+import androidx.compose.material.icons.filled.Home
+import androidx.compose.material.icons.filled.Keyboard
+import androidx.compose.material.icons.filled.PhotoCamera
+import androidx.compose.material.icons.filled.Search
+import androidx.compose.material.icons.filled.Settings
+
+object BookReportIcons {
+ val Keyboard = Icons.Default.Keyboard
+ val Search = Icons.Default.Search
+ val Book = Icons.Default.Book
+ val Home = Icons.Default.Home
+ val CalendarMonth = Icons.Default.CalendarMonth
+ val PhotoCamera = Icons.Default.PhotoCamera
+ val ArrowBackIosNew = Icons.Default.ArrowBackIosNew
+ val FilterAlt = Icons.Default.FilterAlt
+ val Settings = Icons.Default.Settings
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/towitty/bookreport/ui/components/Modal.kt b/app/src/main/java/com/towitty/bookreport/presentation/ui/components/Modal.kt
similarity index 88%
rename from app/src/main/java/com/towitty/bookreport/ui/components/Modal.kt
rename to app/src/main/java/com/towitty/bookreport/presentation/ui/components/Modal.kt
index b1d440e..2393a0b 100644
--- a/app/src/main/java/com/towitty/bookreport/ui/components/Modal.kt
+++ b/app/src/main/java/com/towitty/bookreport/presentation/ui/components/Modal.kt
@@ -1,4 +1,4 @@
-package com.towitty.bookreport.ui.components
+package com.towitty.bookreport.presentation.ui.components
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
@@ -9,9 +9,6 @@ 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.material.icons.Icons
-import androidx.compose.material.icons.filled.Keyboard
-import androidx.compose.material.icons.filled.Search
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
@@ -26,7 +23,8 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.unit.dp
import com.towitty.bookreport.R
-import com.towitty.bookreport.ui.navigation.Routes
+import com.towitty.bookreport.presentation.navigation.Routes
+import com.towitty.bookreport.presentation.ui.common.BookReportIcons
@OptIn(ExperimentalMaterial3Api::class)
@Composable
@@ -47,15 +45,15 @@ fun FabModal(
.padding(16.dp),
) {
FabModalSheetItem(
- icon = Icons.Default.Keyboard,
+ icon = BookReportIcons.Keyboard,
label = stringResource(R.string.fab_modal_keyboard),
onClicked = {
onDismissRequest()
- onClicked(Routes.DIRECTLY_BOOK_REPORT)
+ onClicked("${Routes.DIRECTLY_BOOK_REPORT}/")
},
)
FabModalSheetItem(
- icon = Icons.Default.Search,
+ icon = BookReportIcons.Search,
label = stringResource(R.string.fab_modal_book_search),
onClicked = {
onDismissRequest()
diff --git a/app/src/main/java/com/towitty/bookreport/presentation/ui/fragment/SettingsFragment.kt b/app/src/main/java/com/towitty/bookreport/presentation/ui/fragment/SettingsFragment.kt
new file mode 100644
index 0000000..5292a47
--- /dev/null
+++ b/app/src/main/java/com/towitty/bookreport/presentation/ui/fragment/SettingsFragment.kt
@@ -0,0 +1,64 @@
+package com.towitty.bookreport.presentation.ui.fragment
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.ArrayAdapter
+import androidx.fragment.app.Fragment
+import androidx.navigation.fragment.findNavController
+import com.towitty.bookreport.R
+import com.towitty.bookreport.databinding.FragmentSettingsBinding
+
+class SettingsFragment : Fragment() {
+ private var _binding: FragmentSettingsBinding? = null
+ private val binding get() = _binding!!
+
+ private lateinit var settingItems: List
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
+ ): View {
+ super.onCreateView(inflater, container, savedInstanceState)
+ _binding = FragmentSettingsBinding.inflate(inflater, container, false)
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ setNavigationList()
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ _binding = null
+ }
+
+ private fun setNavigationList() {
+ settingItems = listOf(
+ getString(R.string.alarm_setting),
+ getString(R.string.tag_management),
+ getString(R.string.backup_and_sync),
+ getString(R.string.theme),
+ getString(R.string.user_feedback_and_improvement),
+ getString(R.string.reset_settings)
+ )
+
+ binding.lvSettings.adapter = ArrayAdapter(
+ requireContext(), R.layout.item_settings, R.id.tv_settings_item, settingItems
+ )
+
+ binding.lvSettings.setOnItemClickListener { _, _, position, _ ->
+ navigateToFragment(settingItems[position])
+ }
+ }
+
+ private fun navigateToFragment(selected: String) {
+ val action = when (selected) {
+ getString(R.string.tag_management) -> R.id.action_settingsFragment_to_tagManagementFragment
+ else -> R.id.action_settingsFragment_to_tagManagementFragment
+ }
+ findNavController().navigate(action)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/towitty/bookreport/presentation/ui/fragment/addtag/AddTagBottomSheetColorAdapter.kt b/app/src/main/java/com/towitty/bookreport/presentation/ui/fragment/addtag/AddTagBottomSheetColorAdapter.kt
new file mode 100644
index 0000000..e9149b7
--- /dev/null
+++ b/app/src/main/java/com/towitty/bookreport/presentation/ui/fragment/addtag/AddTagBottomSheetColorAdapter.kt
@@ -0,0 +1,46 @@
+package com.towitty.bookreport.presentation.ui.fragment.addtag
+
+import android.annotation.SuppressLint
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.recyclerview.widget.RecyclerView
+import com.towitty.bookreport.databinding.ItemSelectableColorBinding
+import com.towitty.bookreport.presentation.model.ColorItem
+
+class AddTagBottomSheetColorAdapter(private val items: List) :
+ RecyclerView.Adapter() {
+
+ private var selectedColor = items[0]
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ColorViewHolder {
+
+ val binding = ItemSelectableColorBinding.inflate(LayoutInflater.from(parent.context), parent, false)
+ return ColorViewHolder(binding)
+ }
+
+ override fun onBindViewHolder(holder: ColorViewHolder, position: Int) {
+ holder.bind(items[position], position)
+ }
+
+ override fun getItemCount(): Int = items.size
+
+ inner class ColorViewHolder(private val binding: ItemSelectableColorBinding) :
+ RecyclerView.ViewHolder(binding.root) {
+
+ @SuppressLint("NotifyDataSetChanged")
+ fun bind(colorItem: ColorItem, position: Int) {
+ binding.ivColor.setBackgroundResource(colorItem.color)
+
+ binding.ivColor.setOnClickListener {
+ if (selectedColor !== colorItem) {
+ selectedColor.selected = false
+ notifyItemChanged(items.indexOf(selectedColor))
+ colorItem.selected = true
+ selectedColor = colorItem
+ notifyItemChanged(position)
+ }
+ }
+ binding.ivColor.strokeWidth = if (colorItem.selected) 5f else 0f
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/towitty/bookreport/presentation/ui/fragment/addtag/AddTagBottomSheetFragment.kt b/app/src/main/java/com/towitty/bookreport/presentation/ui/fragment/addtag/AddTagBottomSheetFragment.kt
new file mode 100644
index 0000000..ecfa4d4
--- /dev/null
+++ b/app/src/main/java/com/towitty/bookreport/presentation/ui/fragment/addtag/AddTagBottomSheetFragment.kt
@@ -0,0 +1,71 @@
+package com.towitty.bookreport.presentation.ui.fragment.addtag
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.app.activityViewModels
+import androidx.lifecycle.lifecycleScope
+import com.google.android.material.bottomsheet.BottomSheetDialogFragment
+import com.towitty.bookreport.data.database.model.TagEntity
+import com.towitty.bookreport.databinding.FragmentAddTagBottomSheetBinding
+import com.towitty.bookreport.presentation.model.ColorItem
+import dagger.hilt.android.AndroidEntryPoint
+import kotlinx.coroutines.launch
+
+@AndroidEntryPoint
+class AddTagBottomSheetFragment : BottomSheetDialogFragment() {
+
+ private var _binding: FragmentAddTagBottomSheetBinding? = null
+ private val binding get() = _binding!!
+
+ private val viewModel: AddTagViewModel by activityViewModels()
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ super.onCreateView(inflater, container, savedInstanceState)
+ _binding = FragmentAddTagBottomSheetBinding.inflate(inflater, container, false)
+
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ setButtonEvents()
+ setColorAdapter()
+ }
+
+ override fun onDestroyView() {
+ super.onDestroyView()
+ binding.etTagName.text?.clear()
+ _binding = null
+ }
+
+ private fun setButtonEvents() {
+ binding.btnCancel.setOnClickListener {
+ dismiss()
+ }
+
+ binding.btnSave.setOnClickListener {
+ val color = ColorItem.entries.find { it.selected }?.color
+ val tagEntity = TagEntity(
+ id = 0,
+ name = binding.etTagName.text.toString(),
+ color = color ?: ColorItem.RED.color
+ )
+ viewLifecycleOwner.lifecycleScope.launch {
+ viewModel.insertTag(tagEntity)
+ dismiss()
+ }
+
+ }
+ }
+
+ private fun setColorAdapter() {
+ val colorItems = ColorItem.entries.toList()
+ binding.rvColorList.adapter = AddTagBottomSheetColorAdapter(colorItems)
+ }
+}
diff --git a/app/src/main/java/com/towitty/bookreport/presentation/ui/fragment/addtag/AddTagViewModel.kt b/app/src/main/java/com/towitty/bookreport/presentation/ui/fragment/addtag/AddTagViewModel.kt
new file mode 100644
index 0000000..a69793c
--- /dev/null
+++ b/app/src/main/java/com/towitty/bookreport/presentation/ui/fragment/addtag/AddTagViewModel.kt
@@ -0,0 +1,32 @@
+package com.towitty.bookreport.presentation.ui.fragment.addtag
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.towitty.bookreport.data.database.model.TagEntity
+import com.towitty.bookreport.data.repository.ITagRepository
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+@HiltViewModel
+class AddTagViewModel @Inject constructor(
+ private val tagRepository: ITagRepository
+) : ViewModel() {
+
+ private val _tagInserted = MutableStateFlow(false)
+ val tagInserted: StateFlow = _tagInserted.asStateFlow()
+
+ fun insertTag(tagEntity: TagEntity) {
+ viewModelScope.launch {
+ try {
+ tagRepository.insertTag(tagEntity)
+ _tagInserted.emit(true)
+ } catch (e: Exception) {
+ _tagInserted.emit(false)
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/towitty/bookreport/presentation/ui/fragment/tag/TagManagementAdapter.kt b/app/src/main/java/com/towitty/bookreport/presentation/ui/fragment/tag/TagManagementAdapter.kt
new file mode 100644
index 0000000..d2e2dc0
--- /dev/null
+++ b/app/src/main/java/com/towitty/bookreport/presentation/ui/fragment/tag/TagManagementAdapter.kt
@@ -0,0 +1,44 @@
+package com.towitty.bookreport.presentation.ui.fragment.tag
+
+import android.annotation.SuppressLint
+import android.view.LayoutInflater
+import android.view.ViewGroup
+import androidx.core.content.ContextCompat
+import androidx.recyclerview.widget.RecyclerView
+import com.towitty.bookreport.data.database.model.TagEntity
+import com.towitty.bookreport.databinding.ItemTagBinding
+
+class TagManagementAdapter(
+ private val onDeleteClick: (TagEntity) -> Unit
+) : RecyclerView.Adapter() {
+
+ private var tags: List = emptyList()
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TagViewHolder {
+ val binding = ItemTagBinding.inflate(LayoutInflater.from(parent.context), parent, false)
+ return TagViewHolder(binding)
+ }
+
+ override fun onBindViewHolder(holder: TagViewHolder, position: Int) {
+ holder.bind(tags[position])
+ }
+
+ override fun getItemCount(): Int = tags.size
+
+ @SuppressLint("NotifyDataSetChanged")
+ fun setTags(tags: List) {
+ this.tags = tags
+ notifyDataSetChanged()
+ }
+
+ inner class TagViewHolder(private val binding: ItemTagBinding) : RecyclerView.ViewHolder(binding.root) {
+ fun bind(tag: TagEntity) {
+ binding.tvTagName.backgroundTintList = ContextCompat.getColorStateList(binding.root.context, tag.color)
+ binding.tvTagName.text = tag.name
+
+ binding.btnDelete.setOnClickListener {
+ onDeleteClick(tag)
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/towitty/bookreport/presentation/ui/fragment/tag/TagManagementFragment.kt b/app/src/main/java/com/towitty/bookreport/presentation/ui/fragment/tag/TagManagementFragment.kt
new file mode 100644
index 0000000..c00d034
--- /dev/null
+++ b/app/src/main/java/com/towitty/bookreport/presentation/ui/fragment/tag/TagManagementFragment.kt
@@ -0,0 +1,90 @@
+package com.towitty.bookreport.presentation.ui.fragment.tag
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.Toast
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.activityViewModels
+import androidx.fragment.app.viewModels
+import androidx.lifecycle.lifecycleScope
+import androidx.navigation.fragment.findNavController
+import com.towitty.bookreport.R
+import com.towitty.bookreport.databinding.FragmentTagManagementBinding
+import com.towitty.bookreport.presentation.ui.fragment.addtag.AddTagBottomSheetFragment
+import com.towitty.bookreport.presentation.ui.fragment.addtag.AddTagViewModel
+import dagger.hilt.android.AndroidEntryPoint
+
+import kotlinx.coroutines.launch
+
+@AndroidEntryPoint
+class TagManagementFragment : Fragment() {
+ private var _binding: FragmentTagManagementBinding? = null
+ private val binding get() = _binding!!
+
+ private val tagViewModel: TagManagementViewModel by viewModels()
+ private val addTagViewModel: AddTagViewModel by activityViewModels()
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ _binding = FragmentTagManagementBinding.inflate(inflater, container, false)
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ setNavigation()
+ showAddTagBottomSheet()
+ setTagList()
+ observeAddTagStatus()
+ }
+
+ private fun observeAddTagStatus() {
+ viewLifecycleOwner.lifecycleScope.launch {
+ addTagViewModel.tagInserted.collect { isSuccess ->
+ if (isSuccess) {
+ Toast.makeText(
+ requireContext(),
+ getString(R.string.succeeded_to_save_tag),
+ Toast.LENGTH_SHORT
+ ).show()
+ } else {
+ Toast.makeText(
+ requireContext(),
+ getString(R.string.failed_to_save_tag),
+ Toast.LENGTH_SHORT
+ ).show()
+ }
+ }
+ }
+ }
+
+ private fun setTagList() {
+ binding.rvTagList.adapter = TagManagementAdapter { tagViewModel.deleteTag(it) }
+ tagViewModel.getAllTags()
+ // TODO: Tag 크기에 맞게 spanCount 를 변경할 수 있도록 수정
+
+ viewLifecycleOwner.lifecycleScope.launch {
+ tagViewModel.tagList.collect { tags ->
+ (binding.rvTagList.adapter as TagManagementAdapter).setTags(tags)
+ }
+ }
+ }
+
+ private fun setNavigation() {
+ binding.tbTagManagement.setNavigationOnClickListener {
+ findNavController().navigateUp()
+ }
+ }
+
+ private fun showAddTagBottomSheet() {
+ binding.btnAddTag.setOnClickListener {
+ val addTagBottomSheetFragment = AddTagBottomSheetFragment()
+ addTagBottomSheetFragment.show(parentFragmentManager, addTagBottomSheetFragment.tag)
+ }
+ }
+}
diff --git a/app/src/main/java/com/towitty/bookreport/presentation/ui/fragment/tag/TagManagementViewModel.kt b/app/src/main/java/com/towitty/bookreport/presentation/ui/fragment/tag/TagManagementViewModel.kt
new file mode 100644
index 0000000..fe0454f
--- /dev/null
+++ b/app/src/main/java/com/towitty/bookreport/presentation/ui/fragment/tag/TagManagementViewModel.kt
@@ -0,0 +1,47 @@
+package com.towitty.bookreport.presentation.ui.fragment.tag
+
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.towitty.bookreport.data.database.model.TagEntity
+import com.towitty.bookreport.data.database.model.emptyTagEntity
+import com.towitty.bookreport.data.repository.ITagRepository
+import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.launch
+import javax.inject.Inject
+
+
+@HiltViewModel
+class TagManagementViewModel @Inject constructor(
+ private val tagLocalRepository: ITagRepository
+) : ViewModel() {
+
+ private val _tagList = MutableStateFlow>(emptyList())
+ val tagList: StateFlow> = _tagList.asStateFlow()
+
+ fun getTagById(id: Int): TagEntity {
+ var tagEntity: TagEntity? = null
+ viewModelScope.launch {
+ tagLocalRepository.getTag(id).collect { tag ->
+ tagEntity = tag
+ }
+ }
+ return tagEntity ?: emptyTagEntity
+ }
+
+ fun getAllTags() {
+ viewModelScope.launch {
+ tagLocalRepository.getAllTags().collect { tags ->
+ _tagList.value = tags
+ }
+ }
+ }
+
+ fun deleteTag(tagEntity: TagEntity) {
+ viewModelScope.launch {
+ tagLocalRepository.deleteTag(tagEntity)
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/towitty/bookreport/presentation/ui/screens/bookreport/AddTagScreen.kt b/app/src/main/java/com/towitty/bookreport/presentation/ui/screens/bookreport/AddTagScreen.kt
new file mode 100644
index 0000000..a1a014f
--- /dev/null
+++ b/app/src/main/java/com/towitty/bookreport/presentation/ui/screens/bookreport/AddTagScreen.kt
@@ -0,0 +1,111 @@
+package com.towitty.bookreport.presentation.ui.screens.bookreport
+
+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.padding
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material3.CenterAlignedTopAppBar
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.painterResource
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.dp
+import androidx.core.content.ContextCompat
+import com.towitty.bookreport.R
+import com.towitty.bookreport.data.database.model.TagEntity
+import com.towitty.bookreport.presentation.ui.common.BookReportIcons
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun AddTagScreen(
+ onNavigateUp: () -> Unit,
+ onAddSelectedTag: (Int) -> Unit,
+ onRemoveTag: (Int) -> Unit,
+ tagListState: State>
+) {
+ Scaffold(
+ topBar = {
+ CenterAlignedTopAppBar(
+ title = { Text("태그 추가") },
+ navigationIcon = {
+ IconButton(onClick = onNavigateUp) {
+ Icon(
+ BookReportIcons.ArrowBackIosNew,
+ contentDescription = stringResource(id = R.string.description_go_back)
+ )
+ }
+ }
+ )
+ }
+
+ ) { paddingValues ->
+ val tagList by tagListState
+
+ LazyColumn(
+ modifier = Modifier.padding(paddingValues)
+ ) {
+ items(tagList) { tag ->
+ TagItem(
+ tag = tag,
+ onClicked = onAddSelectedTag,
+ onRemoveTag = onRemoveTag
+ )
+ }
+ }
+ }
+}
+
+@Composable
+fun TagItem(
+ tag: TagEntity,
+ icon: Int = -1,
+ onClicked: (Int) -> Unit,
+ onRemoveTag: (Int) -> Unit
+) {
+ Box(
+ modifier = Modifier
+ .padding(4.dp)
+ .clip(RoundedCornerShape(24.dp))
+ .background(Color(ContextCompat.getColor(LocalContext.current, tag.color)))
+ .also {
+ if (icon == -1) {
+ it.clickable {
+ onClicked(tag.id)
+ }
+ }
+ }
+ ) {
+ Row {
+ TextButton(
+ onClick = { onClicked(tag.id) },
+ ) {
+ Text(tag.name,color = Color.White)
+ }
+ if (icon != -1) {
+ IconButton(
+ onClick = { onRemoveTag(tag.id) },
+ ) {
+ Icon(
+ painter = painterResource(id = icon),
+ contentDescription = stringResource(R.string.description_delete_tag)
+ )
+ }
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/com/towitty/bookreport/ui/bookreport/BookInfoDetailScreen.kt b/app/src/main/java/com/towitty/bookreport/presentation/ui/screens/bookreport/BookInfoDetailScreen.kt
similarity index 95%
rename from app/src/main/java/com/towitty/bookreport/ui/bookreport/BookInfoDetailScreen.kt
rename to app/src/main/java/com/towitty/bookreport/presentation/ui/screens/bookreport/BookInfoDetailScreen.kt
index c61dbbe..6575021 100644
--- a/app/src/main/java/com/towitty/bookreport/ui/bookreport/BookInfoDetailScreen.kt
+++ b/app/src/main/java/com/towitty/bookreport/presentation/ui/screens/bookreport/BookInfoDetailScreen.kt
@@ -1,4 +1,4 @@
-package com.towitty.bookreport.ui.bookreport
+package com.towitty.bookreport.presentation.ui.screens.bookreport
import android.graphics.Bitmap
import android.graphics.drawable.Drawable
@@ -19,8 +19,6 @@ 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.ArrowBackIosNew
-import androidx.compose.material.icons.filled.PhotoCamera
import androidx.compose.material.icons.outlined.FavoriteBorder
import androidx.compose.material.icons.twotone.Favorite
import androidx.compose.material3.Card
@@ -51,7 +49,8 @@ import com.bumptech.glide.Glide
import com.bumptech.glide.request.target.CustomTarget
import com.bumptech.glide.request.transition.Transition
import com.towitty.bookreport.R
-import com.towitty.bookreport.model.BookItem
+import com.towitty.bookreport.data.network.model.BookItem
+import com.towitty.bookreport.presentation.ui.common.BookReportIcons
@Composable
fun BookInfoDetailScreen(
@@ -77,7 +76,7 @@ fun BookInfoDetailScreen(
modifier = modifier
) { innerPadding ->
Column(
- modifier = Modifier
+ modifier = Modifier
.padding(innerPadding)
.padding(start = 16.dp, end = 16.dp)
.verticalScroll(rememberScrollState())
@@ -113,7 +112,7 @@ fun BookInfoDetailTopAppbar(onBack: () -> Unit, onSelection: () -> Unit, modifie
title = {},
navigationIcon = {
IconButton(onClick = onBack) {
- Icon(Icons.Default.ArrowBackIosNew, contentDescription = stringResource(R.string.description_go_back))
+ Icon(BookReportIcons.ArrowBackIosNew, contentDescription = stringResource(R.string.description_go_back))
}
},
actions = {
@@ -174,7 +173,7 @@ fun BookInfoImage(
.clip(CircleShape)
) {
Icon(
- imageVector = Icons.Default.PhotoCamera,
+ imageVector = BookReportIcons.PhotoCamera,
contentDescription = stringResource(R.string.description_camera_btn),
tint = Color.White,
modifier = Modifier.size(34.dp)
diff --git a/app/src/main/java/com/towitty/bookreport/ui/bookreport/BookReportScreen.kt b/app/src/main/java/com/towitty/bookreport/presentation/ui/screens/bookreport/BookReportScreen.kt
similarity index 76%
rename from app/src/main/java/com/towitty/bookreport/ui/bookreport/BookReportScreen.kt
rename to app/src/main/java/com/towitty/bookreport/presentation/ui/screens/bookreport/BookReportScreen.kt
index 20a857d..621054f 100644
--- a/app/src/main/java/com/towitty/bookreport/ui/bookreport/BookReportScreen.kt
+++ b/app/src/main/java/com/towitty/bookreport/presentation/ui/screens/bookreport/BookReportScreen.kt
@@ -1,9 +1,10 @@
-package com.towitty.bookreport.ui.bookreport
+package com.towitty.bookreport.presentation.ui.screens.bookreport
import android.graphics.Bitmap
import android.graphics.drawable.Drawable
import androidx.compose.foundation.Image
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
@@ -14,6 +15,8 @@ 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.shape.RoundedCornerShape
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
@@ -25,6 +28,7 @@ import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBar
import androidx.compose.runtime.Composable
+import androidx.compose.runtime.State
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.rememberSaveable
@@ -46,17 +50,22 @@ import com.bumptech.glide.Glide
import com.bumptech.glide.request.target.CustomTarget
import com.bumptech.glide.request.transition.Transition
import com.towitty.bookreport.R
-import com.towitty.bookreport.model.BookItem
+import com.towitty.bookreport.data.database.model.TagEntity
+import com.towitty.bookreport.data.network.model.BookItem
@Composable
fun BookReportScreen(
+ addedTagListState: State>,
onCancel: () -> Unit,
- onSave: () -> Unit,
+ onSaveBookReport: () -> Unit,
+ onAddSelectedTag: (Int) -> Unit,
+ onRemoveTag: (Int) -> Unit,
+ onNavigateAddTag: () -> Unit,
modifier: Modifier = Modifier,
bookItem: BookItem
) {
Scaffold(
- topBar = { BookReportTopAppbar(onCancel, onSave) },
+ topBar = { BookReportTopAppbar(onCancel, onSaveBookReport) },
modifier = modifier
) { innerPadding ->
Column(modifier = Modifier.padding(innerPadding)) {
@@ -82,17 +91,24 @@ fun BookReportScreen(
Spacer(modifier = Modifier.size(4.dp))
BookReportContent(
- Modifier.fillMaxWidth()
+ addedTagListState = addedTagListState,
+ onAddSelectedTag = onAddSelectedTag,
+ onRemoveTag = onRemoveTag,
+ onNavigateAddTag = onNavigateAddTag,
+ modifier = Modifier.fillMaxWidth()
)
}
-
}
}
@Composable
@OptIn(ExperimentalMaterial3Api::class)
-private fun BookReportTopAppbar(onCancel: () -> Unit, onSave: () -> Unit, modifier: Modifier = Modifier) {
+private fun BookReportTopAppbar(
+ onCancel: () -> Unit,
+ onSaveBookReport: () -> Unit,
+ modifier: Modifier = Modifier
+) {
TopAppBar(
title = {},
navigationIcon = {
@@ -106,7 +122,7 @@ private fun BookReportTopAppbar(onCancel: () -> Unit, onSave: () -> Unit, modifi
},
actions = {
TextButton(
- onClick = onSave,
+ onClick = onSaveBookReport,
modifier = Modifier
.border(1.dp, color = Color.Gray, shape = RoundedCornerShape(4.dp))
) {
@@ -208,13 +224,16 @@ fun BookReportBookInfo(
}
@Composable
-fun BookReportContent(modifier: Modifier = Modifier) {
- var bookReportTitle by rememberSaveable {
- mutableStateOf("")
- }
- var bookReportContent by rememberSaveable {
- mutableStateOf("")
- }
+fun BookReportContent(
+ addedTagListState: State>,
+ onAddSelectedTag: (Int) -> Unit,
+ onRemoveTag: (Int) -> Unit,
+ onNavigateAddTag: () -> Unit,
+ modifier: Modifier = Modifier
+) {
+ var bookReportTitle by rememberSaveable { mutableStateOf("") }
+ var bookReportContent by rememberSaveable { mutableStateOf("") }
+
Column(modifier = modifier) {
Spacer(modifier = Modifier.size(8.dp))
OutlinedTextField(
@@ -226,6 +245,13 @@ fun BookReportContent(modifier: Modifier = Modifier) {
modifier = Modifier.fillMaxWidth()
)
Spacer(modifier = Modifier.size(8.dp))
+ TagListWithAddButton(
+ addedTagListState,
+ onNavigateAddTag,
+ onAddSelectedTag,
+ onRemoveTag,
+ )
+ Spacer(modifier = Modifier.size(8.dp))
OutlinedTextField(
value = bookReportContent,
onValueChange = { bookReportContent = it },
@@ -239,10 +265,41 @@ fun BookReportContent(modifier: Modifier = Modifier) {
}
+@Composable
+fun TagListWithAddButton(
+ addedTagListState: State>,
+ onNavigateAddTag: () -> Unit,
+ onAddSelectedTag: (Int) -> Unit,
+ onRemoveTag: (Int) -> Unit,
+) {
+ val addedTagList by addedTagListState
+ Row(
+ modifier = Modifier.fillMaxWidth()
+ ) {
+ LazyRow(
+ modifier = Modifier
+ .fillMaxWidth()
+ .weight(0.8f)
+ ) {
+ items(addedTagList) { tag ->
+ TagItem(tag = tag, icon = R.drawable.ic_outline_cancel, onClicked = onAddSelectedTag, onRemoveTag = onRemoveTag)
+ }
+ }
+ Image(
+ painter = painterResource(id = R.drawable.ic_rounded_add_circle),
+ contentDescription = stringResource(R.string.description_add_tag),
+ modifier = Modifier
+ .size(48.dp)
+ .align(Alignment.CenterVertically)
+ .clickable { onNavigateAddTag() }
+ )
+ }
+}
+
@Preview(showBackground = true)
@Composable
fun BookReportContentPreview(modifier: Modifier = Modifier) {
- BookReportContent()
+
}
@Preview(showBackground = true)
diff --git a/app/src/main/java/com/towitty/bookreport/ui/bookreport/BookSearchScreen.kt b/app/src/main/java/com/towitty/bookreport/presentation/ui/screens/bookreport/BookSearchScreen.kt
similarity index 94%
rename from app/src/main/java/com/towitty/bookreport/ui/bookreport/BookSearchScreen.kt
rename to app/src/main/java/com/towitty/bookreport/presentation/ui/screens/bookreport/BookSearchScreen.kt
index f74f20f..6eed4d4 100644
--- a/app/src/main/java/com/towitty/bookreport/ui/bookreport/BookSearchScreen.kt
+++ b/app/src/main/java/com/towitty/bookreport/presentation/ui/screens/bookreport/BookSearchScreen.kt
@@ -1,4 +1,4 @@
-package com.towitty.bookreport.ui.bookreport
+package com.towitty.bookreport.presentation.ui.screens.bookreport
import android.graphics.Bitmap
import android.graphics.drawable.Drawable
@@ -23,10 +23,6 @@ import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardActionScope
import androidx.compose.foundation.text.KeyboardActions
import androidx.compose.foundation.text.KeyboardOptions
-import androidx.compose.material.icons.Icons
-import androidx.compose.material.icons.filled.ArrowBackIosNew
-import androidx.compose.material.icons.filled.FilterAlt
-import androidx.compose.material.icons.filled.Search
import androidx.compose.material3.Button
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Card
@@ -59,7 +55,8 @@ import com.bumptech.glide.Glide
import com.bumptech.glide.request.target.CustomTarget
import com.bumptech.glide.request.transition.Transition
import com.towitty.bookreport.R
-import com.towitty.bookreport.model.BookItem
+import com.towitty.bookreport.data.network.model.BookItem
+import com.towitty.bookreport.presentation.ui.common.BookReportIcons
@OptIn(ExperimentalMaterial3Api::class)
@@ -82,7 +79,7 @@ fun BookSearchScreen(
navigationIcon = {
IconButton(onClick = onNavigateUp) {
Icon(
- imageVector = Icons.Default.ArrowBackIosNew, contentDescription = stringResource(
+ imageVector = BookReportIcons.ArrowBackIosNew, contentDescription = stringResource(
R.string.description_go_back
)
)
@@ -133,7 +130,7 @@ fun BookSearchBar(
value = searchText,
onValueChange = onValueChange,
leadingIcon = {
- Icon(imageVector = Icons.Default.Search, contentDescription = null)
+ Icon(imageVector = BookReportIcons.Search, contentDescription = null)
},
placeholder = {
Text(stringResource(id = R.string.placeholder_search))
@@ -157,7 +154,7 @@ fun SearchFilter(
horizontalArrangement = Arrangement.spacedBy(8.dp),
modifier = modifier.wrapContentHeight()
) {
- Icon(imageVector = Icons.Default.FilterAlt, contentDescription = null)
+ Icon(imageVector = BookReportIcons.FilterAlt, contentDescription = null)
TextButton(
onClick = { onSelectedFilter("title") },
diff --git a/app/src/main/java/com/towitty/bookreport/ui/calendar/CalendarScreen.kt b/app/src/main/java/com/towitty/bookreport/presentation/ui/screens/calendar/CalendarScreen.kt
similarity index 86%
rename from app/src/main/java/com/towitty/bookreport/ui/calendar/CalendarScreen.kt
rename to app/src/main/java/com/towitty/bookreport/presentation/ui/screens/calendar/CalendarScreen.kt
index aa2438c..9c5ddea 100644
--- a/app/src/main/java/com/towitty/bookreport/ui/calendar/CalendarScreen.kt
+++ b/app/src/main/java/com/towitty/bookreport/presentation/ui/screens/calendar/CalendarScreen.kt
@@ -1,4 +1,4 @@
-package com.towitty.bookreport.ui.calendar
+package com.towitty.bookreport.presentation.ui.screens.calendar
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Text
diff --git a/app/src/main/java/com/towitty/bookreport/presentation/ui/screens/home/HomeScreen.kt b/app/src/main/java/com/towitty/bookreport/presentation/ui/screens/home/HomeScreen.kt
new file mode 100644
index 0000000..2242c5a
--- /dev/null
+++ b/app/src/main/java/com/towitty/bookreport/presentation/ui/screens/home/HomeScreen.kt
@@ -0,0 +1,44 @@
+package com.towitty.bookreport.presentation.ui.screens.home
+
+import android.content.Context
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material3.CenterAlignedTopAppBar
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.tooling.preview.Preview
+import com.towitty.bookreport.R
+import com.towitty.bookreport.presentation.ui.common.BookReportIcons
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun HomeScreen(
+ moveSettings: (context: Context) -> Unit,
+) {
+ Surface(modifier = Modifier.fillMaxSize()) {
+ val context = LocalContext.current
+ Column {
+ CenterAlignedTopAppBar(
+ title = { Text(stringResource(id = R.string.app_name)) },
+ actions = {
+ IconButton(
+ onClick = { moveSettings(context) },
+ content = { Icon(BookReportIcons.Settings, contentDescription = null) }
+ )
+ }
+ )
+ }
+ }
+}
+
+@Preview(showBackground = true)
+@Composable
+fun HomeScreenPreview(modifier: Modifier = Modifier) {
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/towitty/bookreport/ui/search/SearchScreen.kt b/app/src/main/java/com/towitty/bookreport/presentation/ui/screens/search/SearchScreen.kt
similarity index 86%
rename from app/src/main/java/com/towitty/bookreport/ui/search/SearchScreen.kt
rename to app/src/main/java/com/towitty/bookreport/presentation/ui/screens/search/SearchScreen.kt
index 1c61ba9..97c3c31 100644
--- a/app/src/main/java/com/towitty/bookreport/ui/search/SearchScreen.kt
+++ b/app/src/main/java/com/towitty/bookreport/presentation/ui/screens/search/SearchScreen.kt
@@ -1,4 +1,4 @@
-package com.towitty.bookreport.ui.search
+package com.towitty.bookreport.presentation.ui.screens.search
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Text
diff --git a/app/src/main/java/com/towitty/bookreport/ui/BookReportViewModel.kt b/app/src/main/java/com/towitty/bookreport/ui/BookReportViewModel.kt
deleted file mode 100644
index 552d8fb..0000000
--- a/app/src/main/java/com/towitty/bookreport/ui/BookReportViewModel.kt
+++ /dev/null
@@ -1,34 +0,0 @@
-package com.towitty.bookreport.ui
-
-import androidx.lifecycle.ViewModel
-import androidx.lifecycle.viewModelScope
-import com.towitty.bookreport.data.network.BookRemoteRepository
-import com.towitty.bookreport.model.BookItem
-import com.towitty.bookreport.model.emptyBookItem
-import dagger.hilt.android.lifecycle.HiltViewModel
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.launch
-import javax.inject.Inject
-
-@HiltViewModel
-class BookReportViewModel @Inject constructor(
- private val bookRemoteRepository: BookRemoteRepository,
-) : ViewModel() {
- private val _bookList = MutableStateFlow>(emptyList())
- val bookList: StateFlow> = _bookList
-
- private val _selectedBook = MutableStateFlow(emptyBookItem)
- val selectedBook: StateFlow = _selectedBook
-
- fun searchBooks(query: String) {
- viewModelScope.launch {
- val books = bookRemoteRepository.searchBooks(query)
- _bookList.value = books.bookList
- }
- }
-
- fun findBookByIsbn(isbn: String) {
- _selectedBook.value = _bookList.value.find { it.isbn == isbn } ?: emptyBookItem
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/towitty/bookreport/ui/home/HomeScreen.kt b/app/src/main/java/com/towitty/bookreport/ui/home/HomeScreen.kt
deleted file mode 100644
index 051862c..0000000
--- a/app/src/main/java/com/towitty/bookreport/ui/home/HomeScreen.kt
+++ /dev/null
@@ -1,16 +0,0 @@
-package com.towitty.bookreport.ui.home
-
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.tooling.preview.Preview
-
-@Composable
-fun HomeScreen() {
-
-}
-
-@Preview(showBackground = true)
-@Composable
-fun HomeScreenPreview(modifier: Modifier = Modifier) {
- HomeScreen()
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/towitty/bookreport/ui/navigation/Navigation.kt b/app/src/main/java/com/towitty/bookreport/ui/navigation/Navigation.kt
deleted file mode 100644
index 1ed5e3d..0000000
--- a/app/src/main/java/com/towitty/bookreport/ui/navigation/Navigation.kt
+++ /dev/null
@@ -1,78 +0,0 @@
-package com.towitty.bookreport.ui.navigation
-
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.collectAsState
-import androidx.compose.runtime.getValue
-import androidx.compose.ui.Modifier
-import androidx.navigation.NavHostController
-import androidx.navigation.compose.NavHost
-import androidx.navigation.compose.composable
-import com.towitty.bookreport.model.BookItem
-import com.towitty.bookreport.model.emptyBookItem
-import com.towitty.bookreport.ui.BookReportViewModel
-import com.towitty.bookreport.ui.bookreport.BookInfoDetailScreen
-import com.towitty.bookreport.ui.bookreport.BookReportScreen
-import com.towitty.bookreport.ui.bookreport.BookSearchScreen
-import com.towitty.bookreport.ui.calendar.CalendarScreen
-import com.towitty.bookreport.ui.home.HomeScreen
-import com.towitty.bookreport.ui.search.SearchScreen
-import com.towitty.bookreport.ui.settings.SettingsScreen
-
-@Composable
-fun Navigation(
- viewModel: BookReportViewModel,
- navController: NavHostController,
- startDestination: String,
- modifier: Modifier = Modifier,
-) {
- val bookList: List by viewModel.bookList.collectAsState()
- val selectedBook: BookItem by viewModel.selectedBook.collectAsState()
-
- NavHost(
- navController = navController,
- startDestination = startDestination,
- modifier = modifier.fillMaxSize()
- ) {
- composable(route = BottomNavItem.HOME.name) {
- HomeScreen()
- }
- composable(route = BottomNavItem.CALENDAR.name) {
- CalendarScreen()
- }
- composable(route = BottomNavItem.SEARCH.name) {
- SearchScreen()
- }
- composable(route = BottomNavItem.SETTINGS.name) {
- SettingsScreen()
- }
- composable(route = Routes.DIRECTLY_BOOK_REPORT) {
- val previousRoute = navController.previousBackStackEntry?.destination?.route
- BookReportScreen(
- onCancel = { navController.navigateUp() },
- onSave = {/*TODO*/ },
- bookItem = if (previousRoute == Routes.BOOK_INFO_DETAIL) selectedBook else emptyBookItem
- )
- }
- composable(route = Routes.BOOK_SEARCH_FOR_BOOK_REPORT) {
- BookSearchScreen(
- onNavigateUp = { navController.navigateUp() },
- searchBook = { viewModel.searchBooks(it) },
- bookList = bookList,
- onItemClicked = { isbn ->
- viewModel.findBookByIsbn(isbn)
- navController.navigate(Routes.BOOK_INFO_DETAIL)
- },
- title = {/*미사용*/ }
- )
- }
-
- composable(route = Routes.BOOK_INFO_DETAIL) {
- BookInfoDetailScreen(
- onNavigateUp = { navController.navigateUp() },
- onSelection = { navController.navigate(Routes.DIRECTLY_BOOK_REPORT) },
- bookItem = selectedBook
- )
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/com/towitty/bookreport/ui/settings/SettingsScreen.kt b/app/src/main/java/com/towitty/bookreport/ui/settings/SettingsScreen.kt
deleted file mode 100644
index 27fb58a..0000000
--- a/app/src/main/java/com/towitty/bookreport/ui/settings/SettingsScreen.kt
+++ /dev/null
@@ -1,13 +0,0 @@
-package com.towitty.bookreport.ui.settings
-
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.material3.Text
-import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.res.stringResource
-import com.towitty.bookreport.R
-
-@Composable
-fun SettingsScreen(modifier: Modifier = Modifier) {
- Text(text = stringResource(id = R.string.label_settings),modifier = modifier.fillMaxSize())
-}
\ No newline at end of file
diff --git a/app/src/main/res/drawable/circle_border.xml b/app/src/main/res/drawable/circle_border.xml
new file mode 100644
index 0000000..012a714
--- /dev/null
+++ b/app/src/main/res/drawable/circle_border.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_baseline_arrow_forward.xml b/app/src/main/res/drawable/ic_baseline_arrow_forward.xml
new file mode 100644
index 0000000..19201cb
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_arrow_forward.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_outline_arrow_back.xml b/app/src/main/res/drawable/ic_outline_arrow_back.xml
new file mode 100644
index 0000000..afe2d49
--- /dev/null
+++ b/app/src/main/res/drawable/ic_outline_arrow_back.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_outline_cancel.xml b/app/src/main/res/drawable/ic_outline_cancel.xml
new file mode 100644
index 0000000..e24726a
--- /dev/null
+++ b/app/src/main/res/drawable/ic_outline_cancel.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_outline_photo_camera.xml b/app/src/main/res/drawable/ic_outline_photo_camera.xml
new file mode 100644
index 0000000..e71e765
--- /dev/null
+++ b/app/src/main/res/drawable/ic_outline_photo_camera.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_rounded_add_circle.xml b/app/src/main/res/drawable/ic_rounded_add_circle.xml
new file mode 100644
index 0000000..fbd91ea
--- /dev/null
+++ b/app/src/main/res/drawable/ic_rounded_add_circle.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_sharp_person_edit.xml b/app/src/main/res/drawable/ic_sharp_person_edit.xml
new file mode 100644
index 0000000..3f6d2f1
--- /dev/null
+++ b/app/src/main/res/drawable/ic_sharp_person_edit.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/rounded_button.xml b/app/src/main/res/drawable/rounded_button.xml
new file mode 100644
index 0000000..d3917fe
--- /dev/null
+++ b/app/src/main/res/drawable/rounded_button.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_settings.xml b/app/src/main/res/layout/activity_settings.xml
new file mode 100644
index 0000000..33c1dd2
--- /dev/null
+++ b/app/src/main/res/layout/activity_settings.xml
@@ -0,0 +1,21 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_add_tag_bottom_sheet.xml b/app/src/main/res/layout/fragment_add_tag_bottom_sheet.xml
new file mode 100644
index 0000000..1c841de
--- /dev/null
+++ b/app/src/main/res/layout/fragment_add_tag_bottom_sheet.xml
@@ -0,0 +1,80 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_settings.xml b/app/src/main/res/layout/fragment_settings.xml
new file mode 100644
index 0000000..06d1953
--- /dev/null
+++ b/app/src/main/res/layout/fragment_settings.xml
@@ -0,0 +1,54 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/fragment_tag_management.xml b/app/src/main/res/layout/fragment_tag_management.xml
new file mode 100644
index 0000000..d201cb7
--- /dev/null
+++ b/app/src/main/res/layout/fragment_tag_management.xml
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_selectable_color.xml b/app/src/main/res/layout/item_selectable_color.xml
new file mode 100644
index 0000000..1fe8da6
--- /dev/null
+++ b/app/src/main/res/layout/item_selectable_color.xml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_settings.xml b/app/src/main/res/layout/item_settings.xml
new file mode 100644
index 0000000..f878dce
--- /dev/null
+++ b/app/src/main/res/layout/item_settings.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_tag.xml b/app/src/main/res/layout/item_tag.xml
new file mode 100644
index 0000000..0034660
--- /dev/null
+++ b/app/src/main/res/layout/item_tag.xml
@@ -0,0 +1,44 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/navigation/settings_navigation.xml b/app/src/main/res/navigation/settings_navigation.xml
new file mode 100644
index 0000000..f8d783d
--- /dev/null
+++ b/app/src/main/res/navigation/settings_navigation.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index f8c6127..5a24075 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -7,4 +7,12 @@
#FF018786
#FF000000
#FFFFFFFF
+
+ #FF0000
+ #FFA500
+ #FFFF00
+ #008000
+ #0000FF
+ #4B0082
+ #EE82EE
\ 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 36956a8..75200f2 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -8,11 +8,12 @@
직접 입력
책 검색
바코드
- 독후감 작성 하기
+ 독후감 작성 하기
직접 입력
책 검색
바코드
선택
+ 등록
저장
취소
책 이름
@@ -29,4 +30,22 @@
카메라 버튼
ISBN
소개
+ 프로필 편집
+ 프로필 이미지
+ 선택한 설정 이동
+ 알람 설정
+ 태그 관리
+ 백업 및 동기화
+ 테마
+ 사용자 피드백 및 개선 보고
+ 설정 초기화
+ 태그 추가 버튼
+ 태그 삭제 버튼
+ 태그
+ 태그 이름
+ 태그 추가
+ 태그 저장
+ 태그 저장 실패
+ 사용자 지정
+ Color
\ No newline at end of file
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..42b8cee
--- /dev/null
+++ b/app/src/main/res/values/styles.xml
@@ -0,0 +1,6 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml
index ad4fa18..f537bd5 100644
--- a/app/src/main/res/values/themes.xml
+++ b/app/src/main/res/values/themes.xml
@@ -1,5 +1,4 @@
-
-
+
\ No newline at end of file
diff --git a/app/src/test/java/com/towitty/bookreport/data/database/FakeTagDao.kt b/app/src/test/java/com/towitty/bookreport/data/database/FakeTagDao.kt
new file mode 100644
index 0000000..6c81aa1
--- /dev/null
+++ b/app/src/test/java/com/towitty/bookreport/data/database/FakeTagDao.kt
@@ -0,0 +1,35 @@
+package com.towitty.bookreport.data.database
+
+import com.towitty.bookreport.data.database.model.TagEntity
+import com.towitty.bookreport.data.database.model.emptyTagEntity
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flow
+
+class FakeTagDao(
+ val tagDatabase: MutableList
+) : TagDao {
+
+ override suspend fun insertTag(tagEntity: TagEntity) {
+ tagDatabase.add(tagEntity)
+ }
+
+ override suspend fun updateTag(tagEntity: TagEntity) {
+ val index = tagDatabase.indexOfFirst { it.id == tagEntity.id }
+ if (index >= 0) {
+ tagDatabase[index] = tagEntity
+ }
+ }
+
+ override suspend fun deleteTag(tagEntity: TagEntity) {
+ tagDatabase.remove(tagEntity)
+ }
+
+ override fun getTag(id: Int): Flow = flow {
+ emit(tagDatabase.firstOrNull { it.id == id } ?: emptyTagEntity)
+ }
+
+ override fun getAllTags(): Flow> = flow {
+ emit(tagDatabase.toList())
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/test/java/com/towitty/bookreport/data/database/FakeTagLocalRepository.kt b/app/src/test/java/com/towitty/bookreport/data/database/FakeTagLocalRepository.kt
new file mode 100644
index 0000000..fcf0ead
--- /dev/null
+++ b/app/src/test/java/com/towitty/bookreport/data/database/FakeTagLocalRepository.kt
@@ -0,0 +1,35 @@
+package com.towitty.bookreport.data.database
+
+import com.towitty.bookreport.data.database.model.TagEntity
+import com.towitty.bookreport.data.repository.ITagRepository
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flow
+
+class FakeTagLocalRepository(
+ private val tagDatabase: MutableList
+) : ITagRepository {
+
+ override suspend fun insertTag(tagEntity: TagEntity) {
+ tagDatabase.add(tagEntity)
+ }
+
+ override suspend fun updateTag(tagEntity: TagEntity) {
+ val index = tagDatabase.indexOfFirst { it.id == tagEntity.id }
+ if (index >= 0) {
+ tagDatabase[index] = tagEntity
+ }
+ }
+
+ override suspend fun deleteTag(tagEntity: TagEntity) {
+ tagDatabase.remove(tagEntity)
+ }
+
+ override fun getTag(id: Int): Flow = flow {
+ tagDatabase.first { it.id == id }
+ }
+
+ override fun getAllTags(): Flow> = flow {
+ tagDatabase
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/test/java/com/towitty/bookreport/data/database/TagLocalRepositoryTest.kt b/app/src/test/java/com/towitty/bookreport/data/database/TagLocalRepositoryTest.kt
new file mode 100644
index 0000000..b061b77
--- /dev/null
+++ b/app/src/test/java/com/towitty/bookreport/data/database/TagLocalRepositoryTest.kt
@@ -0,0 +1,60 @@
+import com.towitty.bookreport.data.database.FakeTagDao
+import com.towitty.bookreport.data.database.model.TagEntity
+import com.towitty.bookreport.data.database.model.emptyTagEntity
+import kotlinx.coroutines.flow.firstOrNull
+import kotlinx.coroutines.test.runTest
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+
+class TagLocalRepositoryTest {
+
+ private lateinit var fakeTagDao: FakeTagDao
+ private val tagDatabase = mutableListOf()
+
+ @Before
+ fun setUp() {
+ fakeTagDao = FakeTagDao(tagDatabase)
+ }
+
+ @Test
+ fun insertTag_WhenTagAdded_ShouldRetrieveSameTag() = runTest {
+ tagDatabase.clear()
+
+ val tag = TagEntity(10, "", 0)
+
+ fakeTagDao.insertTag(tag)
+
+ val getTag = fakeTagDao.getTag(10).firstOrNull()
+ assertEquals(tag, getTag)
+ }
+
+ @Test
+ fun getAllTags_WhenMultipleTagsExist_ShouldReturnAllTags() = runTest {
+ fakeTagDao.tagDatabase.clear()
+ fakeTagDao.tagDatabase.add(TagEntity(1, "", 0))
+ fakeTagDao.tagDatabase.add(TagEntity(2, "", 0))
+ fakeTagDao.tagDatabase.add(TagEntity(3, "", 0))
+
+ val tags = fakeTagDao.getAllTags().firstOrNull() ?: emptyList()
+
+ assertEquals(3, tags.size)
+ assertTrue(tags.contains(TagEntity(1, "", 0)))
+ assertTrue(tags.contains(TagEntity(2, "", 0)))
+ assertTrue(tags.contains(TagEntity(3, "", 0)))
+ }
+
+ @Test
+ fun deleteTagById_WhenTagExists_ShouldRemoveTagFromDatabase() = runTest {
+ fakeTagDao.tagDatabase.clear()
+ fakeTagDao.tagDatabase.add(TagEntity(1, "", 0))
+ fakeTagDao.deleteTag(TagEntity(1, "", 0))
+
+ val deletedTag = fakeTagDao.getTag(1).firstOrNull() ?: emptyTagEntity
+
+ assertEquals(deletedTag, emptyTagEntity)
+ assertFalse(fakeTagDao.tagDatabase.contains(TagEntity(1, "", 0)))
+ }
+}
\ No newline at end of file
diff --git a/app/src/test/java/com/towitty/bookreport/data/network/BookRemoteRepositoryTest.kt b/app/src/test/java/com/towitty/bookreport/data/network/BookRemoteRepositoryTest.kt
new file mode 100644
index 0000000..c9a3e80
--- /dev/null
+++ b/app/src/test/java/com/towitty/bookreport/data/network/BookRemoteRepositoryTest.kt
@@ -0,0 +1,96 @@
+package com.towitty.bookreport.data.network
+
+import com.towitty.bookreport.data.network.model.BookItem
+import com.towitty.bookreport.data.repository.BookRemoteRepository
+import kotlinx.coroutines.runBlocking
+import org.junit.Assert.assertEquals
+import org.junit.Before
+import org.junit.Test
+
+class BookRemoteRepositoryTest {
+
+ private lateinit var bookRemoteDataSource: IBookDataSource
+ private lateinit var bookRemoteRepository: BookRemoteRepository
+
+ @Before
+ fun setUp() {
+ bookRemoteDataSource = FakeBookRemoteDataSource()
+ bookRemoteRepository = BookRemoteRepository(bookRemoteDataSource)
+ }
+
+ @Test
+ fun getBookSearch_WhenSearchedBooksExist_ShouldReturnMatchingBooks() = runBlocking {
+ val androidQuery = "안드로이드"
+ val kotlinQuery = "코틀린"
+ ((bookRemoteDataSource as FakeBookRemoteDataSource).book.bookList as MutableList).add(
+ BookItem(
+ title = androidQuery,
+ link = "",
+ image = "",
+ author = "",
+ price = "",
+ publisher = "",
+ pubDate = "",
+ isbn = "1234",
+ description = ""
+ )
+ )
+ ((bookRemoteDataSource as FakeBookRemoteDataSource).book.bookList as MutableList).add(
+ BookItem(
+ title = kotlinQuery,
+ link = "",
+ image = "",
+ author = "",
+ price = "",
+ publisher = "",
+ pubDate = "",
+ isbn = "5678",
+ description = ""
+ )
+ )
+
+ val androidBook = bookRemoteRepository.searchBooks(androidQuery)
+ val kotlinBook = bookRemoteRepository.searchBooks(kotlinQuery)
+
+ assert(androidBook.isEmpty().not())
+ assert(kotlinBook.isEmpty().not())
+ assertEquals(1, androidBook.size)
+ }
+
+ @Test
+ fun getBookSearch_WhenNoMatchingSearchedBooks_ShouldReturnEmptyList() = runBlocking {
+ val query = "Kotlin"
+ val androidQuery = "안드로이드"
+ val kotlinQuery = "코틀린"
+ ((bookRemoteDataSource as FakeBookRemoteDataSource).book.bookList as MutableList).add(
+ BookItem(
+ title = androidQuery,
+ link = "",
+ image = "",
+ author = "",
+ price = "",
+ publisher = "",
+ pubDate = "",
+ isbn = "1234",
+ description = ""
+ )
+ )
+ ((bookRemoteDataSource as FakeBookRemoteDataSource).book.bookList as MutableList).add(
+ BookItem(
+ title = kotlinQuery,
+ link = "",
+ image = "",
+ author = "",
+ price = "",
+ publisher = "",
+ pubDate = "",
+ isbn = "5678",
+ description = ""
+ )
+ )
+
+ val book = bookRemoteRepository.searchBooks(query)
+
+ assert(book.isEmpty())
+ }
+}
\ No newline at end of file
diff --git a/app/src/test/java/com/towitty/bookreport/data/network/FakeBookRemoteDataSource.kt b/app/src/test/java/com/towitty/bookreport/data/network/FakeBookRemoteDataSource.kt
new file mode 100644
index 0000000..ee226e2
--- /dev/null
+++ b/app/src/test/java/com/towitty/bookreport/data/network/FakeBookRemoteDataSource.kt
@@ -0,0 +1,22 @@
+package com.towitty.bookreport.data.network
+
+import com.towitty.bookreport.data.network.model.Book
+import com.towitty.bookreport.data.network.model.emptyBook
+
+class FakeBookRemoteDataSource : IBookDataSource {
+
+ var book = Book(
+ lastBuildDate = "",
+ total = 0,
+ start = 0,
+ display = 0,
+ bookList = mutableListOf()
+ )
+
+ override suspend fun getBook(query: String): Book {
+ return book.bookList.find { it.title.contains(query) }?.let {
+ book
+ } ?: emptyBook
+ }
+
+}
diff --git a/build.gradle.kts b/build.gradle.kts
index b273b82..237df91 100644
--- a/build.gradle.kts
+++ b/build.gradle.kts
@@ -4,4 +4,5 @@ plugins {
alias(libs.plugins.jetbrains.kotlin.android) apply false
alias(libs.plugins.compose.compiler) apply false
id("com.google.dagger.hilt.android") version "2.52" apply false
+ id("com.google.devtools.ksp") version "2.0.20-1.0.25" apply false
}
\ No newline at end of file
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 38f8b66..3cab0e5 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,5 +1,6 @@
[versions]
agp = "8.5.2"
+constraintlayout = "2.1.4"
glide = "4.13.0"
hiltAndroid = "2.52"
hiltNavigationCompose = "1.2.0"
@@ -9,22 +10,43 @@ junit = "4.13.2"
junitVersion = "1.2.1"
espressoCore = "3.6.1"
kotlinxCoroutinesCore = "1.9.0"
+kotlinxCoroutinesTest = "1.8.1"
lifecycleRuntimeKtx = "2.8.6"
activityCompose = "1.9.3"
composeBom = "2024.10.00"
+material = "1.12.0"
+mockk = "1.12.0"
navigationCompose = "2.8.3"
+fragment = "1.8.4"
+recyclerview = "1.3.2"
retrofit = "2.11.0"
+roomCompiler = "2.6.1"
timber = "5.0.1"
runtimeLivedata = "1.7.4"
+uiViewbinding = "1.7.4"
+appcompat = "1.7.0"
+activity = "1.9.3"
+navigationFragment = "2.8.3"
[libraries]
+androidx-constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version.ref = "constraintlayout" }
+androidx-fragment = { module = "androidx.fragment:fragment", version.ref = "fragment" }
+androidx-recyclerview = { module = "androidx.recyclerview:recyclerview", version.ref = "recyclerview" }
+androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "roomCompiler" }
+androidx-room-ktx = { module = "androidx.room:room-ktx", version.ref = "roomCompiler" }
+androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "roomCompiler" }
glide-compiler = { module = "com.github.bumptech.glide:compiler", version.ref = "glide" }
glide = { module = "com.github.bumptech.glide:glide", version.ref = "glide" }
hilt-android = { module = "com.google.dagger:hilt-android", version.ref = "hiltAndroid" }
hilt-android-compiler = { module = "com.google.dagger:hilt-android-compiler", version.ref = "hiltAndroid" }
androidx-hilt-navigation-compose = { module = "androidx.hilt:hilt-navigation-compose", version.ref = "hiltNavigationCompose" }
+hilt-android-testing = { module = "com.google.dagger:hilt-android-testing", version.ref = "hiltAndroid" }
+hilt-compiler = { module = "com.google.dagger:hilt-compiler", version.ref = "hiltAndroid" }
kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinxCoroutinesCore" }
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinxCoroutinesCore" }
+kotlinx-coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "kotlinxCoroutinesTest" }
+material = { module = "com.google.android.material:material", version.ref = "material" }
+mockk = { module = "io.mockk:mockk", version.ref = "mockk" }
retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
converter-gson = { module = "com.squareup.retrofit2:converter-gson", version.ref = "retrofit" }
@@ -46,6 +68,10 @@ androidx-ui-test-manifest = { group = "androidx.compose.ui", name = "ui-test-man
androidx-ui-test-junit4 = { group = "androidx.compose.ui", name = "ui-test-junit4" }
timber = { module = "com.jakewharton.timber:timber", version.ref = "timber" }
androidx-runtime-livedata = { group = "androidx.compose.runtime", name = "runtime-livedata", version.ref = "runtimeLivedata" }
+androidx-ui-viewbinding = { group = "androidx.compose.ui", name = "ui-viewbinding", version.ref = "uiViewbinding" }
+androidx-appcompat = { group = "androidx.appcompat", name = "appcompat", version.ref = "appcompat" }
+androidx-activity = { group = "androidx.activity", name = "activity", version.ref = "activity" }
+androidx-navigation-fragment = { group = "androidx.navigation", name = "navigation-fragment", version.ref = "navigationFragment" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }