0.1.0 - Basic Setting

This commit is contained in:
Acclorite 2023-12-30 21:13:44 +02:00
parent 34d3e5c38a
commit 29a75aa18a
32 changed files with 744 additions and 16 deletions

View 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
View 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>

View file

@ -2,6 +2,8 @@ plugins {
id("com.android.application")
id("org.jetbrains.kotlin.android")
id("com.google.devtools.ksp")
id("com.google.dagger.hilt.android")
kotlin("kapt")
}
@ -47,6 +49,7 @@ android {
packaging {
resources {
excludes += "/META-INF/{AL2.0,LGPL2.1}"
excludes += "/META-INF/gradle/incremental.annotation.processors"
}
}
}
@ -77,16 +80,13 @@ dependencies {
// All dependencies
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 ("com.google.accompanist:accompanist-swiperefresh:0.24.2-alpha")
//Dagger - Hilt
implementation ("com.google.dagger:hilt-compiler:latest.release")
implementation ("com.google.dagger:hilt-android:latest.release")
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")
// Dagger
implementation("com.google.dagger:hilt-android:2.50")
kapt("com.google.dagger:hilt-android-compiler:2.50")
// Room
implementation ("androidx.room:room-runtime:2.6.1")
@ -94,4 +94,7 @@ dependencies {
// Kotlin Extensions and Coroutines support for Room
implementation ("androidx.room:room-ktx:2.6.1")
// Datastore
implementation ("androidx.datastore:datastore-preferences:latest.release")
}

View file

@ -3,6 +3,7 @@
xmlns:tools="http://schemas.android.com/tools">
<application
android:name=".util.BookApplication"
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"

View file

@ -3,14 +3,35 @@ package com.acclorite.books_history
import android.os.Bundle
import androidx.activity.ComponentActivity
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 dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint
class MainActivity : ComponentActivity() {
private val mainViewModel: MainViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContent {
BooksHistoryResurrectionTheme {
NavigationHost(startScreen = Screen.LIBRARY, this) {
}
}
}
}

View file

@ -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
}
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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)
}

View file

@ -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
}
}
}

View file

@ -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()
}
}

View file

@ -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>)
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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")
}
}

View file

@ -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)
}
}

View file

@ -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
)
}
}

View file

@ -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?
)

View file

@ -0,0 +1,5 @@
package com.acclorite.books_history.domain.model
enum class Category {
READING, ALREADY_READ, PLANNING, DROPPED
}

View file

@ -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()
)

View file

@ -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
)
}

View file

@ -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
)
}

View file

@ -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() {
}

View file

@ -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)
}
}

View file

@ -0,0 +1,7 @@
package com.acclorite.books_history.util
import android.app.Application
import dagger.hilt.android.HiltAndroidApp
@HiltAndroidApp
class BookApplication : Application()

View file

@ -0,0 +1,5 @@
package com.acclorite.books_history.util
object Constants {
}

View file

@ -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")
}

View file

@ -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>()
}

View 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)
}
}
}

View file

@ -1,6 +1,5 @@
<resources>
<!-- Basic Strings -->
<string name="app_name">Воскресіння Історії Книги</string>
<string name="app_name">Історія Книги: Воскресіння</string>
</resources>

View file

@ -1,7 +1,7 @@
<resources>
<!-- Basic Strings -->
<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>

View file

@ -3,10 +3,5 @@ plugins {
id("com.android.application") version "8.2.0-rc03" 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
}
buildscript {
dependencies {
classpath ("com.google.dagger:hilt-android-gradle-plugin:latest.release")
}
id("com.google.dagger.hilt.android") version "2.50" apply false
}