0.1.0 - Basic Setting
This commit is contained in:
parent
34d3e5c38a
commit
29a75aa18a
32 changed files with 744 additions and 16 deletions
32
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
32
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
|
|
@ -0,0 +1,32 @@
|
||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<profile version="1.0">
|
||||||
|
<option name="myName" value="Project Default" />
|
||||||
|
<inspection_tool class="PreviewAnnotationInFunctionWithParameters" enabled="true" level="ERROR" enabled_by_default="true">
|
||||||
|
<option name="composableFile" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="PreviewApiLevelMustBeValid" enabled="true" level="ERROR" enabled_by_default="true">
|
||||||
|
<option name="composableFile" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="PreviewDimensionRespectsLimit" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="composableFile" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="PreviewFontScaleMustBeGreaterThanZero" enabled="true" level="ERROR" enabled_by_default="true">
|
||||||
|
<option name="composableFile" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="PreviewMultipleParameterProviders" enabled="true" level="ERROR" enabled_by_default="true">
|
||||||
|
<option name="composableFile" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="PreviewMustBeTopLevelFunction" enabled="true" level="ERROR" enabled_by_default="true">
|
||||||
|
<option name="composableFile" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="PreviewNeedsComposableAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
|
||||||
|
<option name="composableFile" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="PreviewNotSupportedInUnitTestFiles" enabled="true" level="ERROR" enabled_by_default="true">
|
||||||
|
<option name="composableFile" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
<inspection_tool class="PreviewPickerAnnotation" enabled="true" level="ERROR" enabled_by_default="true">
|
||||||
|
<option name="composableFile" value="true" />
|
||||||
|
</inspection_tool>
|
||||||
|
</profile>
|
||||||
|
</component>
|
||||||
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
|
|
@ -2,6 +2,8 @@ plugins {
|
||||||
id("com.android.application")
|
id("com.android.application")
|
||||||
id("org.jetbrains.kotlin.android")
|
id("org.jetbrains.kotlin.android")
|
||||||
id("com.google.devtools.ksp")
|
id("com.google.devtools.ksp")
|
||||||
|
|
||||||
|
id("com.google.dagger.hilt.android")
|
||||||
kotlin("kapt")
|
kotlin("kapt")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -47,6 +49,7 @@ android {
|
||||||
packaging {
|
packaging {
|
||||||
resources {
|
resources {
|
||||||
excludes += "/META-INF/{AL2.0,LGPL2.1}"
|
excludes += "/META-INF/{AL2.0,LGPL2.1}"
|
||||||
|
excludes += "/META-INF/gradle/incremental.annotation.processors"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -77,16 +80,13 @@ dependencies {
|
||||||
|
|
||||||
// All dependencies
|
// All dependencies
|
||||||
implementation ("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.2")
|
implementation ("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.2")
|
||||||
implementation ("androidx.compose.material:material-icons-extended:$composeVersion")
|
implementation ("androidx.compose.material:material-icons-extended")
|
||||||
implementation ("androidx.activity:activity-compose:1.8.2")
|
implementation ("androidx.activity:activity-compose:1.8.2")
|
||||||
implementation ("com.google.accompanist:accompanist-swiperefresh:0.24.2-alpha")
|
implementation ("com.google.accompanist:accompanist-swiperefresh:0.24.2-alpha")
|
||||||
|
|
||||||
//Dagger - Hilt
|
// Dagger
|
||||||
implementation ("com.google.dagger:hilt-compiler:latest.release")
|
implementation("com.google.dagger:hilt-android:2.50")
|
||||||
implementation ("com.google.dagger:hilt-android:latest.release")
|
kapt("com.google.dagger:hilt-android-compiler:2.50")
|
||||||
kapt ("com.google.dagger:hilt-android-compiler:latest.release")
|
|
||||||
kapt ("androidx.hilt:hilt-compiler:latest.release")
|
|
||||||
implementation ("androidx.hilt:hilt-navigation-compose:1.1.0")
|
|
||||||
|
|
||||||
// Room
|
// Room
|
||||||
implementation ("androidx.room:room-runtime:2.6.1")
|
implementation ("androidx.room:room-runtime:2.6.1")
|
||||||
|
|
@ -94,4 +94,7 @@ dependencies {
|
||||||
|
|
||||||
// Kotlin Extensions and Coroutines support for Room
|
// Kotlin Extensions and Coroutines support for Room
|
||||||
implementation ("androidx.room:room-ktx:2.6.1")
|
implementation ("androidx.room:room-ktx:2.6.1")
|
||||||
|
|
||||||
|
// Datastore
|
||||||
|
implementation ("androidx.datastore:datastore-preferences:latest.release")
|
||||||
}
|
}
|
||||||
|
|
@ -3,6 +3,7 @@
|
||||||
xmlns:tools="http://schemas.android.com/tools">
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
<application
|
<application
|
||||||
|
android:name=".util.BookApplication"
|
||||||
android:allowBackup="true"
|
android:allowBackup="true"
|
||||||
android:dataExtractionRules="@xml/data_extraction_rules"
|
android:dataExtractionRules="@xml/data_extraction_rules"
|
||||||
android:fullBackupContent="@xml/backup_rules"
|
android:fullBackupContent="@xml/backup_rules"
|
||||||
|
|
|
||||||
|
|
@ -3,15 +3,36 @@ package com.acclorite.books_history
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.activity.compose.setContent
|
import androidx.activity.compose.setContent
|
||||||
|
import androidx.activity.viewModels
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.datastore.core.DataStore
|
||||||
|
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||||
|
import com.acclorite.books_history.data.use_case.ChangeLanguageImpl
|
||||||
|
import com.acclorite.books_history.presentation.MainViewModel
|
||||||
|
import com.acclorite.books_history.presentation.NavigationHost
|
||||||
|
import com.acclorite.books_history.presentation.Screen
|
||||||
import com.acclorite.books_history.ui.theme.BooksHistoryResurrectionTheme
|
import com.acclorite.books_history.ui.theme.BooksHistoryResurrectionTheme
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
|
||||||
|
@AndroidEntryPoint
|
||||||
class MainActivity : ComponentActivity() {
|
class MainActivity : ComponentActivity() {
|
||||||
|
private val mainViewModel: MainViewModel by viewModels()
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
setContent {
|
setContent {
|
||||||
BooksHistoryResurrectionTheme {
|
BooksHistoryResurrectionTheme {
|
||||||
|
NavigationHost(startScreen = Screen.LIBRARY, this) {
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
package com.acclorite.books_history.data.di
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.datastore.core.DataStore
|
||||||
|
import androidx.datastore.preferences.core.Preferences
|
||||||
|
import androidx.datastore.preferences.preferencesDataStore
|
||||||
|
import androidx.room.Room
|
||||||
|
import com.acclorite.books_history.data.local.room.BookDao
|
||||||
|
import com.acclorite.books_history.data.local.room.BookDatabase
|
||||||
|
import dagger.Module
|
||||||
|
import dagger.Provides
|
||||||
|
import dagger.hilt.InstallIn
|
||||||
|
import dagger.hilt.components.SingletonComponent
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Module
|
||||||
|
@InstallIn(SingletonComponent::class)
|
||||||
|
object AppModule {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideBookDao(app: Application): BookDao {
|
||||||
|
return Room.databaseBuilder(
|
||||||
|
app,
|
||||||
|
BookDatabase::class.java,
|
||||||
|
"book_db"
|
||||||
|
).build().dao
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
package com.acclorite.books_history.data.di
|
||||||
|
|
||||||
|
import com.acclorite.books_history.data.local.data_store.DataStore
|
||||||
|
import com.acclorite.books_history.data.local.data_store.DataStoreImpl
|
||||||
|
import com.acclorite.books_history.data.repository.BookRepositoryImpl
|
||||||
|
import com.acclorite.books_history.domain.repository.BookRepository
|
||||||
|
import dagger.Binds
|
||||||
|
import dagger.Module
|
||||||
|
import dagger.hilt.InstallIn
|
||||||
|
import dagger.hilt.components.SingletonComponent
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Module
|
||||||
|
@InstallIn(SingletonComponent::class)
|
||||||
|
abstract class RepositoryModule {
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindDataStore(
|
||||||
|
dataStoreImpl: DataStoreImpl
|
||||||
|
): DataStore
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
@Singleton
|
||||||
|
abstract fun bindBookRepository(
|
||||||
|
bookRepositoryImpl: BookRepositoryImpl
|
||||||
|
): BookRepository
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
package com.acclorite.books_history.data.di
|
||||||
|
|
||||||
|
import com.acclorite.books_history.data.use_case.ChangeLanguageImpl
|
||||||
|
import com.acclorite.books_history.domain.use_case.ChangeLanguage
|
||||||
|
import dagger.Binds
|
||||||
|
import dagger.Module
|
||||||
|
import dagger.hilt.InstallIn
|
||||||
|
import dagger.hilt.components.SingletonComponent
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Module
|
||||||
|
@InstallIn(SingletonComponent::class)
|
||||||
|
abstract class UseCaseModule {
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindChangeLanguage(
|
||||||
|
changeLanguageImpl: ChangeLanguageImpl
|
||||||
|
): ChangeLanguage
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
package com.acclorite.books_history.data.local.data_store
|
||||||
|
|
||||||
|
import androidx.datastore.preferences.core.Preferences
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
interface DataStore {
|
||||||
|
suspend fun <T> getData (key: Preferences.Key<T>, defaultValue: T): Flow<T>
|
||||||
|
suspend fun <T> putData (key: Preferences.Key<T>, value: T)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
package com.acclorite.books_history.data.local.data_store
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.datastore.preferences.core.Preferences
|
||||||
|
import androidx.datastore.preferences.core.edit
|
||||||
|
import androidx.datastore.preferences.core.emptyPreferences
|
||||||
|
import androidx.datastore.preferences.preferencesDataStore
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.catch
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import java.io.IOException
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
private val Context.dataStore by preferencesDataStore("data_store")
|
||||||
|
|
||||||
|
class DataStoreImpl @Inject constructor(context: Application): DataStore {
|
||||||
|
private val dataStore = context.dataStore
|
||||||
|
|
||||||
|
override suspend fun <T> getData(key: Preferences.Key<T>, defaultValue: T): Flow<T> =
|
||||||
|
dataStore.data.catch { exception ->
|
||||||
|
if (exception is IOException){
|
||||||
|
emit(emptyPreferences())
|
||||||
|
} else{
|
||||||
|
throw exception
|
||||||
|
}
|
||||||
|
}.map { preferences ->
|
||||||
|
val result = preferences[key]?: defaultValue
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun <T> putData(key: Preferences.Key<T>, value: T) {
|
||||||
|
dataStore.edit { preferences ->
|
||||||
|
preferences[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
package com.acclorite.books_history.data.local.dto
|
||||||
|
|
||||||
|
import androidx.room.Entity
|
||||||
|
import androidx.room.PrimaryKey
|
||||||
|
import com.acclorite.books_history.domain.model.Category
|
||||||
|
|
||||||
|
@Entity
|
||||||
|
data class BookEntity(
|
||||||
|
@PrimaryKey(true) val id: Int? = null,
|
||||||
|
val title: String,
|
||||||
|
val filePath: String,
|
||||||
|
val progress: Float,
|
||||||
|
var lastOpened: Long? = null,
|
||||||
|
val image: ByteArray = byteArrayOf(),
|
||||||
|
val category: Category
|
||||||
|
) {
|
||||||
|
override fun equals(other: Any?): Boolean {
|
||||||
|
if (this === other) return true
|
||||||
|
if (javaClass != other?.javaClass) return false
|
||||||
|
|
||||||
|
other as BookEntity
|
||||||
|
|
||||||
|
return image.contentEquals(other.image)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun hashCode(): Int {
|
||||||
|
return image.contentHashCode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
package com.acclorite.books_history.data.local.room
|
||||||
|
|
||||||
|
import androidx.room.Dao
|
||||||
|
import androidx.room.Delete
|
||||||
|
import androidx.room.Insert
|
||||||
|
import androidx.room.OnConflictStrategy
|
||||||
|
import androidx.room.Query
|
||||||
|
import androidx.room.Update
|
||||||
|
import com.acclorite.books_history.data.local.dto.BookEntity
|
||||||
|
|
||||||
|
@Dao
|
||||||
|
interface BookDao {
|
||||||
|
|
||||||
|
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||||
|
suspend fun insertBooks(
|
||||||
|
books: List<BookEntity>
|
||||||
|
)
|
||||||
|
|
||||||
|
@Query(
|
||||||
|
"""
|
||||||
|
SELECT *
|
||||||
|
FROM bookentity
|
||||||
|
WHERE LOWER(title) LIKE '%' || LOWER(:query) || '%'
|
||||||
|
"""
|
||||||
|
)
|
||||||
|
suspend fun searchBooks(query: String): List<BookEntity>
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
suspend fun deleteBooks(books: List<BookEntity>)
|
||||||
|
|
||||||
|
@Update
|
||||||
|
suspend fun updateBooks(books: List<BookEntity>)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
package com.acclorite.books_history.data.local.room
|
||||||
|
|
||||||
|
import androidx.room.Database
|
||||||
|
import androidx.room.RoomDatabase
|
||||||
|
import com.acclorite.books_history.data.local.dto.BookEntity
|
||||||
|
|
||||||
|
@Database(
|
||||||
|
entities = [BookEntity::class],
|
||||||
|
version = 1,
|
||||||
|
exportSchema = false
|
||||||
|
)
|
||||||
|
abstract class BookDatabase : RoomDatabase() {
|
||||||
|
abstract val dao: BookDao
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
package com.acclorite.books_history.data.mapper
|
||||||
|
|
||||||
|
import com.acclorite.books_history.data.local.dto.BookEntity
|
||||||
|
import com.acclorite.books_history.domain.model.Book
|
||||||
|
|
||||||
|
interface BookMapper {
|
||||||
|
fun Book.toBookEntity(): BookEntity
|
||||||
|
|
||||||
|
fun BookEntity.toBook(): Book
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
package com.acclorite.books_history.data.mapper
|
||||||
|
|
||||||
|
import com.acclorite.books_history.data.local.dto.BookEntity
|
||||||
|
import com.acclorite.books_history.domain.model.Book
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class BookMapperImpl @Inject constructor(): BookMapper {
|
||||||
|
override fun Book.toBookEntity(): BookEntity {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun BookEntity.toBook(): Book {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
package com.acclorite.books_history.data.repository
|
||||||
|
|
||||||
|
import androidx.datastore.preferences.core.Preferences
|
||||||
|
import com.acclorite.books_history.data.local.data_store.DataStore
|
||||||
|
import com.acclorite.books_history.data.local.room.BookDao
|
||||||
|
import com.acclorite.books_history.data.mapper.toBookEntity
|
||||||
|
import com.acclorite.books_history.domain.model.Book
|
||||||
|
import com.acclorite.books_history.domain.repository.BookRepository
|
||||||
|
import com.acclorite.books_history.util.Resource
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import javax.inject.Inject
|
||||||
|
import javax.inject.Singleton
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
class BookRepositoryImpl @Inject constructor(
|
||||||
|
private val database: BookDao,
|
||||||
|
private val dataStore: DataStore
|
||||||
|
): BookRepository {
|
||||||
|
|
||||||
|
override suspend fun getBooks(query: String): Flow<Resource<List<Book>>> {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun insertBooks(books: List<Book>) {
|
||||||
|
database.insertBooks(books.map { it.toBookEntity() })
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun updateBooks(books: List<Book>) {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun deleteBooks(books: List<Book>) {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun <T> retrieveDataFromDataStore(
|
||||||
|
key: Preferences.Key<T>,
|
||||||
|
defaultValue: T
|
||||||
|
): Flow<T> {
|
||||||
|
return dataStore.getData(key, defaultValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun <T> putDataToDataStore(key: Preferences.Key<T>, value: T) {
|
||||||
|
dataStore.putData(key, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
package com.acclorite.books_history.data.use_case
|
||||||
|
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import com.acclorite.books_history.domain.use_case.ChangeLanguage
|
||||||
|
import java.util.Locale
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
class ChangeLanguageImpl @Inject constructor() : ChangeLanguage {
|
||||||
|
override fun execute(language: String, activity: ComponentActivity) {
|
||||||
|
val config = activity.resources.configuration
|
||||||
|
val resources = activity.resources
|
||||||
|
val locale = Locale(language)
|
||||||
|
|
||||||
|
Locale.setDefault(locale)
|
||||||
|
config.setLocale(locale)
|
||||||
|
|
||||||
|
activity.createConfigurationContext(config)
|
||||||
|
resources.updateConfiguration(
|
||||||
|
config,
|
||||||
|
resources.displayMetrics
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
package com.acclorite.books_history.domain.model
|
||||||
|
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import java.io.File
|
||||||
|
|
||||||
|
data class Book(
|
||||||
|
val id: Int,
|
||||||
|
val title: String,
|
||||||
|
val author: String,
|
||||||
|
val description: String?,
|
||||||
|
val text: List<FormattedLine> = emptyList(),
|
||||||
|
val progress: Float,
|
||||||
|
val file: File?,
|
||||||
|
val lastOpened: Long?,
|
||||||
|
val category: Category,
|
||||||
|
val coverImage: Bitmap?
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
package com.acclorite.books_history.domain.model
|
||||||
|
|
||||||
|
enum class Category {
|
||||||
|
READING, ALREADY_READ, PLANNING, DROPPED
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
package com.acclorite.books_history.domain.model
|
||||||
|
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
|
import androidx.compose.ui.text.TextStyle
|
||||||
|
import androidx.compose.ui.unit.TextUnit
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
@Immutable
|
||||||
|
data class FormattedLine(
|
||||||
|
val line: String,
|
||||||
|
val style: TextStyle = TextStyle(),
|
||||||
|
val textSize: TextUnit = 0.sp,
|
||||||
|
val id: String = UUID.randomUUID().toString()
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
package com.acclorite.books_history.domain.repository
|
||||||
|
|
||||||
|
import androidx.datastore.preferences.core.Preferences
|
||||||
|
import com.acclorite.books_history.domain.model.Book
|
||||||
|
import com.acclorite.books_history.util.Resource
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
|
||||||
|
interface BookRepository {
|
||||||
|
|
||||||
|
suspend fun getBooks(
|
||||||
|
query: String
|
||||||
|
): Flow<Resource<List<Book>>>
|
||||||
|
|
||||||
|
suspend fun insertBooks(
|
||||||
|
books: List<Book>
|
||||||
|
)
|
||||||
|
|
||||||
|
suspend fun updateBooks(
|
||||||
|
books: List<Book>
|
||||||
|
)
|
||||||
|
|
||||||
|
suspend fun deleteBooks(
|
||||||
|
books: List<Book>
|
||||||
|
)
|
||||||
|
|
||||||
|
suspend fun <T> retrieveDataFromDataStore(
|
||||||
|
key: Preferences.Key<T>,
|
||||||
|
defaultValue: T
|
||||||
|
): Flow<T>
|
||||||
|
|
||||||
|
suspend fun <T> putDataToDataStore(
|
||||||
|
key: Preferences.Key<T>,
|
||||||
|
value: T
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
package com.acclorite.books_history.domain.use_case
|
||||||
|
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.annotation.Size
|
||||||
|
|
||||||
|
interface ChangeLanguage {
|
||||||
|
fun execute(
|
||||||
|
@Size(max = 2) language: String,
|
||||||
|
activity: ComponentActivity
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
package com.acclorite.books_history.presentation
|
||||||
|
|
||||||
|
import androidx.lifecycle.SavedStateHandle
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import com.acclorite.books_history.domain.repository.BookRepository
|
||||||
|
import com.acclorite.books_history.domain.use_case.ChangeLanguage
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores all variables such as theme, language etc
|
||||||
|
*/
|
||||||
|
@HiltViewModel
|
||||||
|
class MainViewModel @Inject constructor(
|
||||||
|
private val stateHandle: SavedStateHandle,
|
||||||
|
private val repository: BookRepository,
|
||||||
|
|
||||||
|
private val changeLanguage: ChangeLanguage
|
||||||
|
) : ViewModel() {
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,208 @@
|
||||||
|
package com.acclorite.books_history.presentation
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.os.Bundle
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.activity.compose.BackHandler
|
||||||
|
import androidx.activity.viewModels
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.animation.EnterTransition
|
||||||
|
import androidx.compose.animation.ExitTransition
|
||||||
|
import androidx.compose.animation.core.tween
|
||||||
|
import androidx.compose.animation.fadeIn
|
||||||
|
import androidx.compose.animation.fadeOut
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.mutableStateListOf
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.lifecycle.AbstractSavedStateViewModelFactory
|
||||||
|
import androidx.lifecycle.SavedStateHandle
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.createSavedStateHandle
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import androidx.savedstate.SavedStateRegistryOwner
|
||||||
|
import dagger.assisted.Assisted
|
||||||
|
import dagger.assisted.AssistedFactory
|
||||||
|
import dagger.assisted.AssistedInject
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import dagger.hilt.android.lifecycle.withCreationCallback
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
|
||||||
|
private const val CURRENT_SCREEN = "current_screen"
|
||||||
|
private const val BACKSTACK = "back_stack"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* All screens are listed here, later each screen will be passed as a param for [Navigator.composable] function.
|
||||||
|
*/
|
||||||
|
enum class Screen {
|
||||||
|
LIBRARY,
|
||||||
|
HISTORY,
|
||||||
|
BROWSE,
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Argument(
|
||||||
|
val key: String,
|
||||||
|
val arg: Any?
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigator. Using for navigation between screens.
|
||||||
|
*
|
||||||
|
* [Navigator.currentScreen] param represents current [Screen].
|
||||||
|
* [Navigator.navigate] navigates to [Screen] passed as param.
|
||||||
|
*/
|
||||||
|
@HiltViewModel(assistedFactory = Navigator.Factory::class)
|
||||||
|
class Navigator @AssistedInject constructor(
|
||||||
|
private val savedStateHandle: SavedStateHandle,
|
||||||
|
@Assisted startScreen: Screen
|
||||||
|
) : ViewModel() {
|
||||||
|
|
||||||
|
private val currentScreen = savedStateHandle.getStateFlow(CURRENT_SCREEN, startScreen)
|
||||||
|
private val backStack = savedStateHandle.getStateFlow(BACKSTACK, mutableListOf<Screen>())
|
||||||
|
private val arguments = mutableStateListOf<Argument>()
|
||||||
|
|
||||||
|
private fun putArgument(argument: Argument) {
|
||||||
|
var found = false
|
||||||
|
|
||||||
|
for ((index, arg) in arguments.withIndex()) {
|
||||||
|
if (arg.key == argument.key) {
|
||||||
|
arguments[index] = argument
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!found) {
|
||||||
|
arguments.add(argument)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun retrieveArgument(key: String): Any? {
|
||||||
|
for (arg in arguments) {
|
||||||
|
if (arg.key == key) {
|
||||||
|
return arg.arg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun navigate(screen: Screen, vararg args: Argument) =
|
||||||
|
viewModelScope.launch(Dispatchers.Default) {
|
||||||
|
backStack.value.add(currentScreen.value)
|
||||||
|
|
||||||
|
args.forEach {
|
||||||
|
putArgument(it)
|
||||||
|
}
|
||||||
|
|
||||||
|
savedStateHandle[CURRENT_SCREEN] = screen
|
||||||
|
}
|
||||||
|
|
||||||
|
fun navigateBack() = viewModelScope.launch(Dispatchers.Default) {
|
||||||
|
if (canGoBack()) {
|
||||||
|
savedStateHandle[CURRENT_SCREEN] = backStack.value.last()
|
||||||
|
backStack.value.removeLast()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun canGoBack(): Boolean {
|
||||||
|
return backStack.value.isNotEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearBackStack() {
|
||||||
|
backStack.value.clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getCurrentScreen(): StateFlow<Screen> {
|
||||||
|
return currentScreen
|
||||||
|
}
|
||||||
|
|
||||||
|
fun init(startScreen: Screen) {
|
||||||
|
savedStateHandle[CURRENT_SCREEN] = startScreen
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Animated Screen. Using in [NavigationHost]. Be sure to not use the same [screen] parameter twice, it'll override the highest one in your code.
|
||||||
|
*
|
||||||
|
* @param screen The [Screen] that represents [content].
|
||||||
|
* @param enterAnim Enter Animation.
|
||||||
|
* @param exitAnim Exit Animation.
|
||||||
|
* @param content The Screen content to show when [Navigator.currentScreen] equals [screen].
|
||||||
|
*/
|
||||||
|
@SuppressLint("ComposableNaming")
|
||||||
|
@Composable
|
||||||
|
fun composable(
|
||||||
|
screen: Screen,
|
||||||
|
enterAnim: EnterTransition = fadeIn(tween(300)),
|
||||||
|
exitAnim: ExitTransition = fadeOut(tween(300)),
|
||||||
|
content: @Composable () -> Unit
|
||||||
|
) {
|
||||||
|
AnimatedVisibility(
|
||||||
|
visible = getCurrentScreen().collectAsState().value == screen,
|
||||||
|
enter = enterAnim,
|
||||||
|
exit = exitAnim,
|
||||||
|
label = screen.toString()
|
||||||
|
) {
|
||||||
|
content()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@AssistedFactory
|
||||||
|
interface Factory {
|
||||||
|
fun create(startScreen: Screen): Navigator
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigation Host. Contains [Navigator.composable]s in [content].
|
||||||
|
*
|
||||||
|
* @param startScreen Start Screen. Be sure to pass [Screen] that uses in one of your [Navigator.composable]s.
|
||||||
|
* @param colorBetweenAnimations The color, that using between animations, recommended to set this to background or navigation bar color.
|
||||||
|
* @param content Content of the [NavigationHost]. Highly recommended to use [Navigator.composable].
|
||||||
|
*/
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun NavigationHost(
|
||||||
|
startScreen: Screen,
|
||||||
|
activity: ComponentActivity,
|
||||||
|
colorBetweenAnimations: Color = MaterialTheme.colorScheme.surface,
|
||||||
|
content: @Composable Navigator.() -> Unit
|
||||||
|
) {
|
||||||
|
activity.apply {
|
||||||
|
val navigator by viewModels<Navigator>(
|
||||||
|
extrasProducer = {
|
||||||
|
defaultViewModelCreationExtras.withCreationCallback<Navigator.Factory> { factory ->
|
||||||
|
factory.create(startScreen)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
BackHandler {
|
||||||
|
if (navigator.canGoBack()) {
|
||||||
|
navigator.navigateBack()
|
||||||
|
} else {
|
||||||
|
activity.finishAffinity()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Box(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.background(colorBetweenAnimations)
|
||||||
|
)
|
||||||
|
|
||||||
|
content(navigator)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
package com.acclorite.books_history.util
|
||||||
|
|
||||||
|
import android.app.Application
|
||||||
|
import dagger.hilt.android.HiltAndroidApp
|
||||||
|
|
||||||
|
@HiltAndroidApp
|
||||||
|
class BookApplication : Application()
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
package com.acclorite.books_history.util
|
||||||
|
|
||||||
|
object Constants {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
package com.acclorite.books_history.util
|
||||||
|
|
||||||
|
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||||
|
|
||||||
|
object DataStoreConstants {
|
||||||
|
val LANGUAGE = stringPreferencesKey("language")
|
||||||
|
val THEME = stringPreferencesKey("theme")
|
||||||
|
val DARK_THEME = stringPreferencesKey("dark_theme")
|
||||||
|
val GUIDE = stringPreferencesKey("guide")
|
||||||
|
|
||||||
|
val BACKGROUND_COLOR = stringPreferencesKey("background_color")
|
||||||
|
val FONT_COLOR = stringPreferencesKey("font_color")
|
||||||
|
val FONT = stringPreferencesKey("font")
|
||||||
|
val FONT_STYLE = stringPreferencesKey("font_style")
|
||||||
|
val FONT_SIZE = stringPreferencesKey("font_size")
|
||||||
|
val LINE_HEIGHT = stringPreferencesKey("line_height")
|
||||||
|
val PARAGRAPH_HEIGHT = stringPreferencesKey("paragraph_height")
|
||||||
|
val PARAGRAPH_INDENTATION = stringPreferencesKey("paragraph_indentation")
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
package com.acclorite.books_history.util
|
||||||
|
|
||||||
|
sealed class Resource <T>(val data: T? = null, val message: String? = null) {
|
||||||
|
class Success<T>(data: T?): Resource<T>(data)
|
||||||
|
class Error<T>(message: String, data: T? = null): Resource<T>(data, message)
|
||||||
|
class Loading<T>(val isLoading: Boolean = true): Resource<T>()
|
||||||
|
}
|
||||||
26
app/src/main/java/com/acclorite/books_history/util/UIText.kt
Normal file
26
app/src/main/java/com/acclorite/books_history/util/UIText.kt
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
package com.acclorite.books_history.util
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.annotation.StringRes
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
|
||||||
|
sealed class UIText {
|
||||||
|
data class StringValue(val value: String): UIText()
|
||||||
|
class StringResource(@StringRes val resId: Int, vararg val args: Any): UIText()
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun asString(): String {
|
||||||
|
return when(this) {
|
||||||
|
is StringValue -> value
|
||||||
|
is StringResource -> stringResource(resId, *args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun asString(context: Context): String {
|
||||||
|
return when(this) {
|
||||||
|
is StringValue -> value
|
||||||
|
is StringResource -> context.getString(resId, *args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
<resources>
|
<resources>
|
||||||
<!-- Basic Strings -->
|
<!-- Basic Strings -->
|
||||||
<string name="app_name">Воскресіння Історії Книги</string>
|
<string name="app_name">Історія Книги: Воскресіння</string>
|
||||||
|
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
<resources>
|
<resources>
|
||||||
<!-- Basic Strings -->
|
<!-- Basic Strings -->
|
||||||
<string name="app_version" translatable="false">0.0.1</string>
|
<string name="app_version" translatable="false">0.0.1</string>
|
||||||
<string name="app_name">Book\'s History Resurrection</string>
|
<string name="app_name">Book\'s History: Resurrection</string>
|
||||||
|
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
@ -3,10 +3,5 @@ plugins {
|
||||||
id("com.android.application") version "8.2.0-rc03" apply false
|
id("com.android.application") version "8.2.0-rc03" apply false
|
||||||
id("org.jetbrains.kotlin.android") version "1.9.21" apply false
|
id("org.jetbrains.kotlin.android") version "1.9.21" apply false
|
||||||
id("com.google.devtools.ksp") version "1.9.21-1.0.16" apply false
|
id("com.google.devtools.ksp") version "1.9.21-1.0.16" apply false
|
||||||
}
|
id("com.google.dagger.hilt.android") version "2.50" apply false
|
||||||
|
|
||||||
buildscript {
|
|
||||||
dependencies {
|
|
||||||
classpath ("com.google.dagger:hilt-android-gradle-plugin:latest.release")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue