0.8.0 - History Screen and Project Name change

This commit is contained in:
acclorite 2024-03-04 19:12:00 +02:00
parent 0b3a31a183
commit 5a966e9d9d
171 changed files with 3027 additions and 1877 deletions

2
.idea/.name generated
View file

@ -1 +1 @@
Book's History Resurrection Book's Story

View file

@ -4,19 +4,18 @@
<value> <value>
<entry key="app"> <entry key="app">
<State> <State>
<runningDeviceTargetSelectedWithDropDown> <targetSelectedWithDropDown>
<Target> <Target>
<type value="RUNNING_DEVICE_TARGET" /> <type value="QUICK_BOOT_TARGET" />
<deviceKey> <deviceKey>
<Key> <Key>
<type value="VIRTUAL_DEVICE_PATH" /> <type value="VIRTUAL_DEVICE_PATH" />
<value value="$USER_HOME$/.android/avd/Phone_13_.avd" /> <value value="C:\Users\Administrator\.android\avd\Phone_33_.avd" />
</Key> </Key>
</deviceKey> </deviceKey>
</Target> </Target>
</runningDeviceTargetSelectedWithDropDown> </targetSelectedWithDropDown>
<timeTargetWasSelectedWithDropDown value="2024-02-22T17:49:53.931710327Z" /> <timeTargetWasSelectedWithDropDown value="2024-02-29T15:05:04.852701900Z" />
<multipleDevicesSelectedInDropDown value="true" />
<targetsSelectedWithDialog> <targetsSelectedWithDialog>
<Target> <Target>
<type value="QUICK_BOOT_TARGET" /> <type value="QUICK_BOOT_TARGET" />

2
.idea/gradle.xml generated
View file

@ -5,7 +5,7 @@
<option name="linkedExternalProjectsSettings"> <option name="linkedExternalProjectsSettings">
<GradleProjectSettings> <GradleProjectSettings>
<option name="externalProjectPath" value="$PROJECT_DIR$" /> <option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="jbr-17" /> <option name="gradleJvm" value="jbr-17 (2)" />
<option name="modules"> <option name="modules">
<set> <set>
<option value="$PROJECT_DIR$" /> <option value="$PROJECT_DIR$" />

2
.idea/vcs.xml generated
View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="VcsDirectoryMappings"> <component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" /> <mapping directory="" vcs="Git" />
</component> </component>
</project> </project>

View file

@ -10,15 +10,15 @@ plugins {
} }
android { android {
namespace = "com.acclorite.books_history" namespace = "ua.acclorite.book_story"
compileSdk = 34 compileSdk = 34
defaultConfig { defaultConfig {
applicationId = "com.acclorite.books_history" applicationId = "ua.acclorite.book_story"
minSdk = 26 minSdk = 26
targetSdk = 34 targetSdk = 34
versionCode = 1 versionCode = 1
versionName = "0.6.0" versionName = "0.8.0"
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables { vectorDrawables {
@ -140,4 +140,7 @@ dependencies {
} }
implementation("org.slf4j:slf4j-android:latest.release") implementation("org.slf4j:slf4j-android:latest.release")
implementation("org.jsoup:jsoup:latest.release") implementation("org.jsoup:jsoup:latest.release")
implementation("androidx.appcompat:appcompat:latest.release")
implementation("androidx.appcompat:appcompat-resources:latest.release")
} }

View file

@ -1,22 +0,0 @@
package com.acclorite.books_history
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import org.junit.Assert.*
import org.junit.Test
import org.junit.runner.RunWith
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.acclorite.books_history", appContext.packageName)
}
}

View file

@ -17,9 +17,10 @@
android:theme="@style/Theme.Start.Splash" android:theme="@style/Theme.Start.Splash"
android:enableOnBackInvokedCallback="true" android:enableOnBackInvokedCallback="true"
tools:targetApi="tiramisu"> tools:targetApi="tiramisu">
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:configChanges="screenSize|screenLayout|smallestScreenSize|locale|density" android:configChanges="screenSize|screenLayout|smallestScreenSize|locale|density|uiMode"
android:windowSoftInputMode="adjustResize" android:windowSoftInputMode="adjustResize"
android:exported="true" android:exported="true"
android:theme="@style/Theme.Start.Splash"> android:theme="@style/Theme.Start.Splash">
@ -28,6 +29,16 @@
<category android:name="android.intent.category.LAUNCHER" /> <category android:name="android.intent.category.LAUNCHER" />
</intent-filter> </intent-filter>
</activity> </activity>
<service
android:name="androidx.appcompat.app.AppLocalesMetadataHolderService"
android:enabled="false"
android:exported="false">
<meta-data
android:name="autoStoreLocales"
android:value="true" />
</service>
</application> </application>
</manifest> </manifest>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

View file

@ -1,35 +0,0 @@
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.mapper.BookMapper
import com.acclorite.books_history.data.mapper.BookMapperImpl
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
@Binds
@Singleton
abstract fun bindBookMapper(
bookMapperImpl: BookMapperImpl
): BookMapper
}

View file

@ -1,14 +0,0 @@
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

@ -1,10 +0,0 @@
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 {
suspend fun toBookEntity(book: Book): BookEntity
suspend fun toBook(bookEntity: BookEntity): Book
}

View file

@ -1,9 +0,0 @@
package com.acclorite.books_history.data.parser
import com.acclorite.books_history.domain.model.Book
import java.io.File
interface FileParser {
suspend fun parse(file: File): Book?
}

View file

@ -1,8 +0,0 @@
package com.acclorite.books_history.data.parser
import com.acclorite.books_history.domain.model.StringWithId
import java.io.File
interface TextParser {
suspend fun parse(file: File): List<StringWithId>
}

View file

@ -1,12 +0,0 @@
package com.acclorite.books_history.domain.use_case
import com.acclorite.books_history.domain.model.Book
import com.acclorite.books_history.domain.repository.BookRepository
import javax.inject.Inject
class DeleteBooks @Inject constructor(private val repository: BookRepository) {
suspend fun execute(books: List<Book>) {
repository.deleteBooks(books)
}
}

View file

@ -1,128 +0,0 @@
package com.acclorite.books_history.presentation.components
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.spring
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.statusBars
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
import com.acclorite.books_history.ui.DefaultTransition
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AnimatedTopAppBar(
containerColor: Color = MaterialTheme.colorScheme.surface,
scrolledContainerColor: Color?,
scrollBehavior: TopAppBarScrollBehavior?,
isTopBarScrolled: Boolean?,
content1Visibility: Boolean,
content1NavigationIcon: @Composable () -> Unit,
content1Title: @Composable () -> Unit,
content1Actions: @Composable RowScope.() -> Unit,
content2Visibility: Boolean,
content2NavigationIcon: @Composable () -> Unit,
content2Title: @Composable () -> Unit,
content2Actions: @Composable RowScope.() -> Unit,
content3Visibility: Boolean,
content3NavigationIcon: @Composable () -> Unit,
content3Title: @Composable () -> Unit,
content3Actions: @Composable RowScope.() -> Unit
) {
val color =
if (isTopBarScrolled == true && scrolledContainerColor != null) scrolledContainerColor
else containerColor
val animatedColor by animateColorAsState(
targetValue = color,
animationSpec = spring(stiffness = Spring.StiffnessMediumLow),
label = "TopAppBar color animation"
)
if (scrollBehavior != null && scrolledContainerColor != null) {
val isScrolled = scrollBehavior.state.overlappedFraction > 0.01f
val statusBarPadding = with(LocalDensity.current) {
WindowInsets.statusBars.getTop(LocalDensity.current).toDp()
}
Box(
modifier = Modifier
.fillMaxWidth()
.height(64.dp + statusBarPadding)
.background(
if (isScrolled) scrolledContainerColor
else containerColor
)
)
}
Box(modifier = Modifier.fillMaxWidth()) {
DefaultTransition(visible = content1Visibility) {
TopAppBar(
navigationIcon = content1NavigationIcon,
title = content1Title,
actions = content1Actions,
scrollBehavior = scrollBehavior,
colors = TopAppBarDefaults.topAppBarColors(
containerColor = animatedColor,
scrolledContainerColor = scrolledContainerColor ?: Color.Transparent
)
)
}
DefaultTransition(visible = content2Visibility) {
TopAppBar(
navigationIcon = content2NavigationIcon,
title = content2Title,
actions = content2Actions,
scrollBehavior = scrollBehavior,
colors = TopAppBarDefaults.topAppBarColors(
containerColor = animatedColor,
scrolledContainerColor = scrolledContainerColor ?: Color.Transparent
)
)
}
DefaultTransition(visible = content3Visibility) {
TopAppBar(
navigationIcon = content3NavigationIcon,
title = content3Title,
actions = content3Actions,
scrollBehavior = scrollBehavior,
colors = TopAppBarDefaults.topAppBarColors(
containerColor = animatedColor,
scrolledContainerColor = scrolledContainerColor ?: Color.Transparent
)
)
}
}
}

View file

@ -1,16 +0,0 @@
package com.acclorite.books_history.presentation.screens.history
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.hilt.navigation.compose.hiltViewModel
import com.acclorite.books_history.presentation.Navigator
import com.acclorite.books_history.presentation.screens.history.data.HistoryViewModel
@Composable
fun HistoryScreen(
viewModel: HistoryViewModel = hiltViewModel(),
navigator: Navigator
) {
val state by viewModel.state.collectAsState()
}

View file

@ -1,3 +0,0 @@
package com.acclorite.books_history.presentation.screens.history.data
sealed class HistoryEvent

View file

@ -1,5 +0,0 @@
package com.acclorite.books_history.presentation.screens.history.data
data class HistoryState(
val foo: Int = 0
)

View file

@ -1,36 +0,0 @@
package com.acclorite.books_history.presentation.screens.history.data
import androidx.lifecycle.ViewModel
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
import javax.inject.Inject
@HiltViewModel
class HistoryViewModel @Inject constructor(
) : ViewModel() {
private val _state = MutableStateFlow(HistoryState())
val state = _state.asStateFlow()
fun onEvent(event: HistoryEvent) {
//
}
}

View file

@ -1,50 +0,0 @@
package com.acclorite.books_history.presentation.screens.reader.components
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@Composable
fun ReaderSettingsBottomSheetItem(
modifier: Modifier = Modifier,
title: String,
description: String,
verticalPadding: Dp = 8.dp,
onClick: () -> Unit
) {
Column(
modifier
.fillMaxWidth()
.clickable {
onClick()
}
.padding(horizontal = 18.dp, vertical = verticalPadding),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.Start
) {
Text(
title,
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurface,
fontSize = 18.sp
)
Spacer(modifier = Modifier.height(2.dp))
Text(
description,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
}

View file

@ -1,50 +0,0 @@
package com.acclorite.books_history.presentation.screens.settings.components
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
@Composable
fun SettingsOptionItem(
modifier: Modifier = Modifier,
title: String,
description: String,
verticalPadding: Dp = 8.dp,
onClick: () -> Unit
) {
Column(
modifier
.fillMaxWidth()
.clickable {
onClick()
}
.padding(horizontal = 16.dp, vertical = verticalPadding),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.Start
) {
Text(
title,
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurface,
fontSize = 18.sp
)
Spacer(modifier = Modifier.height(2.dp))
Text(
description,
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
}
}

View file

@ -1,11 +1,15 @@
package com.acclorite.books_history package ua.acclorite.book_story
import android.content.res.Configuration import android.content.res.Configuration
import android.os.Bundle import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge import androidx.activity.enableEdgeToEdge
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.slideInHorizontally
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
@ -15,33 +19,29 @@ import androidx.compose.material3.Scaffold
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen
import androidx.core.view.WindowCompat import androidx.core.view.WindowCompat
import com.acclorite.books_history.domain.model.Book
import com.acclorite.books_history.presentation.NavigationHost
import com.acclorite.books_history.presentation.Screen
import com.acclorite.books_history.presentation.components.BottomNavigationBar
import com.acclorite.books_history.presentation.data.MainEvent
import com.acclorite.books_history.presentation.data.MainViewModel
import com.acclorite.books_history.presentation.screens.book_info.BookInfoScreen
import com.acclorite.books_history.presentation.screens.browse.BrowseScreen
import com.acclorite.books_history.presentation.screens.history.HistoryScreen
import com.acclorite.books_history.presentation.screens.library.LibraryScreen
import com.acclorite.books_history.presentation.screens.reader.ReaderScreen
import com.acclorite.books_history.presentation.screens.settings.SettingsScreen
import com.acclorite.books_history.presentation.screens.settings.nested.appearance.AppearanceSettings
import com.acclorite.books_history.presentation.screens.settings.nested.general.GeneralSettings
import com.acclorite.books_history.ui.BooksHistoryResurrectionTheme
import com.acclorite.books_history.ui.DefaultTransition
import com.acclorite.books_history.ui.Transitions
import com.acclorite.books_history.ui.isDark
import com.google.accompanist.permissions.ExperimentalPermissionsApi
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import ua.acclorite.book_story.domain.model.Book
import ua.acclorite.book_story.presentation.NavigationHost
import ua.acclorite.book_story.presentation.Screen
import ua.acclorite.book_story.presentation.components.BottomNavigationBar
import ua.acclorite.book_story.presentation.data.MainEvent
import ua.acclorite.book_story.presentation.data.MainViewModel
import ua.acclorite.book_story.presentation.screens.book_info.BookInfoScreen
import ua.acclorite.book_story.presentation.screens.browse.BrowseScreen
import ua.acclorite.book_story.presentation.screens.history.HistoryScreen
import ua.acclorite.book_story.presentation.screens.library.LibraryScreen
import ua.acclorite.book_story.presentation.screens.reader.ReaderScreen
import ua.acclorite.book_story.presentation.screens.settings.SettingsScreen
import ua.acclorite.book_story.presentation.screens.settings.nested.appearance.AppearanceSettings
import ua.acclorite.book_story.presentation.screens.settings.nested.general.GeneralSettings
import ua.acclorite.book_story.ui.BooksHistoryResurrectionTheme
import ua.acclorite.book_story.ui.Transitions
import ua.acclorite.book_story.ui.isDark
@OptIn(ExperimentalPermissionsApi::class)
@AndroidEntryPoint @AndroidEntryPoint
class MainActivity : ComponentActivity() { class MainActivity : AppCompatActivity() {
private val mainViewModel: MainViewModel by viewModels() private val mainViewModel: MainViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@ -70,14 +70,17 @@ class MainActivity : ComponentActivity() {
if (!updating) { if (!updating) {
NavigationHost(startScreen = Screen.LIBRARY) { NavigationHost(startScreen = Screen.LIBRARY) {
val currentScreen by this.getCurrentScreen().collectAsState() val currentScreen by this.getCurrentScreen().collectAsState()
DefaultTransition( AnimatedVisibility(
visible = currentScreen == Screen.LIBRARY || visible = currentScreen == Screen.LIBRARY ||
currentScreen == Screen.HISTORY || currentScreen == Screen.HISTORY ||
currentScreen == Screen.BROWSE currentScreen == Screen.BROWSE,
enter = fadeIn(tween(200)) +
slideInHorizontally(tween(200)) { it / 15 },
exit = Transitions.SlidingTransitionOut
) { ) {
Scaffold( Scaffold(
bottomBar = { bottomBar = {
BottomNavigationBar(navigator = this) BottomNavigationBar(navigator = this@NavigationHost)
}, },
containerColor = MaterialTheme.colorScheme.surface containerColor = MaterialTheme.colorScheme.surface
) { ) {
@ -122,8 +125,7 @@ class MainActivity : ComponentActivity() {
exitAnim = Transitions.SlidingTransitionOut exitAnim = Transitions.SlidingTransitionOut
) { ) {
ReaderScreen( ReaderScreen(
navigator = this@NavigationHost, navigator = this@NavigationHost
mainViewModel = mainViewModel
) )
} }
@ -145,7 +147,6 @@ class MainActivity : ComponentActivity() {
exitAnim = Transitions.SlidingTransitionOut exitAnim = Transitions.SlidingTransitionOut
) { ) {
GeneralSettings( GeneralSettings(
mainViewModel = mainViewModel,
navigator = this@NavigationHost navigator = this@NavigationHost
) )
} }
@ -155,7 +156,6 @@ class MainActivity : ComponentActivity() {
exitAnim = Transitions.SlidingTransitionOut exitAnim = Transitions.SlidingTransitionOut
) { ) {
AppearanceSettings( AppearanceSettings(
mainViewModel = mainViewModel,
navigator = this@NavigationHost navigator = this@NavigationHost
) )
} }
@ -165,8 +165,7 @@ class MainActivity : ComponentActivity() {
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.background( .background(
if (darkTheme.isDark()) Color.Black MaterialTheme.colorScheme.surface
else Color.White
) )
) )
} }

View file

@ -1,13 +1,13 @@
package com.acclorite.books_history.data.di package ua.acclorite.book_story.data.di
import android.app.Application import android.app.Application
import androidx.room.Room 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.Module
import dagger.Provides import dagger.Provides
import dagger.hilt.InstallIn import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent import dagger.hilt.components.SingletonComponent
import ua.acclorite.book_story.data.local.room.BookDao
import ua.acclorite.book_story.data.local.room.BookDatabase
import javax.inject.Singleton import javax.inject.Singleton
@Module @Module

View file

@ -0,0 +1,43 @@
package ua.acclorite.book_story.data.di
import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import ua.acclorite.book_story.data.local.data_store.DataStore
import ua.acclorite.book_story.data.local.data_store.DataStoreImpl
import ua.acclorite.book_story.data.mapper.BookMapper
import ua.acclorite.book_story.data.mapper.BookMapperImpl
import ua.acclorite.book_story.data.mapper.HistoryMapper
import ua.acclorite.book_story.data.mapper.HistoryMapperImpl
import ua.acclorite.book_story.data.repository.BookRepositoryImpl
import ua.acclorite.book_story.domain.repository.BookRepository
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
@Binds
@Singleton
abstract fun bindBookMapper(
bookMapperImpl: BookMapperImpl
): BookMapper
@Binds
@Singleton
abstract fun bindHistoryMapper(
historyMapperImpl: HistoryMapperImpl
): HistoryMapper
}

View file

@ -1,4 +1,4 @@
package com.acclorite.books_history.data.local.data_store package ua.acclorite.book_story.data.local.data_store
import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.Preferences
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow

View file

@ -1,4 +1,4 @@
package com.acclorite.books_history.data.local.data_store package ua.acclorite.book_story.data.local.data_store
import android.app.Application import android.app.Application
import android.content.Context import android.content.Context

View file

@ -1,8 +1,8 @@
package com.acclorite.books_history.data.local.dto package ua.acclorite.book_story.data.local.dto
import androidx.room.Entity import androidx.room.Entity
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import com.acclorite.books_history.domain.model.Category import ua.acclorite.book_story.domain.model.Category
@Entity @Entity
data class BookEntity( data class BookEntity(

View file

@ -0,0 +1,12 @@
package ua.acclorite.book_story.data.local.dto
import androidx.room.Entity
import androidx.room.PrimaryKey
@Entity
data class HistoryEntity(
@PrimaryKey(true)
val id: Int? = null,
val bookId: Int,
val time: Long
)

View file

@ -1,4 +1,4 @@
package com.acclorite.books_history.data.local.room package ua.acclorite.book_story.data.local.room
import androidx.room.Dao import androidx.room.Dao
import androidx.room.Delete import androidx.room.Delete
@ -6,7 +6,8 @@ import androidx.room.Insert
import androidx.room.OnConflictStrategy import androidx.room.OnConflictStrategy
import androidx.room.Query import androidx.room.Query
import androidx.room.Update import androidx.room.Update
import com.acclorite.books_history.data.local.dto.BookEntity import ua.acclorite.book_story.data.local.dto.BookEntity
import ua.acclorite.book_story.data.local.dto.HistoryEntity
@Dao @Dao
interface BookDao { interface BookDao {
@ -35,4 +36,22 @@ interface BookDao {
@Update @Update
suspend fun updateBooks(books: List<BookEntity>) suspend fun updateBooks(books: List<BookEntity>)
@Query("SELECT * FROM historyentity")
suspend fun getHistory(): List<HistoryEntity>
@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertHistory(
history: List<HistoryEntity>
)
@Query("DELETE FROM historyentity")
suspend fun deleteWholeHistory()
@Query("DELETE FROM historyentity WHERE bookId = :bookId")
suspend fun deleteBookHistory(bookId: Int)
@Delete
suspend fun deleteHistory(history: List<HistoryEntity>)
} }

View file

@ -0,0 +1,18 @@
package ua.acclorite.book_story.data.local.room
import androidx.room.Database
import androidx.room.RoomDatabase
import ua.acclorite.book_story.data.local.dto.BookEntity
import ua.acclorite.book_story.data.local.dto.HistoryEntity
@Database(
entities = [
BookEntity::class,
HistoryEntity::class
],
version = 1,
exportSchema = false
)
abstract class BookDatabase : RoomDatabase() {
abstract val dao: BookDao
}

View file

@ -0,0 +1,10 @@
package ua.acclorite.book_story.data.mapper
import ua.acclorite.book_story.data.local.dto.BookEntity
import ua.acclorite.book_story.domain.model.Book
interface BookMapper {
suspend fun toBookEntity(book: Book): BookEntity
suspend fun toBook(bookEntity: BookEntity): Book
}

View file

@ -1,14 +1,14 @@
package com.acclorite.books_history.data.mapper package ua.acclorite.book_story.data.mapper
import android.graphics.Bitmap import android.graphics.Bitmap
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import com.acclorite.books_history.R import ua.acclorite.book_story.R
import com.acclorite.books_history.data.local.dto.BookEntity import ua.acclorite.book_story.data.local.dto.BookEntity
import com.acclorite.books_history.data.parser.EpubFileParser import ua.acclorite.book_story.data.parser.EpubFileParser
import com.acclorite.books_history.data.parser.PdfFileParser import ua.acclorite.book_story.data.parser.PdfFileParser
import com.acclorite.books_history.data.parser.TxtFileParser import ua.acclorite.book_story.data.parser.TxtFileParser
import com.acclorite.books_history.domain.model.Book import ua.acclorite.book_story.domain.model.Book
import com.acclorite.books_history.util.UIText import ua.acclorite.book_story.util.UIText
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.File import java.io.File

View file

@ -0,0 +1,11 @@
package ua.acclorite.book_story.data.mapper
import ua.acclorite.book_story.data.local.dto.HistoryEntity
import ua.acclorite.book_story.domain.model.Book
import ua.acclorite.book_story.domain.model.History
interface HistoryMapper {
suspend fun toHistoryEntity(history: History): HistoryEntity
suspend fun toHistory(historyEntity: HistoryEntity, book: Book): History
}

View file

@ -0,0 +1,24 @@
package ua.acclorite.book_story.data.mapper
import ua.acclorite.book_story.data.local.dto.HistoryEntity
import ua.acclorite.book_story.domain.model.Book
import ua.acclorite.book_story.domain.model.History
import javax.inject.Inject
class HistoryMapperImpl @Inject constructor() : HistoryMapper {
override suspend fun toHistoryEntity(history: History): HistoryEntity {
return HistoryEntity(
id = history.id,
bookId = history.book.id ?: 0,
time = history.time
)
}
override suspend fun toHistory(historyEntity: HistoryEntity, book: Book): History {
return History(
historyEntity.id,
book = book,
time = historyEntity.time
)
}
}

View file

@ -1,10 +1,12 @@
package com.acclorite.books_history.data.parser package ua.acclorite.book_story.data.parser
import android.graphics.BitmapFactory import android.graphics.BitmapFactory
import com.acclorite.books_history.domain.model.Book import kotlinx.coroutines.Dispatchers
import com.acclorite.books_history.domain.model.Category import kotlinx.coroutines.withContext
import com.acclorite.books_history.util.UIText
import nl.siegmann.epublib.epub.EpubReader import nl.siegmann.epublib.epub.EpubReader
import ua.acclorite.book_story.domain.model.Book
import ua.acclorite.book_story.domain.model.Category
import ua.acclorite.book_story.util.UIText
import java.io.File import java.io.File
import java.io.FileInputStream import java.io.FileInputStream
import javax.inject.Inject import javax.inject.Inject
@ -18,7 +20,9 @@ class EpubFileParser @Inject constructor() : FileParser {
try { try {
val epubReader = EpubReader() val epubReader = EpubReader()
val book = epubReader.readEpub(FileInputStream(file)) val book = withContext(Dispatchers.IO) {
epubReader.readEpub(FileInputStream(file))
}
val metadata = book.metadata val metadata = book.metadata
val author = UIText.StringValue(metadata.authors.joinToString(", ")) val author = UIText.StringValue(metadata.authors.joinToString(", "))

View file

@ -1,8 +1,10 @@
package com.acclorite.books_history.data.parser package ua.acclorite.book_story.data.parser
import com.acclorite.books_history.domain.model.StringWithId import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import nl.siegmann.epublib.epub.EpubReader import nl.siegmann.epublib.epub.EpubReader
import org.jsoup.Jsoup import org.jsoup.Jsoup
import ua.acclorite.book_story.domain.model.StringWithId
import java.io.BufferedReader import java.io.BufferedReader
import java.io.File import java.io.File
import java.io.FileInputStream import java.io.FileInputStream
@ -18,7 +20,9 @@ class EpubTextParser @Inject constructor() : TextParser {
} }
try { try {
val book = EpubReader().readEpub(FileInputStream(file)) val book = withContext(Dispatchers.IO) {
EpubReader().readEpub(FileInputStream(file))
}
val unformattedText = StringBuilder() val unformattedText = StringBuilder()
for (spineReference in book.spine.spineReferences) { for (spineReference in book.spine.spineReferences) {
@ -28,7 +32,9 @@ class EpubTextParser @Inject constructor() : TextParser {
BufferedReader(InputStreamReader(inputStream, Charset.forName("UTF-8"))) BufferedReader(InputStreamReader(inputStream, Charset.forName("UTF-8")))
var line: String? var line: String?
while (reader.readLine().also { line = it } != null) { while (withContext(Dispatchers.IO) {
reader.readLine()
}.also { line = it } != null) {
unformattedText.append(line).append("\n") unformattedText.append(line).append("\n")
} }
} }

View file

@ -0,0 +1,9 @@
package ua.acclorite.book_story.data.parser
import ua.acclorite.book_story.domain.model.Book
import java.io.File
interface FileParser {
suspend fun parse(file: File): Book?
}

View file

@ -1,13 +1,13 @@
package com.acclorite.books_history.data.parser package ua.acclorite.book_story.data.parser
import android.app.Application import android.app.Application
import com.acclorite.books_history.R
import com.acclorite.books_history.domain.model.Book
import com.acclorite.books_history.domain.model.Category
import com.acclorite.books_history.domain.model.StringWithId
import com.acclorite.books_history.util.UIText
import com.tom_roush.pdfbox.android.PDFBoxResourceLoader import com.tom_roush.pdfbox.android.PDFBoxResourceLoader
import com.tom_roush.pdfbox.pdmodel.PDDocument import com.tom_roush.pdfbox.pdmodel.PDDocument
import ua.acclorite.book_story.R
import ua.acclorite.book_story.domain.model.Book
import ua.acclorite.book_story.domain.model.Category
import ua.acclorite.book_story.domain.model.StringWithId
import ua.acclorite.book_story.util.UIText
import java.io.File import java.io.File
import javax.inject.Inject import javax.inject.Inject

View file

@ -1,10 +1,10 @@
package com.acclorite.books_history.data.parser package ua.acclorite.book_story.data.parser
import android.app.Application import android.app.Application
import com.acclorite.books_history.domain.model.StringWithId
import com.tom_roush.pdfbox.android.PDFBoxResourceLoader import com.tom_roush.pdfbox.android.PDFBoxResourceLoader
import com.tom_roush.pdfbox.pdmodel.PDDocument import com.tom_roush.pdfbox.pdmodel.PDDocument
import com.tom_roush.pdfbox.text.PDFTextStripper import com.tom_roush.pdfbox.text.PDFTextStripper
import ua.acclorite.book_story.domain.model.StringWithId
import java.io.File import java.io.File
import javax.inject.Inject import javax.inject.Inject

View file

@ -0,0 +1,8 @@
package ua.acclorite.book_story.data.parser
import ua.acclorite.book_story.domain.model.StringWithId
import java.io.File
interface TextParser {
suspend fun parse(file: File): List<StringWithId>
}

View file

@ -1,10 +1,10 @@
package com.acclorite.books_history.data.parser package ua.acclorite.book_story.data.parser
import com.acclorite.books_history.R import ua.acclorite.book_story.R
import com.acclorite.books_history.domain.model.Book import ua.acclorite.book_story.domain.model.Book
import com.acclorite.books_history.domain.model.Category import ua.acclorite.book_story.domain.model.Category
import com.acclorite.books_history.domain.model.StringWithId import ua.acclorite.book_story.domain.model.StringWithId
import com.acclorite.books_history.util.UIText import ua.acclorite.book_story.util.UIText
import java.io.File import java.io.File
import javax.inject.Inject import javax.inject.Inject

View file

@ -1,6 +1,8 @@
package com.acclorite.books_history.data.parser package ua.acclorite.book_story.data.parser
import com.acclorite.books_history.domain.model.StringWithId import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import ua.acclorite.book_story.domain.model.StringWithId
import java.io.BufferedReader import java.io.BufferedReader
import java.io.File import java.io.File
import java.io.FileReader import java.io.FileReader
@ -16,11 +18,13 @@ class TxtTextParser @Inject constructor() : TextParser {
return try { return try {
val formattedLines = mutableListOf<StringWithId>() val formattedLines = mutableListOf<StringWithId>()
withContext(Dispatchers.IO) {
BufferedReader(FileReader(file)).forEachLine { line -> BufferedReader(FileReader(file)).forEachLine { line ->
formattedLines.add( formattedLines.add(
StringWithId(line) StringWithId(line)
) )
} }
}
formattedLines formattedLines
} catch (e: Exception) { } catch (e: Exception) {

View file

@ -1,25 +1,27 @@
package com.acclorite.books_history.data.repository package ua.acclorite.book_story.data.repository
import android.os.Environment import android.os.Environment
import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.Preferences
import com.acclorite.books_history.data.local.data_store.DataStore
import com.acclorite.books_history.data.local.dto.BookEntity
import com.acclorite.books_history.data.local.room.BookDao
import com.acclorite.books_history.data.mapper.BookMapper
import com.acclorite.books_history.data.parser.EpubFileParser
import com.acclorite.books_history.data.parser.EpubTextParser
import com.acclorite.books_history.data.parser.PdfFileParser
import com.acclorite.books_history.data.parser.PdfTextParser
import com.acclorite.books_history.data.parser.TxtFileParser
import com.acclorite.books_history.data.parser.TxtTextParser
import com.acclorite.books_history.domain.model.Book
import com.acclorite.books_history.domain.model.NullableBook
import com.acclorite.books_history.domain.model.StringWithId
import com.acclorite.books_history.domain.repository.BookRepository
import com.acclorite.books_history.util.Constants
import com.acclorite.books_history.util.Resource
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flow
import ua.acclorite.book_story.data.local.data_store.DataStore
import ua.acclorite.book_story.data.local.dto.BookEntity
import ua.acclorite.book_story.data.local.room.BookDao
import ua.acclorite.book_story.data.mapper.BookMapper
import ua.acclorite.book_story.data.mapper.HistoryMapper
import ua.acclorite.book_story.data.parser.EpubFileParser
import ua.acclorite.book_story.data.parser.EpubTextParser
import ua.acclorite.book_story.data.parser.PdfFileParser
import ua.acclorite.book_story.data.parser.PdfTextParser
import ua.acclorite.book_story.data.parser.TxtFileParser
import ua.acclorite.book_story.data.parser.TxtTextParser
import ua.acclorite.book_story.domain.model.Book
import ua.acclorite.book_story.domain.model.History
import ua.acclorite.book_story.domain.model.NullableBook
import ua.acclorite.book_story.domain.model.StringWithId
import ua.acclorite.book_story.domain.repository.BookRepository
import ua.acclorite.book_story.util.Constants
import ua.acclorite.book_story.util.Resource
import java.io.File import java.io.File
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Singleton import javax.inject.Singleton
@ -30,6 +32,7 @@ class BookRepositoryImpl @Inject constructor(
private val dataStore: DataStore, private val dataStore: DataStore,
private val bookMapper: BookMapper, private val bookMapper: BookMapper,
private val historyMapper: HistoryMapper,
private val txtFileParser: TxtFileParser, private val txtFileParser: TxtFileParser,
private val pdfFileParser: PdfFileParser, private val pdfFileParser: PdfFileParser,
@ -233,6 +236,38 @@ class BookRepositoryImpl @Inject constructor(
emit(Resource.Success(books)) emit(Resource.Success(books))
} }
} }
override suspend fun insertHistory(history: List<History>) {
database.insertHistory(history.map { historyMapper.toHistoryEntity(it) })
}
override suspend fun getHistory(): Flow<Resource<List<History>>> {
return flow {
emit(Resource.Loading(true))
val history = database.getHistory()
emit(
Resource.Success(
data = history.map {
val book = database.findBookById(it.bookId)
historyMapper.toHistory(it, bookMapper.toBook(book))
}
)
)
}
}
override suspend fun deleteWholeHistory() {
database.deleteWholeHistory()
}
override suspend fun deleteBookHistory(bookId: Int) {
database.deleteBookHistory(bookId)
}
override suspend fun deleteHistory(history: List<History>) {
database.deleteHistory(history.map { historyMapper.toHistoryEntity(it) })
}
} }
@ -248,3 +283,4 @@ class BookRepositoryImpl @Inject constructor(

View file

@ -1,8 +1,8 @@
package com.acclorite.books_history.domain.model package ua.acclorite.book_story.domain.model
import android.graphics.Bitmap import android.graphics.Bitmap
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import com.acclorite.books_history.util.UIText import ua.acclorite.book_story.util.UIText
import java.io.File import java.io.File
@Immutable @Immutable

View file

@ -1,4 +1,4 @@
package com.acclorite.books_history.domain.model package ua.acclorite.book_story.domain.model
enum class Category { enum class Category {
READING, ALREADY_READ, PLANNING, DROPPED READING, ALREADY_READ, PLANNING, DROPPED

View file

@ -1,4 +1,4 @@
package com.acclorite.books_history.domain.model package ua.acclorite.book_story.domain.model
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.TextStyle

View file

@ -1,8 +1,8 @@
package com.acclorite.books_history.domain.model package ua.acclorite.book_story.domain.model
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.text.font.FontFamily
import com.acclorite.books_history.util.UIText import ua.acclorite.book_story.util.UIText
@Immutable @Immutable
data class FontWithName( data class FontWithName(

View file

@ -0,0 +1,9 @@
package ua.acclorite.book_story.domain.model
import androidx.compose.runtime.Immutable
@Immutable
data class GroupedHistory(
val title: String,
val history: List<History>
)

View file

@ -0,0 +1,10 @@
package ua.acclorite.book_story.domain.model
import androidx.compose.runtime.Immutable
@Immutable
data class History(
val id: Int?,
val book: Book,
val time: Long
)

View file

@ -1,4 +1,4 @@
package com.acclorite.books_history.domain.model package ua.acclorite.book_story.domain.model
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable
import androidx.compose.ui.graphics.painter.Painter import androidx.compose.ui.graphics.painter.Painter

View file

@ -1,4 +1,4 @@
package com.acclorite.books_history.domain.model package ua.acclorite.book_story.domain.model
sealed class NullableBook(val book: Pair<Book, Boolean>?, val fileName: String?) { sealed class NullableBook(val book: Pair<Book, Boolean>?, val fileName: String?) {
class NotNull(book: Pair<Book, Boolean>) : NullableBook(book, null) class NotNull(book: Pair<Book, Boolean>) : NullableBook(book, null)

View file

@ -1,4 +1,4 @@
package com.acclorite.books_history.domain.model package ua.acclorite.book_story.domain.model
import android.os.Parcelable import android.os.Parcelable
import androidx.compose.runtime.Immutable import androidx.compose.runtime.Immutable

View file

@ -1,12 +1,13 @@
package com.acclorite.books_history.domain.repository package ua.acclorite.book_story.domain.repository
import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.Preferences
import com.acclorite.books_history.data.local.dto.BookEntity
import com.acclorite.books_history.domain.model.Book
import com.acclorite.books_history.domain.model.NullableBook
import com.acclorite.books_history.domain.model.StringWithId
import com.acclorite.books_history.util.Resource
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import ua.acclorite.book_story.data.local.dto.BookEntity
import ua.acclorite.book_story.domain.model.Book
import ua.acclorite.book_story.domain.model.History
import ua.acclorite.book_story.domain.model.NullableBook
import ua.acclorite.book_story.domain.model.StringWithId
import ua.acclorite.book_story.util.Resource
import java.io.File import java.io.File
interface BookRepository { interface BookRepository {
@ -50,4 +51,17 @@ interface BookRepository {
suspend fun getBookTextFromFile(file: File): Flow<Resource<List<StringWithId>>> suspend fun getBookTextFromFile(file: File): Flow<Resource<List<StringWithId>>>
suspend fun getBooksFromFiles(files: List<File>): Flow<Resource<List<NullableBook>>> suspend fun getBooksFromFiles(files: List<File>): Flow<Resource<List<NullableBook>>>
suspend fun insertHistory(history: List<History>)
suspend fun getHistory(): Flow<Resource<List<History>>>
suspend fun deleteWholeHistory()
suspend fun deleteBookHistory(bookId: Int)
suspend fun deleteHistory(
history: List<History>
)
} }

View file

@ -1,12 +1,14 @@
package com.acclorite.books_history.domain.use_case package ua.acclorite.book_story.domain.use_case
import android.annotation.SuppressLint
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import com.acclorite.books_history.domain.repository.BookRepository import ua.acclorite.book_story.domain.repository.BookRepository
import com.acclorite.books_history.util.DataStoreConstants import ua.acclorite.book_story.util.DataStoreConstants
import java.util.Locale import java.util.Locale
import javax.inject.Inject import javax.inject.Inject
class ChangeLanguage @Inject constructor(private val repository: BookRepository) { class ChangeLanguage @Inject constructor(private val repository: BookRepository) {
@SuppressLint("AppBundleLocaleChanges")
suspend fun execute(language: String, activity: ComponentActivity) { suspend fun execute(language: String, activity: ComponentActivity) {
val config = activity.resources.configuration val config = activity.resources.configuration
val resources = activity.resources val resources = activity.resources

View file

@ -0,0 +1,17 @@
package ua.acclorite.book_story.domain.use_case
import ua.acclorite.book_story.domain.model.Book
import ua.acclorite.book_story.domain.repository.BookRepository
import javax.inject.Inject
class DeleteBooks @Inject constructor(private val repository: BookRepository) {
suspend fun execute(books: List<Book>) {
repository.deleteBooks(books)
books.forEach {
if (it.id != null) {
repository.deleteBookHistory(it.id)
}
}
}
}

View file

@ -0,0 +1,12 @@
package ua.acclorite.book_story.domain.use_case
import ua.acclorite.book_story.domain.model.History
import ua.acclorite.book_story.domain.repository.BookRepository
import javax.inject.Inject
class DeleteHistory @Inject constructor(private val repository: BookRepository) {
suspend fun execute(history: List<History>) {
repository.deleteHistory(history)
}
}

View file

@ -0,0 +1,11 @@
package ua.acclorite.book_story.domain.use_case
import ua.acclorite.book_story.domain.repository.BookRepository
import javax.inject.Inject
class DeleteWholeHistory @Inject constructor(private val repository: BookRepository) {
suspend fun execute() {
repository.deleteWholeHistory()
}
}

View file

@ -1,7 +1,7 @@
package com.acclorite.books_history.domain.use_case package ua.acclorite.book_story.domain.use_case
import com.acclorite.books_history.domain.model.Book import ua.acclorite.book_story.domain.model.Book
import com.acclorite.books_history.domain.repository.BookRepository import ua.acclorite.book_story.domain.repository.BookRepository
import javax.inject.Inject import javax.inject.Inject
class FastGetBooks @Inject constructor(private val repository: BookRepository) { class FastGetBooks @Inject constructor(private val repository: BookRepository) {

View file

@ -1,9 +1,9 @@
package com.acclorite.books_history.domain.use_case package ua.acclorite.book_story.domain.use_case
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 kotlinx.coroutines.flow.Flow
import ua.acclorite.book_story.domain.model.Book
import ua.acclorite.book_story.domain.repository.BookRepository
import ua.acclorite.book_story.util.Resource
import javax.inject.Inject import javax.inject.Inject
class GetBooks @Inject constructor(private val repository: BookRepository) { class GetBooks @Inject constructor(private val repository: BookRepository) {

View file

@ -1,9 +1,9 @@
package com.acclorite.books_history.domain.use_case package ua.acclorite.book_story.domain.use_case
import com.acclorite.books_history.domain.model.NullableBook
import com.acclorite.books_history.domain.repository.BookRepository
import com.acclorite.books_history.util.Resource
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import ua.acclorite.book_story.domain.model.NullableBook
import ua.acclorite.book_story.domain.repository.BookRepository
import ua.acclorite.book_story.util.Resource
import java.io.File import java.io.File
import javax.inject.Inject import javax.inject.Inject

View file

@ -1,8 +1,8 @@
package com.acclorite.books_history.domain.use_case package ua.acclorite.book_story.domain.use_case
import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.Preferences
import com.acclorite.books_history.domain.repository.BookRepository
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import ua.acclorite.book_story.domain.repository.BookRepository
import javax.inject.Inject import javax.inject.Inject
class GetDatastore @Inject constructor(private val repository: BookRepository) { class GetDatastore @Inject constructor(private val repository: BookRepository) {

View file

@ -1,8 +1,8 @@
package com.acclorite.books_history.domain.use_case package ua.acclorite.book_story.domain.use_case
import com.acclorite.books_history.domain.repository.BookRepository
import com.acclorite.books_history.util.Resource
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import ua.acclorite.book_story.domain.repository.BookRepository
import ua.acclorite.book_story.util.Resource
import java.io.File import java.io.File
import javax.inject.Inject import javax.inject.Inject

View file

@ -0,0 +1,14 @@
package ua.acclorite.book_story.domain.use_case
import kotlinx.coroutines.flow.Flow
import ua.acclorite.book_story.domain.model.History
import ua.acclorite.book_story.domain.repository.BookRepository
import ua.acclorite.book_story.util.Resource
import javax.inject.Inject
class GetHistory @Inject constructor(private val repository: BookRepository) {
suspend fun execute(): Flow<Resource<List<History>>> {
return repository.getHistory()
}
}

View file

@ -1,9 +1,9 @@
package com.acclorite.books_history.domain.use_case package ua.acclorite.book_story.domain.use_case
import com.acclorite.books_history.domain.model.StringWithId
import com.acclorite.books_history.domain.repository.BookRepository
import com.acclorite.books_history.util.Resource
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import ua.acclorite.book_story.domain.model.StringWithId
import ua.acclorite.book_story.domain.repository.BookRepository
import ua.acclorite.book_story.util.Resource
import java.io.File import java.io.File
import javax.inject.Inject import javax.inject.Inject

View file

@ -1,7 +1,7 @@
package com.acclorite.books_history.domain.use_case package ua.acclorite.book_story.domain.use_case
import com.acclorite.books_history.domain.model.Book import ua.acclorite.book_story.domain.model.Book
import com.acclorite.books_history.domain.repository.BookRepository import ua.acclorite.book_story.domain.repository.BookRepository
import javax.inject.Inject import javax.inject.Inject
class InsertBooks @Inject constructor(private val repository: BookRepository) { class InsertBooks @Inject constructor(private val repository: BookRepository) {

View file

@ -0,0 +1,11 @@
package ua.acclorite.book_story.domain.use_case
import ua.acclorite.book_story.domain.model.History
import ua.acclorite.book_story.domain.repository.BookRepository
import javax.inject.Inject
class InsertHistory @Inject constructor(private val repository: BookRepository) {
suspend fun execute(history: List<History>) {
repository.insertHistory(history)
}
}

View file

@ -1,7 +1,7 @@
package com.acclorite.books_history.domain.use_case package ua.acclorite.book_story.domain.use_case
import androidx.datastore.preferences.core.Preferences import androidx.datastore.preferences.core.Preferences
import com.acclorite.books_history.domain.repository.BookRepository import ua.acclorite.book_story.domain.repository.BookRepository
import javax.inject.Inject import javax.inject.Inject
class SetDatastore @Inject constructor(private val repository: BookRepository) { class SetDatastore @Inject constructor(private val repository: BookRepository) {

View file

@ -1,7 +1,7 @@
package com.acclorite.books_history.domain.use_case package ua.acclorite.book_story.domain.use_case
import com.acclorite.books_history.domain.model.Book import ua.acclorite.book_story.domain.model.Book
import com.acclorite.books_history.domain.repository.BookRepository import ua.acclorite.book_story.domain.repository.BookRepository
import javax.inject.Inject import javax.inject.Inject
class UpdateBooks @Inject constructor(private val repository: BookRepository) { class UpdateBooks @Inject constructor(private val repository: BookRepository) {

View file

@ -1,4 +1,4 @@
package com.acclorite.books_history.presentation package ua.acclorite.book_story.presentation
import android.annotation.SuppressLint import android.annotation.SuppressLint
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
@ -20,7 +20,6 @@ import androidx.compose.ui.platform.LocalContext
import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.acclorite.books_history.ui.Transitions
import dagger.assisted.Assisted import dagger.assisted.Assisted
import dagger.assisted.AssistedFactory import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject import dagger.assisted.AssistedInject
@ -29,6 +28,7 @@ import dagger.hilt.android.lifecycle.withCreationCallback
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import ua.acclorite.book_story.ui.Transitions
private const val CURRENT_SCREEN = "current_screen" private const val CURRENT_SCREEN = "current_screen"
@ -155,8 +155,8 @@ class Navigator @AssistedInject constructor(
@Composable @Composable
fun composable( fun composable(
screen: Screen, screen: Screen,
enterAnim: EnterTransition = Transitions.DefaultTransitionIn, enterAnim: EnterTransition = Transitions.FadeTransitionIn,
exitAnim: ExitTransition = Transitions.DefaultTransitionOut, exitAnim: ExitTransition = Transitions.FadeTransitionOut,
content: @Composable () -> Unit content: @Composable () -> Unit
) { ) {
AnimatedVisibility( AnimatedVisibility(

View file

@ -0,0 +1,141 @@
package ua.acclorite.book_story.presentation.components
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.spring
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.statusBars
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.TopAppBarScrollBehavior
import androidx.compose.runtime.Composable
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.unit.dp
import ua.acclorite.book_story.ui.DefaultTransition
import ua.acclorite.book_story.ui.elevation
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun AnimatedTopAppBar(
containerColor: Color = MaterialTheme.colorScheme.surface,
scrolledContainerColor: Color? = MaterialTheme.elevation(),
scrollBehavior: TopAppBarScrollBehavior?,
isTopBarScrolled: Boolean?,
content1Visibility: Boolean? = null,
content1NavigationIcon: @Composable () -> Unit,
content1Title: @Composable () -> Unit,
content1Actions: @Composable RowScope.() -> Unit,
content2Visibility: Boolean? = null,
content2NavigationIcon: @Composable () -> Unit = {},
content2Title: @Composable () -> Unit = {},
content2Actions: @Composable RowScope.() -> Unit = {},
content3Visibility: Boolean? = null,
content3NavigationIcon: @Composable () -> Unit = {},
content3Title: @Composable () -> Unit = {},
content3Actions: @Composable RowScope.() -> Unit = {}
) {
Box(modifier = Modifier.fillMaxWidth()) {
if ((scrollBehavior != null || isTopBarScrolled != null) && scrolledContainerColor != null) {
val fraction by remember {
derivedStateOf {
scrollBehavior?.state?.overlappedFraction ?: 0f
}
}
val isScrolled = if (scrollBehavior != null) {
fraction > 0.01f
} else isTopBarScrolled ?: false
val color = if (isScrolled) scrolledContainerColor else containerColor
val animatedColor by animateColorAsState(
targetValue = color,
animationSpec = spring(stiffness = Spring.StiffnessMediumLow),
label = "TopAppBar color animation"
)
val statusBarPadding = with(LocalDensity.current) {
WindowInsets.statusBars.getTop(LocalDensity.current).toDp()
}
Box(
modifier = Modifier
.fillMaxWidth()
.height(64.dp + statusBarPadding)
.background(
animatedColor
)
)
}
DefaultTransition(visible = content1Visibility ?: true) {
TopAppBar(
navigationIcon = content1NavigationIcon,
title = content1Title,
actions = content1Actions,
scrollBehavior = scrollBehavior,
colors = TopAppBarDefaults.topAppBarColors(
containerColor = Color.Transparent,
scrolledContainerColor = Color.Transparent
)
)
}
if (content2Visibility != null) {
DefaultTransition(visible = content2Visibility) {
TopAppBar(
navigationIcon = content2NavigationIcon,
title = content2Title,
actions = content2Actions,
scrollBehavior = scrollBehavior,
colors = TopAppBarDefaults.topAppBarColors(
containerColor = Color.Transparent,
scrolledContainerColor = Color.Transparent
)
)
}
}
if (content3Visibility != null) {
DefaultTransition(visible = content3Visibility) {
TopAppBar(
navigationIcon = content3NavigationIcon,
title = content3Title,
actions = content3Actions,
scrollBehavior = scrollBehavior,
colors = TopAppBarDefaults.topAppBarColors(
containerColor = Color.Transparent,
scrolledContainerColor = Color.Transparent
)
)
}
}
}
}

View file

@ -1,17 +1,20 @@
package com.acclorite.books_history.presentation.components package ua.acclorite.book_story.presentation.components
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.NavigationBar import androidx.compose.material3.NavigationBar
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import com.acclorite.books_history.R import ua.acclorite.book_story.R
import com.acclorite.books_history.domain.model.NavigationBarItem import ua.acclorite.book_story.domain.model.NavigationBarItem
import com.acclorite.books_history.presentation.Navigator import ua.acclorite.book_story.presentation.Navigator
import com.acclorite.books_history.presentation.Screen import ua.acclorite.book_story.presentation.Screen
import com.acclorite.books_history.ui.elevation import ua.acclorite.book_story.ui.elevation
/** /**
* Bottom navigation bar, uses default [NavigationBar]. * Bottom navigation bar, uses default [NavigationBar].
@ -20,7 +23,15 @@ import com.acclorite.books_history.ui.elevation
fun BottomNavigationBar( fun BottomNavigationBar(
navigator: Navigator navigator: Navigator
) { ) {
val currentScreen by navigator.getCurrentScreen().collectAsState() var currentScreen: Screen? by remember { mutableStateOf(null) }
LaunchedEffect(Unit) {
navigator.getCurrentScreen().collect {
if (it == Screen.LIBRARY || it == Screen.HISTORY || it == Screen.BROWSE) {
currentScreen = it
}
}
}
NavigationBar( NavigationBar(
containerColor = MaterialTheme.elevation() containerColor = MaterialTheme.elevation()

View file

@ -1,4 +1,4 @@
package com.acclorite.books_history.presentation.components package ua.acclorite.book_story.presentation.components
import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.RowScope
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
@ -11,7 +11,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.acclorite.books_history.domain.model.NavigationBarItem import ua.acclorite.book_story.domain.model.NavigationBarItem
/** /**
* Bottom Navigation Bar Item, uses default [NavigationBarItem]. * Bottom Navigation Bar Item, uses default [NavigationBarItem].

View file

@ -1,4 +1,4 @@
package com.acclorite.books_history.presentation.components package ua.acclorite.book_story.presentation.components
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme

View file

@ -1,4 +1,4 @@
package com.acclorite.books_history.presentation.components package ua.acclorite.book_story.presentation.components
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.border import androidx.compose.foundation.border
@ -14,7 +14,7 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.acclorite.books_history.ui.elevation import ua.acclorite.book_story.ui.elevation
@Composable @Composable
fun CustomCheckbox(selected: Boolean) { fun CustomCheckbox(selected: Boolean) {

View file

@ -1,4 +1,4 @@
package com.acclorite.books_history.presentation.components package ua.acclorite.book_story.presentation.components
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@ -31,9 +31,9 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.DialogProperties import androidx.compose.ui.window.DialogProperties
import androidx.compose.ui.window.DialogWindowProvider import androidx.compose.ui.window.DialogWindowProvider
import com.acclorite.books_history.R import ua.acclorite.book_story.R
import com.acclorite.books_history.ui.ElevationDefaults import ua.acclorite.book_story.ui.ElevationDefaults
import com.acclorite.books_history.ui.elevation import ua.acclorite.book_story.ui.elevation
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
@ -100,6 +100,8 @@ fun CustomDialogWithContent(
else Alignment.Start else Alignment.Start
) )
.padding(horizontal = 24.dp), .padding(horizontal = 24.dp),
textAlign = if (drawableIcon != null || imageVectorIcon != null) TextAlign.Center
else TextAlign.Start,
style = MaterialTheme.typography.headlineSmall, style = MaterialTheme.typography.headlineSmall,
color = MaterialTheme.colorScheme.onSurface color = MaterialTheme.colorScheme.onSurface
) )

View file

@ -1,4 +1,4 @@
package com.acclorite.books_history.presentation.components package ua.acclorite.book_story.presentation.components
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
@ -32,9 +32,9 @@ import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.compose.ui.window.DialogProperties import androidx.compose.ui.window.DialogProperties
import androidx.compose.ui.window.DialogWindowProvider import androidx.compose.ui.window.DialogWindowProvider
import com.acclorite.books_history.R import ua.acclorite.book_story.R
import com.acclorite.books_history.ui.ElevationDefaults import ua.acclorite.book_story.ui.ElevationDefaults
import com.acclorite.books_history.ui.elevation import ua.acclorite.book_story.ui.elevation
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
@ -101,6 +101,8 @@ fun CustomDialogWithLazyColumn(
else Alignment.Start else Alignment.Start
) )
.padding(horizontal = 24.dp), .padding(horizontal = 24.dp),
textAlign = if (drawableIcon != null || imageVectorIcon != null) TextAlign.Center
else TextAlign.Start,
style = MaterialTheme.typography.headlineSmall, style = MaterialTheme.typography.headlineSmall,
color = MaterialTheme.colorScheme.onSurface color = MaterialTheme.colorScheme.onSurface
) )

View file

@ -1,4 +1,4 @@
package com.acclorite.books_history.presentation.components package ua.acclorite.book_story.presentation.components
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size

View file

@ -0,0 +1,49 @@
package ua.acclorite.book_story.presentation.components
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.DismissValue
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.SwipeToDismiss
import androidx.compose.material.rememberDismissState
import androidx.compose.material3.Snackbar
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun CustomSnackbar(snackbarState: SnackbarHostState) {
val dismissState = rememberDismissState { value ->
if (value != DismissValue.Default) {
snackbarState.currentSnackbarData?.dismiss()
true
} else false
}
LaunchedEffect(dismissState.currentValue) {
if (dismissState.currentValue != DismissValue.Default) {
dismissState.reset()
}
}
SwipeToDismiss(
modifier = Modifier
.padding(vertical = 4.dp)
.fillMaxWidth(),
state = dismissState,
background = {}
) {
SnackbarHost(
hostState = snackbarState,
snackbar = { data ->
Snackbar(
data
)
}
)
}
}

View file

@ -1,4 +1,4 @@
package com.acclorite.books_history.presentation.components package ua.acclorite.book_story.presentation.components
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column

View file

@ -1,4 +1,4 @@
package com.acclorite.books_history.presentation.components package ua.acclorite.book_story.presentation.components
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column

View file

@ -1,4 +1,4 @@
package com.acclorite.books_history.presentation.components package ua.acclorite.book_story.presentation.components
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
@ -20,9 +20,9 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.acclorite.books_history.R import ua.acclorite.book_story.R
import com.acclorite.books_history.presentation.Navigator import ua.acclorite.book_story.presentation.Navigator
import com.acclorite.books_history.presentation.Screen import ua.acclorite.book_story.presentation.Screen
@Composable @Composable
fun MoreDropDown(navigator: Navigator) { fun MoreDropDown(navigator: Navigator) {

View file

@ -1,4 +1,4 @@
package com.acclorite.books_history.presentation.components package ua.acclorite.book_story.presentation.components
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row

View file

@ -1,4 +1,4 @@
package com.acclorite.books_history.presentation.data package ua.acclorite.book_story.presentation.data
fun String.removeTrailingZero(): String { fun String.removeTrailingZero(): String {
if (!this.contains('.')) if (!this.contains('.'))

View file

@ -1,4 +1,4 @@
package com.acclorite.books_history.presentation.data package ua.acclorite.book_story.presentation.data
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity

View file

@ -1,4 +1,4 @@
package com.acclorite.books_history.presentation.data package ua.acclorite.book_story.presentation.data
import android.os.Build import android.os.Build
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
@ -6,15 +6,6 @@ import androidx.compose.ui.graphics.Color
import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.SavedStateHandle
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.acclorite.books_history.domain.use_case.ChangeLanguage
import com.acclorite.books_history.domain.use_case.GetDatastore
import com.acclorite.books_history.domain.use_case.SetDatastore
import com.acclorite.books_history.ui.DarkTheme
import com.acclorite.books_history.ui.Theme
import com.acclorite.books_history.ui.toDarkTheme
import com.acclorite.books_history.ui.toTheme
import com.acclorite.books_history.util.Constants
import com.acclorite.books_history.util.DataStoreConstants
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
@ -25,6 +16,15 @@ import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import ua.acclorite.book_story.domain.use_case.ChangeLanguage
import ua.acclorite.book_story.domain.use_case.GetDatastore
import ua.acclorite.book_story.domain.use_case.SetDatastore
import ua.acclorite.book_story.ui.DarkTheme
import ua.acclorite.book_story.ui.Theme
import ua.acclorite.book_story.ui.toDarkTheme
import ua.acclorite.book_story.ui.toTheme
import ua.acclorite.book_story.util.Constants
import ua.acclorite.book_story.util.DataStoreConstants
import java.util.Locale import java.util.Locale
import javax.inject.Inject import javax.inject.Inject

View file

@ -1,4 +1,4 @@
package com.acclorite.books_history.presentation.screens.book_info package ua.acclorite.book_story.presentation.screens.book_info
import android.widget.Toast import android.widget.Toast
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
@ -33,7 +33,6 @@ import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBar
import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
@ -51,34 +50,39 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import com.acclorite.books_history.R
import com.acclorite.books_history.domain.model.Book
import com.acclorite.books_history.presentation.Argument
import com.acclorite.books_history.presentation.Navigator
import com.acclorite.books_history.presentation.Screen
import com.acclorite.books_history.presentation.screens.book_info.components.BookInfoBackground
import com.acclorite.books_history.presentation.screens.book_info.components.BookInfoChangeCoverBottomSheet
import com.acclorite.books_history.presentation.screens.book_info.components.BookInfoDeleteDialog
import com.acclorite.books_history.presentation.screens.book_info.components.BookInfoDescriptionSection
import com.acclorite.books_history.presentation.screens.book_info.components.BookInfoDetailsBottomSheet
import com.acclorite.books_history.presentation.screens.book_info.components.BookInfoInfoSection
import com.acclorite.books_history.presentation.screens.book_info.components.BookInfoMoreDropDown
import com.acclorite.books_history.presentation.screens.book_info.components.BookInfoMoveDialog
import com.acclorite.books_history.presentation.screens.book_info.components.BookInfoStatisticSection
import com.acclorite.books_history.presentation.screens.book_info.data.BookInfoEvent
import com.acclorite.books_history.presentation.screens.book_info.data.BookInfoViewModel
import com.acclorite.books_history.presentation.screens.library.data.LibraryEvent
import com.acclorite.books_history.presentation.screens.library.data.LibraryViewModel
import com.acclorite.books_history.ui.DefaultTransition
import com.acclorite.books_history.ui.Transitions
import com.acclorite.books_history.ui.elevation
import com.acclorite.books_history.util.Constants
import dagger.hilt.android.lifecycle.withCreationCallback import dagger.hilt.android.lifecycle.withCreationCallback
import ua.acclorite.book_story.R
import ua.acclorite.book_story.domain.model.Book
import ua.acclorite.book_story.presentation.Argument
import ua.acclorite.book_story.presentation.Navigator
import ua.acclorite.book_story.presentation.Screen
import ua.acclorite.book_story.presentation.components.AnimatedTopAppBar
import ua.acclorite.book_story.presentation.screens.book_info.components.BookInfoBackground
import ua.acclorite.book_story.presentation.screens.book_info.components.BookInfoChangeCoverBottomSheet
import ua.acclorite.book_story.presentation.screens.book_info.components.BookInfoDeleteDialog
import ua.acclorite.book_story.presentation.screens.book_info.components.BookInfoDescriptionSection
import ua.acclorite.book_story.presentation.screens.book_info.components.BookInfoDetailsBottomSheet
import ua.acclorite.book_story.presentation.screens.book_info.components.BookInfoInfoSection
import ua.acclorite.book_story.presentation.screens.book_info.components.BookInfoMoreDropDown
import ua.acclorite.book_story.presentation.screens.book_info.components.BookInfoMoveDialog
import ua.acclorite.book_story.presentation.screens.book_info.components.BookInfoStatisticSection
import ua.acclorite.book_story.presentation.screens.book_info.data.BookInfoEvent
import ua.acclorite.book_story.presentation.screens.book_info.data.BookInfoViewModel
import ua.acclorite.book_story.presentation.screens.browse.data.BrowseViewModel
import ua.acclorite.book_story.presentation.screens.history.data.HistoryViewModel
import ua.acclorite.book_story.presentation.screens.library.data.LibraryEvent
import ua.acclorite.book_story.presentation.screens.library.data.LibraryViewModel
import ua.acclorite.book_story.ui.DefaultTransition
import ua.acclorite.book_story.ui.Transitions
import ua.acclorite.book_story.ui.elevation
import ua.acclorite.book_story.util.Constants
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun BookInfoScreen( fun BookInfoScreen(
libraryViewModel: LibraryViewModel = hiltViewModel(), libraryViewModel: LibraryViewModel = hiltViewModel(),
browseViewModel: BrowseViewModel = hiltViewModel(),
historyViewModel: HistoryViewModel = hiltViewModel(),
navigator: Navigator navigator: Navigator
) { ) {
val context = LocalContext.current val context = LocalContext.current
@ -120,7 +124,11 @@ fun BookInfoScreen(
} }
if (state.showChangeCoverBottomSheet) { if (state.showChangeCoverBottomSheet) {
BookInfoChangeCoverBottomSheet(libraryViewModel = libraryViewModel, viewModel = viewModel) BookInfoChangeCoverBottomSheet(
libraryViewModel = libraryViewModel,
historyViewModel = historyViewModel,
viewModel = viewModel
)
} }
if (state.showDetailsBottomSheet) { if (state.showDetailsBottomSheet) {
BookInfoDetailsBottomSheet(viewModel = viewModel) BookInfoDetailsBottomSheet(viewModel = viewModel)
@ -128,6 +136,8 @@ fun BookInfoScreen(
if (state.showDeleteDialog) { if (state.showDeleteDialog) {
BookInfoDeleteDialog( BookInfoDeleteDialog(
libraryViewModel = libraryViewModel, libraryViewModel = libraryViewModel,
browseViewModel = browseViewModel,
historyViewModel = historyViewModel,
viewModel = viewModel, viewModel = viewModel,
navigator = navigator navigator = navigator
) )
@ -147,9 +157,13 @@ fun BookInfoScreen(
.windowInsetsPadding(WindowInsets.navigationBars), .windowInsetsPadding(WindowInsets.navigationBars),
containerColor = MaterialTheme.colorScheme.surface, containerColor = MaterialTheme.colorScheme.surface,
topBar = { topBar = {
TopAppBar( AnimatedTopAppBar(
containerColor = Color.Transparent,
scrolledContainerColor = MaterialTheme.elevation(),
scrollBehavior = scrollBehavior, scrollBehavior = scrollBehavior,
navigationIcon = { isTopBarScrolled = null,
content1NavigationIcon = {
IconButton(onClick = { IconButton(onClick = {
navigator.navigateBack() navigator.navigateBack()
}) { }) {
@ -160,7 +174,7 @@ fun BookInfoScreen(
) )
} }
}, },
title = { content1Title = {
DefaultTransition(visible = firstVisibleItemIndex > 0) { DefaultTransition(visible = firstVisibleItemIndex > 0) {
Text( Text(
state.book.title, state.book.title,
@ -171,7 +185,7 @@ fun BookInfoScreen(
) )
} }
}, },
actions = { content1Actions = {
Box { Box {
DefaultTransition( DefaultTransition(
visible = !state.editTitle visible = !state.editTitle
@ -214,11 +228,7 @@ fun BookInfoScreen(
} }
} }
} }
}, }
colors = TopAppBarDefaults.topAppBarColors(
containerColor = Color.Transparent,
scrolledContainerColor = MaterialTheme.elevation()
)
) )
} }
) { paddingValues -> ) { paddingValues ->

View file

@ -1,4 +1,4 @@
package com.acclorite.books_history.presentation.screens.book_info.components package ua.acclorite.book_story.presentation.screens.book_info.components
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth

View file

@ -1,4 +1,4 @@
package com.acclorite.books_history.presentation.screens.book_info.components package ua.acclorite.book_story.presentation.screens.book_info.components
import android.widget.Toast import android.widget.Toast
import androidx.activity.compose.rememberLauncherForActivityResult import androidx.activity.compose.rememberLauncherForActivityResult
@ -22,18 +22,21 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.acclorite.books_history.R import ua.acclorite.book_story.R
import com.acclorite.books_history.presentation.screens.book_info.data.BookInfoEvent import ua.acclorite.book_story.presentation.screens.book_info.data.BookInfoEvent
import com.acclorite.books_history.presentation.screens.book_info.data.BookInfoViewModel import ua.acclorite.book_story.presentation.screens.book_info.data.BookInfoViewModel
import com.acclorite.books_history.presentation.screens.library.data.LibraryEvent import ua.acclorite.book_story.presentation.screens.history.data.HistoryEvent
import com.acclorite.books_history.presentation.screens.library.data.LibraryViewModel import ua.acclorite.book_story.presentation.screens.history.data.HistoryViewModel
import com.acclorite.books_history.ui.ElevationDefaults import ua.acclorite.book_story.presentation.screens.library.data.LibraryEvent
import com.acclorite.books_history.ui.elevation import ua.acclorite.book_story.presentation.screens.library.data.LibraryViewModel
import ua.acclorite.book_story.ui.ElevationDefaults
import ua.acclorite.book_story.ui.elevation
@OptIn(ExperimentalMaterial3Api::class) @OptIn(ExperimentalMaterial3Api::class)
@Composable @Composable
fun BookInfoChangeCoverBottomSheet( fun BookInfoChangeCoverBottomSheet(
libraryViewModel: LibraryViewModel, libraryViewModel: LibraryViewModel,
historyViewModel: HistoryViewModel,
viewModel: BookInfoViewModel viewModel: BookInfoViewModel
) { ) {
val state by viewModel.state.collectAsState() val state by viewModel.state.collectAsState()
@ -50,6 +53,7 @@ fun BookInfoChangeCoverBottomSheet(
context, context,
refreshList = { refreshList = {
libraryViewModel.onEvent(LibraryEvent.OnLoadList) libraryViewModel.onEvent(LibraryEvent.OnLoadList)
historyViewModel.onEvent(HistoryEvent.OnLoadList)
} }
) )
) )
@ -91,6 +95,7 @@ fun BookInfoChangeCoverBottomSheet(
viewModel.onEvent(BookInfoEvent.OnDeleteCover( viewModel.onEvent(BookInfoEvent.OnDeleteCover(
refreshList = { refreshList = {
libraryViewModel.onEvent(LibraryEvent.OnLoadList) libraryViewModel.onEvent(LibraryEvent.OnLoadList)
historyViewModel.onEvent(HistoryEvent.OnLoadList)
} }
)) ))
Toast.makeText( Toast.makeText(

View file

@ -1,4 +1,4 @@
package com.acclorite.books_history.presentation.screens.book_info.components package ua.acclorite.book_story.presentation.screens.book_info.components
import androidx.compose.foundation.clickable import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement

View file

@ -1,4 +1,4 @@
package com.acclorite.books_history.presentation.screens.book_info.components package ua.acclorite.book_story.presentation.screens.book_info.components
import android.widget.Toast import android.widget.Toast
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
@ -6,17 +6,23 @@ import androidx.compose.material.icons.outlined.DeleteOutline
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import com.acclorite.books_history.R import ua.acclorite.book_story.R
import com.acclorite.books_history.presentation.Navigator import ua.acclorite.book_story.presentation.Navigator
import com.acclorite.books_history.presentation.components.CustomDialogWithContent import ua.acclorite.book_story.presentation.components.CustomDialogWithContent
import com.acclorite.books_history.presentation.screens.book_info.data.BookInfoEvent import ua.acclorite.book_story.presentation.screens.book_info.data.BookInfoEvent
import com.acclorite.books_history.presentation.screens.book_info.data.BookInfoViewModel import ua.acclorite.book_story.presentation.screens.book_info.data.BookInfoViewModel
import com.acclorite.books_history.presentation.screens.library.data.LibraryEvent import ua.acclorite.book_story.presentation.screens.browse.data.BrowseEvent
import com.acclorite.books_history.presentation.screens.library.data.LibraryViewModel import ua.acclorite.book_story.presentation.screens.browse.data.BrowseViewModel
import ua.acclorite.book_story.presentation.screens.history.data.HistoryEvent
import ua.acclorite.book_story.presentation.screens.history.data.HistoryViewModel
import ua.acclorite.book_story.presentation.screens.library.data.LibraryEvent
import ua.acclorite.book_story.presentation.screens.library.data.LibraryViewModel
@Composable @Composable
fun BookInfoDeleteDialog( fun BookInfoDeleteDialog(
libraryViewModel: LibraryViewModel, libraryViewModel: LibraryViewModel,
browseViewModel: BrowseViewModel,
historyViewModel: HistoryViewModel,
viewModel: BookInfoViewModel, viewModel: BookInfoViewModel,
navigator: Navigator navigator: Navigator
) { ) {
@ -37,9 +43,9 @@ fun BookInfoDeleteDialog(
BookInfoEvent.OnDeleteBook( BookInfoEvent.OnDeleteBook(
navigator = navigator, navigator = navigator,
refreshList = { refreshList = {
libraryViewModel.onEvent( libraryViewModel.onEvent(LibraryEvent.OnLoadList)
LibraryEvent.OnLoadList browseViewModel.onEvent(BrowseEvent.OnLoadList)
) historyViewModel.onEvent(HistoryEvent.OnLoadList)
} }
) )
) )

View file

@ -1,4 +1,4 @@
package com.acclorite.books_history.presentation.screens.book_info.components package ua.acclorite.book_story.presentation.screens.book_info.components
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.height
@ -11,8 +11,8 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.acclorite.books_history.R import ua.acclorite.book_story.R
import com.acclorite.books_history.domain.model.Book import ua.acclorite.book_story.domain.model.Book
@Composable @Composable
fun BookInfoDescriptionSection(book: Book) { fun BookInfoDescriptionSection(book: Book) {

View file

@ -1,4 +1,4 @@
package com.acclorite.books_history.presentation.screens.book_info.components package ua.acclorite.book_story.presentation.screens.book_info.components
import android.widget.Toast import android.widget.Toast
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
@ -16,11 +16,11 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.acclorite.books_history.R import ua.acclorite.book_story.R
import com.acclorite.books_history.presentation.screens.book_info.data.BookInfoEvent import ua.acclorite.book_story.presentation.screens.book_info.data.BookInfoEvent
import com.acclorite.books_history.presentation.screens.book_info.data.BookInfoViewModel import ua.acclorite.book_story.presentation.screens.book_info.data.BookInfoViewModel
import com.acclorite.books_history.ui.ElevationDefaults import ua.acclorite.book_story.ui.ElevationDefaults
import com.acclorite.books_history.ui.elevation import ua.acclorite.book_story.ui.elevation
import java.text.SimpleDateFormat import java.text.SimpleDateFormat
import java.util.Date import java.util.Date
import java.util.Locale import java.util.Locale

View file

@ -1,4 +1,4 @@
package com.acclorite.books_history.presentation.screens.book_info.components package ua.acclorite.book_story.presentation.screens.book_info.components
import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.combinedClickable

View file

@ -1,4 +1,4 @@
package com.acclorite.books_history.presentation.screens.book_info.components package ua.acclorite.book_story.presentation.screens.book_info.components
import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.ExperimentalFoundationApi
import androidx.compose.foundation.Image import androidx.compose.foundation.Image
@ -41,10 +41,10 @@ import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.input.KeyboardCapitalization import androidx.compose.ui.text.input.KeyboardCapitalization
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.acclorite.books_history.domain.model.Book import ua.acclorite.book_story.domain.model.Book
import com.acclorite.books_history.presentation.screens.book_info.data.BookInfoEvent import ua.acclorite.book_story.presentation.screens.book_info.data.BookInfoEvent
import com.acclorite.books_history.presentation.screens.book_info.data.BookInfoViewModel import ua.acclorite.book_story.presentation.screens.book_info.data.BookInfoViewModel
import com.acclorite.books_history.ui.elevation import ua.acclorite.book_story.ui.elevation
@OptIn(ExperimentalFoundationApi::class) @OptIn(ExperimentalFoundationApi::class)
@Composable @Composable
@ -117,8 +117,8 @@ fun BookInfoInfoSection(viewModel: BookInfoViewModel, book: Book) {
) )
} else { } else {
BasicTextField( BasicTextField(
maxLines = 4,
value = state.titleValue, value = state.titleValue,
singleLine = true,
textStyle = TextStyle( textStyle = TextStyle(
color = MaterialTheme.colorScheme.onSurface, color = MaterialTheme.colorScheme.onSurface,
fontSize = MaterialTheme.typography.headlineSmall.fontSize, fontSize = MaterialTheme.typography.headlineSmall.fontSize,

View file

@ -1,4 +1,4 @@
package com.acclorite.books_history.presentation.screens.book_info.components package ua.acclorite.book_story.presentation.screens.book_info.components
import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
@ -18,10 +18,10 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.DpOffset import androidx.compose.ui.unit.DpOffset
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.acclorite.books_history.R import ua.acclorite.book_story.R
import com.acclorite.books_history.presentation.components.CustomDropDownMenuItem import ua.acclorite.book_story.presentation.components.CustomDropDownMenuItem
import com.acclorite.books_history.presentation.screens.book_info.data.BookInfoEvent import ua.acclorite.book_story.presentation.screens.book_info.data.BookInfoEvent
import com.acclorite.books_history.presentation.screens.book_info.data.BookInfoViewModel import ua.acclorite.book_story.presentation.screens.book_info.data.BookInfoViewModel
@Composable @Composable
fun BookInfoMoreDropDown(viewModel: BookInfoViewModel) { fun BookInfoMoreDropDown(viewModel: BookInfoViewModel) {

View file

@ -1,4 +1,4 @@
package com.acclorite.books_history.presentation.screens.book_info.components package ua.acclorite.book_story.presentation.screens.book_info.components
import android.widget.Toast import android.widget.Toast
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
@ -11,15 +11,15 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import com.acclorite.books_history.R import ua.acclorite.book_story.R
import com.acclorite.books_history.domain.model.Category import ua.acclorite.book_story.domain.model.Category
import com.acclorite.books_history.presentation.Navigator import ua.acclorite.book_story.presentation.Navigator
import com.acclorite.books_history.presentation.components.CustomDialogWithLazyColumn import ua.acclorite.book_story.presentation.components.CustomDialogWithLazyColumn
import com.acclorite.books_history.presentation.components.SelectableDialogItem import ua.acclorite.book_story.presentation.components.SelectableDialogItem
import com.acclorite.books_history.presentation.screens.book_info.data.BookInfoEvent import ua.acclorite.book_story.presentation.screens.book_info.data.BookInfoEvent
import com.acclorite.books_history.presentation.screens.book_info.data.BookInfoViewModel import ua.acclorite.book_story.presentation.screens.book_info.data.BookInfoViewModel
import com.acclorite.books_history.presentation.screens.library.data.LibraryEvent import ua.acclorite.book_story.presentation.screens.library.data.LibraryEvent
import com.acclorite.books_history.presentation.screens.library.data.LibraryViewModel import ua.acclorite.book_story.presentation.screens.library.data.LibraryViewModel
@Composable @Composable
fun BookInfoMoveDialog( fun BookInfoMoveDialog(

View file

@ -1,4 +1,4 @@
package com.acclorite.books_history.presentation.screens.book_info.components package ua.acclorite.book_story.presentation.screens.book_info.components
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.Spacer
@ -14,10 +14,10 @@ import androidx.compose.ui.graphics.StrokeCap
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import com.acclorite.books_history.R import ua.acclorite.book_story.R
import com.acclorite.books_history.domain.model.Book import ua.acclorite.book_story.domain.model.Book
import com.acclorite.books_history.presentation.data.removeDigits import ua.acclorite.book_story.presentation.data.removeDigits
import com.acclorite.books_history.presentation.data.removeTrailingZero import ua.acclorite.book_story.presentation.data.removeTrailingZero
@Composable @Composable
fun BookInfoStatisticSection(book: Book) { fun BookInfoStatisticSection(book: Book) {

Some files were not shown because too many files have changed in this diff Show more