diff --git a/README.md b/README.md index b45fb50d..c2ba732a 100644 --- a/README.md +++ b/README.md @@ -39,12 +39,14 @@ Built using Jetpack Compose and Kotlin.
- - - - - - + + + + + + + +

@@ -117,6 +119,10 @@ _Don't see your favorite app store? Create an [Issue](https://github.com/Acclori ## 🤝 How to contribute - **The most improtant thing is I do not want someone's code.** This project is maintaining only by me and I want it to be this way. Creating Pull Requests will not have much sense as they will probably be closed. +- **Place your suggestions on Issues + with [Suggestion Needed](https://github.com/Acclorite/book-story/labels/%F0%9F%93%9D%20Suggestion%20needed) + label.** When the issue has this label that means I seek new ideas on how to improve what I + already propose. Feel free to tell me what's on your mind! - **Share your ideas and suggestions.** Your ideas and suggestions are always welcome, if you miss some feature or got an interesting idea, feel free to create a new [Issue](https://github.com/Acclorite/book-story/issues). - **Report bugs**. Had a crash or something went wrong? Create a new [Issue](https://github.com/Acclorite/book-story/issues), the more information you can give the better it is. - **Enjoy the app**. Your best contribution is using my app and enjoying it! diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 5f53aad4..c72bc2f5 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -137,9 +137,6 @@ dependencies { implementation("com.tom-roush:pdfbox-android:2.0.27.0") // Epub parser - implementation("com.positiondev.epublib:epublib-core:3.1") { - exclude("xmlpull") - } implementation("org.jsoup:jsoup:1.18.1") // Fb2 parser diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro index 0314c8fc..07e3b415 100644 --- a/app/proguard-rules.pro +++ b/app/proguard-rules.pro @@ -37,11 +37,12 @@ -dontwarn org.xmlpull.v1.XmlPullParserFactory # Fixes issue with displaying .xml files, but may produce reproducibility issues --keep class org.xmlpull.** { *; } --keepclassmembers class org.xmlpull.** { *; } +#-keep class org.xmlpull.** { *; } +#-keepclassmembers class org.xmlpull.** { *; } # Keeping names of classes that produce undeterministic results -keepnames class ** -keepnames class org.xmlpull.** { *; } -keepclassmembernames class org.xmlpull.** { *; } --keepnames class kotlin.reflect.jvm.internal.impl.builtins.PrimitiveType { values(); } \ No newline at end of file +-keepnames class kotlin.reflect.jvm.internal.impl.builtins.PrimitiveType { values(); } +-keepnames class * implements android.os.Parcelable { ** CREATOR; } \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 05ff76ad..ae9f380e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -17,26 +17,6 @@ android:name="ua.acclorite.book_story.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION" tools:node="remove" /> - - - - - - - - - - - - - - - - - - - - ? = null + + withContext(Dispatchers.IO) { + ZipFile(file).use { zip -> + val opfEntry = zip.entries().asSequence().find { entry -> + entry.name.endsWith(".opf") + } ?: return@withContext + + val opfContent = zip + .getInputStream(opfEntry) + .bufferedReader() + .use { it.readText() } + val document = Jsoup.parse(opfContent) + + val title = document.select("metadata > dc|title").text().trim() + val author = UIText.StringValue( + document.select("metadata > dc|creator").text().trim() + ) + val description = Jsoup.parse( + document.select("metadata > dc|description").text() + ).text() + + var coverImagePath: String? = null + + val coverId = document + .select("metadata > meta[name=cover]") + .attr("content") + if (coverId.isNotBlank()) { + coverImagePath = document + .select("manifest > item[id=$coverId]") + .attr("href") + } + + if (coverImagePath.isNullOrBlank()) { + coverImagePath = document + .select("manifest > item[media-type*=image]") + .firstOrNull()?.attr("href") + } + + book = Book( + title = title, + author = author, + description = description, + textPath = "", + scrollIndex = 0, + scrollOffset = 0, + progress = 0f, + filePath = file.path, + lastOpened = null, + category = Category.entries[0], + coverImage = null + ) to extractCoverImageBitmap(file, coverImagePath) } } - - val metadata = book.metadata - val author = UIText.StringValue(metadata.authors.joinToString(", ")) - val title = metadata.titles.joinToString(", ") - val coverImage = book.coverImage?.let { BitmapFactory.decodeStream(it.inputStream) } - - var description: StringBuilder? = StringBuilder() - metadata.descriptions.forEach { - description?.append(it)?.append(" ") - } - - if (description != null) { - if (description.isBlank()) { - description = null - } - } - - return Book( - title = title, - author = author, - description = description?.toString(), - textPath = "", - scrollIndex = 0, - scrollOffset = 0, - progress = 0f, - filePath = file.path, - lastOpened = null, - category = Category.entries[0], - coverImage = null - ) to coverImage + return book } catch (e: Exception) { e.printStackTrace() return null } } +} + +private fun extractCoverImageBitmap(file: File, coverImagePath: String?): Bitmap? { + if (coverImagePath.isNullOrBlank()) { + return null + } + + ZipFile(file).use { zip -> + zip.entries().asSequence().forEach { entry -> + if (entry.name.endsWith(coverImagePath)) { + val imageBytes = zip.getInputStream(entry).readBytes() + return BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size) + } + } + } + + return null } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/data/parser/epub/EpubTextParser.kt b/app/src/main/java/ua/acclorite/book_story/data/parser/epub/EpubTextParser.kt index 3930ddd4..992f1efa 100644 --- a/app/src/main/java/ua/acclorite/book_story/data/parser/epub/EpubTextParser.kt +++ b/app/src/main/java/ua/acclorite/book_story/data/parser/epub/EpubTextParser.kt @@ -2,19 +2,13 @@ package ua.acclorite.book_story.data.parser.epub import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext -import nl.siegmann.epublib.epub.EpubReader import org.jsoup.Jsoup -import org.jsoup.nodes.Document.OutputSettings -import org.jsoup.safety.Safelist import ua.acclorite.book_story.R import ua.acclorite.book_story.data.parser.TextParser import ua.acclorite.book_story.domain.util.Resource import ua.acclorite.book_story.domain.util.UIText -import java.io.BufferedReader import java.io.File -import java.io.FileInputStream -import java.io.InputStreamReader -import java.nio.charset.Charset +import java.util.zip.ZipFile import javax.inject.Inject @@ -26,66 +20,42 @@ class EpubTextParser @Inject constructor() : TextParser { } try { - val book = withContext(Dispatchers.IO) { - EpubReader().readEpub(FileInputStream(file)) - } - val unformattedText = StringBuilder() + val lines = mutableListOf() - for (spineReference in book.spine.spineReferences) { - val resource = spineReference.resource - val inputStream = resource.inputStream - val reader = BufferedReader( - InputStreamReader( - inputStream, - Charset.forName("UTF-8") - ) - ) - var line: String? + withContext(Dispatchers.IO) { + ZipFile(file).use { zip -> + zip.entries().asSequence().forEach { entry -> + if ( + entry.name.endsWith(".xhtml") + || entry.name.endsWith(".html") + || entry.name.endsWith(".xml") + || entry.name.endsWith(".htm") + ) { + val content = zip.getInputStream(entry).bufferedReader() + .use { + it.readText() + } - withContext(Dispatchers.IO) { - inputStream.close() - } - - while (withContext(Dispatchers.IO) { reader.readLine() } - .also { line = it } != null) { - unformattedText.append(line).append("\n") - } - - withContext(Dispatchers.IO) { - reader.close() - } - } - - val strings = mutableListOf() - - val parsedText = Jsoup.parse(unformattedText.toString()) - parsedText.outputSettings(OutputSettings().prettyPrint(false)) - parsedText.select("br").append("\n") - parsedText.select("p").prepend("\n") - parsedText.select("em").append(" ").prepend("") - - val formattedText = Jsoup.clean( - parsedText.html(), - "", - Safelist.none(), - OutputSettings().prettyPrint(false) - ) - - formattedText - .replace(" ", "") - .replace("\u00a0", "") - .split("\n") - .forEach { - if (it.isNotBlank()) { - strings.add(it.trim()) + val document = Jsoup.parse(content) + document.select("p").append("\n") + document + .wholeText() + .lines() + .forEach { line -> + if (line.isNotBlank()) { + lines.add(line.trim()) + } + } + } } } + } - if (strings.isEmpty()) { + if (lines.isEmpty()) { return Resource.Error(UIText.StringResource(R.string.error_file_empty)) } - return Resource.Success(strings) + return Resource.Success(lines) } catch (e: Exception) { e.printStackTrace() return Resource.Error( diff --git a/app/src/main/java/ua/acclorite/book_story/data/parser/fb2/Fb2TextParser.kt b/app/src/main/java/ua/acclorite/book_story/data/parser/fb2/Fb2TextParser.kt index 4ddb1f30..0a769ace 100644 --- a/app/src/main/java/ua/acclorite/book_story/data/parser/fb2/Fb2TextParser.kt +++ b/app/src/main/java/ua/acclorite/book_story/data/parser/fb2/Fb2TextParser.kt @@ -58,20 +58,6 @@ class Fb2TextParser @Inject constructor() : TextParser { return@forEachIndexed } - if ( - line.all { - if (it == ' ') { - true - } else if (it.isUpperCase() || it.isDigit() || it == '-') { - true - } else { - false - } - } - ) { - return@forEachIndexed - } - if (line.all { it.isDigit() }) { return@forEachIndexed } diff --git a/app/src/main/java/ua/acclorite/book_story/domain/use_case/GetText.kt b/app/src/main/java/ua/acclorite/book_story/domain/use_case/GetText.kt index e7311860..a3a0c933 100644 --- a/app/src/main/java/ua/acclorite/book_story/domain/use_case/GetText.kt +++ b/app/src/main/java/ua/acclorite/book_story/domain/use_case/GetText.kt @@ -1,15 +1,11 @@ package ua.acclorite.book_story.domain.use_case -import ua.acclorite.book_story.domain.model.ReaderLine import ua.acclorite.book_story.domain.repository.BookRepository -import ua.acclorite.book_story.domain.util.ID import javax.inject.Inject class GetText @Inject constructor(private val repository: BookRepository) { - suspend fun execute(textPath: String): Map { - return repository.getBookText(textPath = textPath).withIndex().associate { - it.index to ReaderLine(line = it.value) - } + suspend fun execute(textPath: String): List { + return repository.getBookText(textPath = textPath) } } \ No newline at end of file diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/components/CustomCheckbox.kt b/app/src/main/java/ua/acclorite/book_story/presentation/components/CustomCheckbox.kt index 48bfa527..602167b9 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/components/CustomCheckbox.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/components/CustomCheckbox.kt @@ -20,12 +20,14 @@ import ua.acclorite.book_story.R /** * Custom Checkbox. Has a Circle shape. * + * @param modifier Modifier. * @param selected Whether checked or not. * @param containerColor Container color. * @param size Size of the checkbox icon(not the whole checkbox). */ @Composable fun CustomCheckbox( + modifier: Modifier = Modifier, selected: Boolean, containerColor: Color, size: Dp = 22.dp @@ -33,7 +35,7 @@ fun CustomCheckbox( Icon( imageVector = Icons.Default.Check, tint = if (selected) containerColor else Color.Transparent, - modifier = Modifier + modifier = modifier .border( width = if (!selected) 1.dp else 0.dp, if (!selected) MaterialTheme.colorScheme.outline else Color.Transparent, diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/components/CustomSelectionContainer.kt b/app/src/main/java/ua/acclorite/book_story/presentation/components/CustomSelectionContainer.kt index 0f4fba42..3944725f 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/components/CustomSelectionContainer.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/components/CustomSelectionContainer.kt @@ -29,8 +29,10 @@ import ua.acclorite.book_story.R private const val MENU_ITEM_COPY = 0 -private const val MENU_ITEM_TRANSLATE = 1 -private const val MENU_ITEM_DICTIONARY = 2 +private const val MENU_ITEM_SHARE = 1 +private const val MENU_ITEM_WEB = 2 +private const val MENU_ITEM_TRANSLATE = 3 +private const val MENU_ITEM_DICTIONARY = 4 /** * Custom Text ActionMode callback. Used in pair with [CustomSelectionToolbar]. Follow [TextToolbar] for more info. @@ -39,6 +41,8 @@ private class CustomTextActionModeCallback( private val context: Context, var rect: Rect = Rect.Zero, var onCopyRequested: (() -> Unit)? = null, + var onShareRequested: (() -> Unit)? = null, + var onWebSearchRequested: (() -> Unit)? = null, var onTranslateRequested: (() -> Unit)? = null, var onDictionaryRequested: (() -> Unit)? = null ) : ActionMode.Callback { @@ -51,13 +55,23 @@ private class CustomTextActionModeCallback( .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER) } + onShareRequested?.let { + menu.add(0, MENU_ITEM_SHARE, 1, context.getString(R.string.share)) + .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER) + } + + onWebSearchRequested?.let { + menu.add(0, MENU_ITEM_WEB, 2, context.getString(R.string.web_search)) + .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER) + } + onTranslateRequested?.let { - menu.add(0, MENU_ITEM_TRANSLATE, 1, context.getString(R.string.translate)) + menu.add(0, MENU_ITEM_TRANSLATE, 3, context.getString(R.string.translate)) .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER) } onDictionaryRequested?.let { - menu.add(0, MENU_ITEM_DICTIONARY, 2, context.getString(R.string.dictionary)) + menu.add(0, MENU_ITEM_DICTIONARY, 4, context.getString(R.string.dictionary)) .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER) } @@ -71,6 +85,8 @@ private class CustomTextActionModeCallback( override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean { when (item!!.itemId) { MENU_ITEM_COPY -> onCopyRequested?.invoke() + MENU_ITEM_SHARE -> onShareRequested?.invoke() + MENU_ITEM_WEB -> onWebSearchRequested?.invoke() MENU_ITEM_TRANSLATE -> onTranslateRequested?.invoke() MENU_ITEM_DICTIONARY -> onDictionaryRequested?.invoke() else -> return false @@ -116,12 +132,15 @@ private class FloatingTextActionModeCallback( } /** - * Custom Selection Toolbar. Used in pair with [CustomSelectionContainer] to display custom toolbar. + * Custom Selection Toolbar. + * Used in pair with [CustomSelectionContainer] to display custom toolbar. */ private class CustomSelectionToolbar( private val view: View, context: Context, private val onCopyRequest: (() -> Unit)?, + private val onShareRequest: ((String) -> Unit)?, + private val onWebSearchRequest: ((String) -> Unit)?, private val onTranslateRequest: ((String) -> Unit)?, private val onDictionaryRequest: ((String) -> Unit)? ) : TextToolbar { @@ -145,6 +164,36 @@ private class CustomSelectionToolbar( onCopyRequested?.invoke() onCopyRequest?.invoke() } + callback.onShareRequested = { + val previousClipboard = clipboardManager.primaryClip + onCopyRequested?.invoke() + val currentClipboard = clipboardManager.text + + onShareRequest?.invoke(currentClipboard.toString()) + + if (previousClipboard != null) { + clipboardManager.setPrimaryClip( + previousClipboard + ) + } else { + clipboardManager.setPrimaryClip(ClipData.newPlainText(null, " ")) + } + } + callback.onWebSearchRequested = { + val previousClipboard = clipboardManager.primaryClip + onCopyRequested?.invoke() + val currentClipboard = clipboardManager.text + + onWebSearchRequest?.invoke(currentClipboard.toString()) + + if (previousClipboard != null) { + clipboardManager.setPrimaryClip( + previousClipboard + ) + } else { + clipboardManager.setPrimaryClip(ClipData.newPlainText(null, " ")) + } + } callback.onTranslateRequested = { val previousClipboard = clipboardManager.primaryClip onCopyRequested?.invoke() @@ -205,6 +254,8 @@ private class CustomSelectionToolbar( @Composable fun CustomSelectionContainer( onCopyRequested: (() -> Unit), + onShareRequested: ((String) -> Unit), + onWebSearchRequested: ((String) -> Unit), onTranslateRequested: ((String) -> Unit), onDictionaryRequested: ((String) -> Unit), content: @Composable (toolbarHidden: Boolean) -> Unit @@ -220,6 +271,12 @@ fun CustomSelectionContainer( onCopyRequest = { onCopyRequested() }, + onShareRequest = { + onShareRequested(it) + }, + onWebSearchRequest = { + onWebSearchRequested(it) + }, onTranslateRequest = { onTranslateRequested(it) }, diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/components/custom_dialog/CustomDialogWithContent.kt b/app/src/main/java/ua/acclorite/book_story/presentation/components/custom_dialog/CustomDialogWithContent.kt index 0c8aa962..2fa53529 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/components/custom_dialog/CustomDialogWithContent.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/components/custom_dialog/CustomDialogWithContent.kt @@ -60,7 +60,7 @@ fun CustomDialogWithContent( modifier: Modifier = Modifier, drawableIcon: Painter? = null, imageVectorIcon: ImageVector? = null, - backgroundTransparency: Float = 0.3f, + backgroundTransparency: Float = 0.5f, title: String, description: String?, actionText: String?, diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/components/custom_dialog/CustomDialogWithLazyColumn.kt b/app/src/main/java/ua/acclorite/book_story/presentation/components/custom_dialog/CustomDialogWithLazyColumn.kt index 521d1229..00dde813 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/components/custom_dialog/CustomDialogWithLazyColumn.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/components/custom_dialog/CustomDialogWithLazyColumn.kt @@ -62,7 +62,7 @@ fun CustomDialogWithLazyColumn( modifier: Modifier = Modifier, drawableIcon: Painter? = null, imageVectorIcon: ImageVector? = null, - backgroundTransparency: Float = 0.3f, + backgroundTransparency: Float = 0.5f, title: String, description: String?, actionText: String?, diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/data/Functions.kt b/app/src/main/java/ua/acclorite/book_story/presentation/data/Functions.kt index 3f84ab28..0abd5fad 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/data/Functions.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/data/Functions.kt @@ -15,16 +15,18 @@ fun Double.removeDigits(digits: Int) = "%.${digits}f".format(this).replace(",", fun Intent.launchActivity( activity: ComponentActivity, + createChooser: Boolean = false, openInNewWindow: Boolean = true, success: (() -> Unit)? = null, error: () -> Unit ) { + val intent = if (createChooser) Intent.createChooser(this, "") else this if (openInNewWindow) { - this.flags = Intent.FLAG_ACTIVITY_NEW_TASK + intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK } try { - activity.baseContext.startActivity(this) + activity.baseContext.startActivity(intent) } catch (e: Exception) { error() return diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/about/nested/licenses/components/LicenseItem.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/about/nested/licenses/components/LicenseItem.kt index e6833260..8f45a0a4 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/about/nested/licenses/components/LicenseItem.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/about/nested/licenses/components/LicenseItem.kt @@ -90,7 +90,9 @@ fun LicenseItem( Text( text = library.author, style = MaterialTheme.typography.bodyMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant + color = MaterialTheme.colorScheme.onSurfaceVariant, + maxLines = 2, + overflow = TextOverflow.Ellipsis ) } if (library.licenses.isNotEmpty()) { diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/data/BookInfoViewModel.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/data/BookInfoViewModel.kt index 36b8a6d0..582d5a51 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/data/BookInfoViewModel.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/book_info/data/BookInfoViewModel.kt @@ -534,7 +534,7 @@ class BookInfoViewModel @Inject constructor( val updatedText = updatedBook.text val text = getText.execute(book.textPath) - if (updatedText != text.map { it.value.line }) { + if (updatedText != text) { textUpdated = true } diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/browse/components/adding_dialog/BrowseAddingDialogItem.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/browse/components/adding_dialog/BrowseAddingDialogItem.kt index 63e2e89b..64ff5e0f 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/browse/components/adding_dialog/BrowseAddingDialogItem.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/browse/components/adding_dialog/BrowseAddingDialogItem.kt @@ -64,7 +64,7 @@ fun BrowseAddingDialogItem(result: Pair, onClick: (Boole Spacer(modifier = Modifier.width(24.dp)) CustomCheckbox( selected = result.second, - MaterialTheme.colorScheme.surfaceContainerHigh + containerColor = MaterialTheme.colorScheme.surfaceContainerHigh ) } } diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/ReaderScreen.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/ReaderScreen.kt index de97e874..9c3c0a53 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/ReaderScreen.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/ReaderScreen.kt @@ -52,7 +52,7 @@ import ua.acclorite.book_story.domain.util.Constants import ua.acclorite.book_story.domain.util.OnNavigate import ua.acclorite.book_story.presentation.components.CustomAnimatedVisibility import ua.acclorite.book_story.presentation.components.CustomSelectionContainer -import ua.acclorite.book_story.presentation.components.customItems +import ua.acclorite.book_story.presentation.components.customItemsIndexed import ua.acclorite.book_story.presentation.components.is_messages.IsError import ua.acclorite.book_story.presentation.data.LocalNavigator import ua.acclorite.book_story.presentation.data.MainEvent @@ -188,9 +188,6 @@ private fun ReaderScreen( } } - val text = remember(state.value.text) { - state.value.text.toList().sortedBy { it.first } - } val fontFamily = remember(mainState.value.fontFamily) { Constants.FONTS.find { it.id == mainState.value.fontFamily @@ -277,10 +274,41 @@ private fun ReaderScreen( ).show() } }, + onShareRequested = { textToShare -> + onEvent( + ReaderEvent.OnOpenShareApp( + textToShare = textToShare, + context, + noAppsFound = { + Toast.makeText( + context, + context.getString(R.string.error_no_share_app), + Toast.LENGTH_SHORT + ).show() + } + ) + ) + }, + onWebSearchRequested = { textToSearch -> + onEvent( + ReaderEvent.OnOpenWebBrowser( + textToSearch = textToSearch, + context, + noAppsFound = { + Toast.makeText( + context, + context.getString(R.string.error_no_browser), + Toast.LENGTH_SHORT + ).show() + } + ) + ) + }, onTranslateRequested = { textToTranslate -> onEvent( ReaderEvent.OnOpenTranslator( - textToTranslate, + textToTranslate = textToTranslate, + translateWholeParagraph = false, context, noAppsFound = { Toast.makeText( @@ -300,7 +328,7 @@ private fun ReaderScreen( noAppsFound = { Toast.makeText( context, - context.getString(R.string.error_no_browser), + context.getString(R.string.error_no_dictionary), Toast.LENGTH_SHORT ).show() } @@ -359,11 +387,11 @@ private fun ReaderScreen( Spacer(modifier = Modifier.height(18.dp)) } - customItems( - text, key = { key -> key.first } - ) { line -> + customItemsIndexed( + state.value.text, key = { _, index -> index } + ) { _, line -> ReaderTextParagraph( - line = line.second, + line = line, context = context, fontFamily = fontFamily, fontColor = fontColor.value, diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/ReaderTextParagraph.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/ReaderTextParagraph.kt index e4284eb9..d05fb875 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/ReaderTextParagraph.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/components/ReaderTextParagraph.kt @@ -22,7 +22,6 @@ import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.TextUnit import ua.acclorite.book_story.R import ua.acclorite.book_story.domain.model.FontWithName -import ua.acclorite.book_story.domain.model.ReaderLine import ua.acclorite.book_story.presentation.screens.reader.data.ReaderEvent /** @@ -44,7 +43,7 @@ import ua.acclorite.book_story.presentation.screens.reader.data.ReaderEvent @OptIn(ExperimentalFoundationApi::class) @Composable fun LazyItemScope.ReaderTextParagraph( - line: ReaderLine, + line: String, context: Context, fontFamily: FontWithName, fontColor: Color, @@ -72,7 +71,7 @@ fun LazyItemScope.ReaderTextParagraph( append(" ") } - append(line.line) + append(line) }, modifier = Modifier.then( if ( @@ -85,7 +84,8 @@ fun LazyItemScope.ReaderTextParagraph( onDoubleClick = { onEvent( ReaderEvent.OnOpenTranslator( - textToTranslate = line.line, + textToTranslate = line, + translateWholeParagraph = true, context = context as ComponentActivity, noAppsFound = { Toast.makeText( diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/data/ReaderEvent.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/data/ReaderEvent.kt index 9134a288..36a149af 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/data/ReaderEvent.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/data/ReaderEvent.kt @@ -46,6 +46,19 @@ sealed class ReaderEvent { data class OnOpenTranslator( val textToTranslate: String, + val translateWholeParagraph: Boolean, + val context: ComponentActivity, + val noAppsFound: () -> Unit + ) : ReaderEvent() + + data class OnOpenShareApp( + val textToShare: String, + val context: ComponentActivity, + val noAppsFound: () -> Unit + ) : ReaderEvent() + + data class OnOpenWebBrowser( + val textToSearch: String, val context: ComponentActivity, val noAppsFound: () -> Unit ) : ReaderEvent() diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/data/ReaderState.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/data/ReaderState.kt index d290483f..5ddf0249 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/data/ReaderState.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/data/ReaderState.kt @@ -3,15 +3,13 @@ package ua.acclorite.book_story.presentation.screens.reader.data import androidx.compose.foundation.lazy.LazyListState import androidx.compose.runtime.Immutable import ua.acclorite.book_story.domain.model.Book -import ua.acclorite.book_story.domain.model.ReaderLine import ua.acclorite.book_story.domain.util.Constants -import ua.acclorite.book_story.domain.util.ID import ua.acclorite.book_story.domain.util.UIText @Immutable data class ReaderState( val book: Book = Constants.EMPTY_BOOK, - val text: Map = emptyMap(), + val text: List = emptyList(), val words: Int = 0, val letters: Int = 0, val listState: LazyListState = LazyListState(), diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/data/ReaderViewModel.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/data/ReaderViewModel.kt index 72afd8f2..2f74e5c0 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/data/ReaderViewModel.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/reader/data/ReaderViewModel.kt @@ -2,6 +2,7 @@ package ua.acclorite.book_story.presentation.screens.reader.data import android.app.SearchManager import android.content.Intent +import android.net.Uri import android.util.Log import androidx.activity.ComponentActivity import androidx.compose.runtime.snapshotFlow @@ -73,7 +74,6 @@ class ReaderViewModel @Inject constructor( } val textAsLine = text - .map { it.value.line } .joinToString( separator = "\n" ) @@ -338,7 +338,10 @@ class ReaderViewModel @Inject constructor( ) var translatorFailure = false - translatorIntent.launchActivity(event.context) { + translatorIntent.launchActivity( + event.context, + createChooser = !event.translateWholeParagraph + ) { translatorFailure = true } if (!translatorFailure) { @@ -357,22 +360,85 @@ class ReaderViewModel @Inject constructor( } } - is ReaderEvent.OnOpenDictionary -> { + is ReaderEvent.OnOpenShareApp -> { + launch { + val shareIntent = Intent() + + shareIntent.action = Intent.ACTION_SEND + shareIntent.type = "text/plain" + shareIntent.putExtra( + Intent.EXTRA_SUBJECT, + event.context.getString(R.string.app_name) + ) + shareIntent.putExtra( + Intent.EXTRA_TEXT, + event.textToShare.trim() + ) + + var shareFailure = false + shareIntent.launchActivity(event.context, createChooser = true) { + shareFailure = true + } + if (!shareFailure) { + return@launch + } + + event.noAppsFound() + } + } + + is ReaderEvent.OnOpenWebBrowser -> { launch { val browserIntent = Intent() browserIntent.action = Intent.ACTION_WEB_SEARCH browserIntent.putExtra( SearchManager.QUERY, - "dictionary" + - ": ${event.textToDefine.trim()}" + event.textToSearch ) - var failure = false + var browserFailure = false browserIntent.launchActivity(event.context) { - failure = true + browserFailure = true } - if (!failure) { + if (!browserFailure) { + return@launch + } + + event.noAppsFound() + } + } + + is ReaderEvent.OnOpenDictionary -> { + launch { + val dictionaryIntent = Intent() + val browserIntent = Intent() + + dictionaryIntent.type = "text/plain" + dictionaryIntent.action = Intent.ACTION_PROCESS_TEXT + dictionaryIntent.putExtra( + Intent.EXTRA_PROCESS_TEXT, + event.textToDefine.trim() + ) + dictionaryIntent.putExtra(Intent.EXTRA_PROCESS_TEXT_READONLY, true) + + browserIntent.action = Intent.ACTION_VIEW + val text = event.textToDefine.trim().replace(" ", "+") + browserIntent.data = Uri.parse("https://www.onelook.com/?w=$text") + + var dictionaryFailure = false + dictionaryIntent.launchActivity(event.context, createChooser = true) { + dictionaryFailure = true + } + if (!dictionaryFailure) { + return@launch + } + + var browserFailure = false + browserIntent.launchActivity(event.context) { + browserFailure = true + } + if (!browserFailure) { return@launch } diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/components/SegmentedButtonWithTitle.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/components/SegmentedButtonWithTitle.kt index 7fa647e2..6fddf8cc 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/components/SegmentedButtonWithTitle.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/components/SegmentedButtonWithTitle.kt @@ -1,38 +1,70 @@ package ua.acclorite.book_story.presentation.screens.settings.components +import androidx.compose.animation.expandHorizontally +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.scaleIn +import androidx.compose.animation.scaleOut +import androidx.compose.animation.shrinkHorizontally +import androidx.compose.animation.slideInVertically +import androidx.compose.animation.slideOutVertically +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.lazy.LazyRow +import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material3.SegmentedButton +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Done +import androidx.compose.material3.Icon +import androidx.compose.material3.SegmentedButtonColors import androidx.compose.material3.SegmentedButtonDefaults -import androidx.compose.material3.SingleChoiceSegmentedButtonRow import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.res.stringResource +import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +import ua.acclorite.book_story.R import ua.acclorite.book_story.domain.model.ButtonItem import ua.acclorite.book_story.presentation.components.CategoryTitle +import ua.acclorite.book_story.presentation.components.CustomAnimatedVisibility /** - * Segmented Button with Title. Contains [buttons]. + * Segmented Button with Title. + * Uses custom implementation(material3 one has many flaws). + * Contains [buttons]. + * + * @param modifier Modifier. + * @param title Title of the buttons. + * @param buttons [ButtonItem]s. + * @param enabled Whether button is enabled. + * @param horizontalPadding Horizontal item padding. + * @param verticalPadding Vertical item padding. + * @param onClick OnClick callback. */ @Composable fun SegmentedButtonWithTitle( modifier: Modifier = Modifier, title: String, buttons: List, - locked: Boolean, + enabled: Boolean = true, horizontalPadding: Dp = 18.dp, verticalPadding: Dp = 8.dp, onClick: (ButtonItem) -> Unit ) { - val colors = SegmentedButtonDefaults.colors() - Column( modifier .fillMaxWidth() @@ -42,50 +74,118 @@ fun SegmentedButtonWithTitle( Spacer(modifier = Modifier.height(8.dp)) - SingleChoiceSegmentedButtonRow { - buttons.forEachIndexed { index, buttonItem -> - SegmentedButton( - enabled = locked, - selected = buttonItem.selected, - onClick = { onClick(buttonItem) }, - shape = when (index) { - buttons.lastIndex -> RoundedCornerShape( - topEndPercent = 100, - bottomEndPercent = 100 + LazyRow(Modifier.fillMaxWidth()) { + item { + Row( + Modifier + .clip(CircleShape) + .border( + width = 0.5.dp, + color = SegmentedButtonDefaults.colors().activeBorderColor, + shape = CircleShape ) + .padding(0.5.dp) + ) { + buttons.forEachIndexed { index, buttonItem -> + SegmentedButton( + button = buttonItem, + enabled = enabled, + shape = when (index) { + buttons.lastIndex -> RoundedCornerShape( + topEndPercent = 100, + bottomEndPercent = 100 + ) - 0 -> RoundedCornerShape( - topStartPercent = 100, - bottomStartPercent = 100 - ) + 0 -> RoundedCornerShape( + topStartPercent = 100, + bottomStartPercent = 100 + ) - else -> RoundedCornerShape(0) - }, - colors = SegmentedButtonDefaults.colors( - disabledInactiveBorderColor = colors.activeBorderColor, - disabledInactiveContentColor = colors.inactiveContentColor, - disabledActiveContainerColor = colors.activeContainerColor, - disabledActiveContentColor = colors.activeContentColor, - disabledInactiveContainerColor = colors.inactiveContainerColor, - disabledActiveBorderColor = colors.activeBorderColor, - ), - label = { - Text( - text = buttonItem.title, - maxLines = 1, - overflow = TextOverflow.Ellipsis + else -> RoundedCornerShape(0) + }, + onClick = { onClick(buttonItem) } ) } - ) + } } } } } +/** + * Custom Segmented button. + * Adjusts width based on component name. + * + * @param button [ButtonItem]. + * @param enabled Whether can be clicked. + * @param shape Shape of the button. For proper implementation should have 100% corners on first and last buttons(start and end). + * @param colors [SegmentedButtonColors]. + * @param onClick OnClick callback. + */ +@Composable +private fun SegmentedButton( + button: ButtonItem, + enabled: Boolean, + shape: RoundedCornerShape, + colors: SegmentedButtonColors = SegmentedButtonDefaults.colors(), + onClick: () -> Unit +) { + Row( + modifier = Modifier + .height(40.dp) + .clip(shape) + .clickable(enabled = enabled && !button.selected) { + onClick() + } + .border( + width = 0.5.dp, + color = colors.activeBorderColor, + shape = shape + ) + .padding(0.5.dp) + .background( + if (button.selected) colors.activeContainerColor + else Color.Transparent, + shape = shape + ) + .padding(horizontal = 18.dp), + verticalAlignment = Alignment.CenterVertically + ) { + CustomAnimatedVisibility( + visible = button.selected, + enter = expandHorizontally() + + slideInVertically(initialOffsetY = { it / 2 }) + + scaleIn() + + fadeIn(), + exit = shrinkHorizontally() + + slideOutVertically(targetOffsetY = { it / 2 }) + + scaleOut() + + fadeOut() + ) { + Row { + Icon( + imageVector = Icons.Default.Done, + contentDescription = stringResource(id = R.string.selected_content_desc), + modifier = Modifier + .size(18.dp), + tint = colors.activeContentColor + ) + Spacer(modifier = Modifier.width(8.dp)) + } + } + + Text( + text = button.title, + style = button.textStyle, + fontWeight = FontWeight.Medium, + color = if (button.selected) colors.activeContentColor + else colors.inactiveContentColor + ) + } +} + - - diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/appearance/components/settings/DarkThemeSetting.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/appearance/components/settings/DarkThemeSetting.kt index b661da8c..4a534ec6 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/appearance/components/settings/DarkThemeSetting.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/appearance/components/settings/DarkThemeSetting.kt @@ -25,17 +25,16 @@ fun LazyItemScope.DarkThemeSetting( SegmentedButtonWithTitle( title = stringResource(id = R.string.dark_theme_option), modifier = Modifier.animateItem(), - locked = true, buttons = DarkTheme.entries.map { ButtonItem( it.toString(), - when (it) { + title = when (it) { DarkTheme.OFF -> stringResource(id = R.string.dark_theme_off) DarkTheme.ON -> stringResource(id = R.string.dark_theme_on) DarkTheme.FOLLOW_SYSTEM -> stringResource(id = R.string.dark_theme_follow_system) }, - MaterialTheme.typography.labelLarge, - it == state.value.darkTheme + textStyle = MaterialTheme.typography.labelLarge, + selected = it == state.value.darkTheme ) } ) { diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/appearance/components/settings/PureDarkSetting.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/appearance/components/settings/PureDarkSetting.kt index 0b1815ee..37a47365 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/appearance/components/settings/PureDarkSetting.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/appearance/components/settings/PureDarkSetting.kt @@ -33,17 +33,16 @@ fun LazyItemScope.PureDarkSetting( ) { SegmentedButtonWithTitle( title = stringResource(id = R.string.pure_dark_option), - locked = true, buttons = PureDark.entries.map { ButtonItem( - it.toString(), - when (it) { + id = it.toString(), + title = when (it) { PureDark.OFF -> stringResource(id = R.string.pure_dark_off) PureDark.ON -> stringResource(id = R.string.pure_dark_on) PureDark.SAVER -> stringResource(id = R.string.pure_dark_power_saver) }, - MaterialTheme.typography.labelLarge, - it == state.value.pureDark + textStyle = MaterialTheme.typography.labelLarge, + selected = it == state.value.pureDark ) } ) { diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/appearance/components/settings/ThemeContrastSetting.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/appearance/components/settings/ThemeContrastSetting.kt index 7b21d698..ae1c910c 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/appearance/components/settings/ThemeContrastSetting.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/appearance/components/settings/ThemeContrastSetting.kt @@ -54,17 +54,17 @@ fun LazyItemScope.ThemeContrastSetting( ) { SegmentedButtonWithTitle( title = stringResource(id = R.string.theme_contrast_option), - locked = state.value.theme != Theme.DYNAMIC, + enabled = state.value.theme != Theme.DYNAMIC, buttons = ThemeContrast.entries.map { ButtonItem( - it.toString(), - when (it) { + id = it.toString(), + title = when (it) { ThemeContrast.STANDARD -> stringResource(id = R.string.theme_contrast_standard) ThemeContrast.MEDIUM -> stringResource(id = R.string.theme_contrast_medium) ThemeContrast.HIGH -> stringResource(id = R.string.theme_contrast_high) }, - MaterialTheme.typography.labelLarge, - it == state.value.themeContrast + textStyle = MaterialTheme.typography.labelLarge, + selected = it == state.value.themeContrast ) } ) { diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/reader/components/settings/FontFamilySetting.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/reader/components/settings/FontFamilySetting.kt index 9f89d94a..ee3ddde3 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/reader/components/settings/FontFamilySetting.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/reader/components/settings/FontFamilySetting.kt @@ -35,12 +35,12 @@ fun LazyItemScope.FontFamilySetting( chips = Constants.FONTS .map { ButtonItem( - it.id, - it.fontName.asString(), - MaterialTheme.typography.labelLarge.copy( + id = it.id, + title = it.fontName.asString(), + textStyle = MaterialTheme.typography.labelLarge.copy( fontFamily = it.font ), - it.id == fontFamily.id + selected = it.id == fontFamily.id ) }, onClick = { diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/reader/components/settings/FontStyleSetting.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/reader/components/settings/FontStyleSetting.kt index 57044070..05e2c796 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/reader/components/settings/FontStyleSetting.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/settings/nested/reader/components/settings/FontStyleSetting.kt @@ -13,7 +13,7 @@ import ua.acclorite.book_story.domain.model.ButtonItem import ua.acclorite.book_story.domain.util.Constants import ua.acclorite.book_story.presentation.data.MainEvent import ua.acclorite.book_story.presentation.data.MainState -import ua.acclorite.book_story.presentation.screens.settings.components.ChipsWithTitle +import ua.acclorite.book_story.presentation.screens.settings.components.SegmentedButtonWithTitle /** * Font Style setting. @@ -30,27 +30,27 @@ fun LazyItemScope.FontStyleSetting( } ?: Constants.FONTS[0] } - ChipsWithTitle( + SegmentedButtonWithTitle( title = stringResource(id = R.string.font_style_option), modifier = Modifier.animateItem(), - chips = listOf( + buttons = listOf( ButtonItem( - "normal", - stringResource(id = R.string.font_style_normal), - MaterialTheme.typography.labelLarge.copy( + id = "normal", + title = stringResource(id = R.string.font_style_normal), + textStyle = MaterialTheme.typography.labelLarge.copy( fontFamily = fontFamily.font, fontStyle = FontStyle.Normal ), - !state.value.isItalic!! + selected = !state.value.isItalic!! ), ButtonItem( - "italic", - stringResource(id = R.string.font_style_italic), - MaterialTheme.typography.labelLarge.copy( + id = "italic", + title = stringResource(id = R.string.font_style_italic), + textStyle = MaterialTheme.typography.labelLarge.copy( fontFamily = fontFamily.font, fontStyle = FontStyle.Italic ), - state.value.isItalic!! + selected = state.value.isItalic!! ), ), onClick = { diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/screens/start/components/StartSettings.kt b/app/src/main/java/ua/acclorite/book_story/presentation/screens/start/components/StartSettings.kt index 40b8f799..3d3aa75f 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/screens/start/components/StartSettings.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/screens/start/components/StartSettings.kt @@ -64,10 +64,10 @@ fun StartSettings( val languages = remember(mainState.value.language) { Constants.LANGUAGES.sortedBy { it.second }.map { ButtonItem( - it.first, - it.second, - TextStyle(), - it.first == mainState.value.language + id = it.first, + title = it.second, + textStyle = TextStyle(), + selected = it.first == mainState.value.language ) }.sortedBy { it.title } } diff --git a/app/src/main/java/ua/acclorite/book_story/presentation/ui/Transitions.kt b/app/src/main/java/ua/acclorite/book_story/presentation/ui/Transitions.kt index b6c943b6..6f043cf3 100644 --- a/app/src/main/java/ua/acclorite/book_story/presentation/ui/Transitions.kt +++ b/app/src/main/java/ua/acclorite/book_story/presentation/ui/Transitions.kt @@ -58,22 +58,6 @@ fun DefaultTransition( } } -@Composable -fun T.DefaultTransition( - visible: Boolean, - modifier: Modifier = Modifier, - content: @Composable T.() -> Unit -) { - CustomAnimatedVisibility( - visible = visible, - modifier = modifier, - enter = Transitions.DefaultTransitionIn, - exit = Transitions.DefaultTransitionOut - ) { - content() - } -} - @Composable fun SlidingTransition( visible: Boolean, diff --git a/app/src/main/res/drawable/browse_screen_filled.png b/app/src/main/res/drawable/browse_screen_filled.png new file mode 100644 index 00000000..e52334d9 Binary files /dev/null and b/app/src/main/res/drawable/browse_screen_filled.png differ diff --git a/app/src/main/res/drawable/browse_screen_filled.xml b/app/src/main/res/drawable/browse_screen_filled.xml deleted file mode 100644 index a03c6520..00000000 --- a/app/src/main/res/drawable/browse_screen_filled.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/browse_screen_outlined.png b/app/src/main/res/drawable/browse_screen_outlined.png new file mode 100644 index 00000000..8f9c1bb3 Binary files /dev/null and b/app/src/main/res/drawable/browse_screen_outlined.png differ diff --git a/app/src/main/res/drawable/browse_screen_outlined.xml b/app/src/main/res/drawable/browse_screen_outlined.xml deleted file mode 100644 index d0bf00e9..00000000 --- a/app/src/main/res/drawable/browse_screen_outlined.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/empty_browse.png b/app/src/main/res/drawable/empty_browse.png new file mode 100644 index 00000000..6a4df38f Binary files /dev/null and b/app/src/main/res/drawable/empty_browse.png differ diff --git a/app/src/main/res/drawable/empty_browse.xml b/app/src/main/res/drawable/empty_browse.xml deleted file mode 100644 index d3230a31..00000000 --- a/app/src/main/res/drawable/empty_browse.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/empty_history.png b/app/src/main/res/drawable/empty_history.png new file mode 100644 index 00000000..a714c2a6 Binary files /dev/null and b/app/src/main/res/drawable/empty_history.png differ diff --git a/app/src/main/res/drawable/empty_history.xml b/app/src/main/res/drawable/empty_history.xml deleted file mode 100644 index ad6d8ff3..00000000 --- a/app/src/main/res/drawable/empty_history.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/empty_library.png b/app/src/main/res/drawable/empty_library.png new file mode 100644 index 00000000..2099c29c Binary files /dev/null and b/app/src/main/res/drawable/empty_library.png differ diff --git a/app/src/main/res/drawable/empty_library.xml b/app/src/main/res/drawable/empty_library.xml deleted file mode 100644 index 8612cf0e..00000000 --- a/app/src/main/res/drawable/empty_library.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/error.png b/app/src/main/res/drawable/error.png new file mode 100644 index 00000000..b86b89de Binary files /dev/null and b/app/src/main/res/drawable/error.png differ diff --git a/app/src/main/res/drawable/error.xml b/app/src/main/res/drawable/error.xml deleted file mode 100644 index 3f8953db..00000000 --- a/app/src/main/res/drawable/error.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/github.png b/app/src/main/res/drawable/github.png new file mode 100644 index 00000000..e03c369a Binary files /dev/null and b/app/src/main/res/drawable/github.png differ diff --git a/app/src/main/res/drawable/github.xml b/app/src/main/res/drawable/github.xml deleted file mode 100644 index 85acf69d..00000000 --- a/app/src/main/res/drawable/github.xml +++ /dev/null @@ -1,14 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/history_screen_filled.png b/app/src/main/res/drawable/history_screen_filled.png new file mode 100644 index 00000000..0ce352d9 Binary files /dev/null and b/app/src/main/res/drawable/history_screen_filled.png differ diff --git a/app/src/main/res/drawable/history_screen_filled.xml b/app/src/main/res/drawable/history_screen_filled.xml deleted file mode 100644 index 3c9844ba..00000000 --- a/app/src/main/res/drawable/history_screen_filled.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/history_screen_outlined.png b/app/src/main/res/drawable/history_screen_outlined.png new file mode 100644 index 00000000..a52acc36 Binary files /dev/null and b/app/src/main/res/drawable/history_screen_outlined.png differ diff --git a/app/src/main/res/drawable/history_screen_outlined.xml b/app/src/main/res/drawable/history_screen_outlined.xml deleted file mode 100644 index ed47780f..00000000 --- a/app/src/main/res/drawable/history_screen_outlined.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/library_screen_filled.png b/app/src/main/res/drawable/library_screen_filled.png new file mode 100644 index 00000000..640f15af Binary files /dev/null and b/app/src/main/res/drawable/library_screen_filled.png differ diff --git a/app/src/main/res/drawable/library_screen_filled.xml b/app/src/main/res/drawable/library_screen_filled.xml deleted file mode 100644 index 6638db7b..00000000 --- a/app/src/main/res/drawable/library_screen_filled.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/library_screen_outlined.png b/app/src/main/res/drawable/library_screen_outlined.png new file mode 100644 index 00000000..2a5ff603 Binary files /dev/null and b/app/src/main/res/drawable/library_screen_outlined.png differ diff --git a/app/src/main/res/drawable/library_screen_outlined.xml b/app/src/main/res/drawable/library_screen_outlined.xml deleted file mode 100644 index ea5bab63..00000000 --- a/app/src/main/res/drawable/library_screen_outlined.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/reddit.png b/app/src/main/res/drawable/reddit.png new file mode 100644 index 00000000..fbb7242a Binary files /dev/null and b/app/src/main/res/drawable/reddit.png differ diff --git a/app/src/main/res/drawable/reddit.xml b/app/src/main/res/drawable/reddit.xml deleted file mode 100644 index 665e0eaa..00000000 --- a/app/src/main/res/drawable/reddit.xml +++ /dev/null @@ -1,45 +0,0 @@ - - - - - - - - - - - diff --git a/app/src/main/res/drawable/start_done.png b/app/src/main/res/drawable/start_done.png new file mode 100644 index 00000000..6493de0c Binary files /dev/null and b/app/src/main/res/drawable/start_done.png differ diff --git a/app/src/main/res/drawable/start_done.xml b/app/src/main/res/drawable/start_done.xml deleted file mode 100644 index 3b9056b3..00000000 --- a/app/src/main/res/drawable/start_done.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/tryzub.png b/app/src/main/res/drawable/tryzub.png new file mode 100644 index 00000000..b51f4d42 Binary files /dev/null and b/app/src/main/res/drawable/tryzub.png differ diff --git a/app/src/main/res/drawable/tryzub.xml b/app/src/main/res/drawable/tryzub.xml deleted file mode 100644 index 176f1594..00000000 --- a/app/src/main/res/drawable/tryzub.xml +++ /dev/null @@ -1,13 +0,0 @@ - - - - - diff --git a/app/src/main/res/drawable/x_logo.png b/app/src/main/res/drawable/x_logo.png new file mode 100644 index 00000000..38d6b28c Binary files /dev/null and b/app/src/main/res/drawable/x_logo.png differ diff --git a/app/src/main/res/drawable/x_logo.xml b/app/src/main/res/drawable/x_logo.xml deleted file mode 100644 index 85cf4d58..00000000 --- a/app/src/main/res/drawable/x_logo.xml +++ /dev/null @@ -1,11 +0,0 @@ - - - - - diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 497e2f34..6d393843 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -101,7 +101,9 @@ Помилка: %1$s Цей файл пустий або пошкоджений. Не знайдено жодного перекладача. + Не знайдено жодного словника. Не знайдено жодного браузера. + Не знайдено застосунка щоб поділитися. Вміст книги пустий. Будь ласка, перевірте вміст книги та оновіть її. @@ -160,6 +162,8 @@ Завантажити Готово Увімкнути + Веб-пошук + Поділитися файли diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 86cbf832..e2480a92 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -118,7 +118,9 @@ Error: %1$s This file is empty or corrupted. No translator app found. + No dictionary app found. No browser app found. + No app to share found. The book content is empty. Please, make sure that the book content is not empty and update it. @@ -176,6 +178,8 @@ Download Done Enable + Web search + Share files diff --git a/assets/Book-Info Dark + Frame.png b/assets/Book-Info Dark + Frame.png deleted file mode 100644 index 9e9b40b4..00000000 Binary files a/assets/Book-Info Dark + Frame.png and /dev/null differ diff --git a/assets/Book-Info Light + Frame.png b/assets/Book-Info Light + Frame.png deleted file mode 100644 index 43f2bebf..00000000 Binary files a/assets/Book-Info Light + Frame.png and /dev/null differ diff --git a/assets/Library Dark + Frame.png b/assets/Library Dark + Frame.png deleted file mode 100644 index d3c2fedc..00000000 Binary files a/assets/Library Dark + Frame.png and /dev/null differ diff --git a/assets/Library Light + Frame.png b/assets/Library Light + Frame.png deleted file mode 100644 index 431efa89..00000000 Binary files a/assets/Library Light + Frame.png and /dev/null differ diff --git a/assets/Reader Dark + Frame.png b/assets/Reader Dark + Frame.png deleted file mode 100644 index 5320ce3f..00000000 Binary files a/assets/Reader Dark + Frame.png and /dev/null differ diff --git a/assets/Reader Light + Frame.png b/assets/Reader Light + Frame.png deleted file mode 100644 index 0b43646e..00000000 Binary files a/assets/Reader Light + Frame.png and /dev/null differ diff --git a/assets/Reader Translator.png b/assets/Reader Translator.png deleted file mode 100644 index 852647cb..00000000 Binary files a/assets/Reader Translator.png and /dev/null differ diff --git a/assets/Translator Settings.png b/assets/Translator Settings.png deleted file mode 100644 index eb4deda3..00000000 Binary files a/assets/Translator Settings.png and /dev/null differ diff --git a/fastlane/metadata/android/en-US/changelogs/6.txt b/fastlane/metadata/android/en-US/changelogs/6.txt new file mode 100644 index 00000000..78ccca08 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/6.txt @@ -0,0 +1,8 @@ +* Added loading indicator in AboutScreen's check for updates +* Reduced APK size (30Mb → 12Mb) +* Improved performance +* Improved text parsers +* Fixed various crashes +* Various small changes +* Removed no longer valid Credits +* More small changes \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png index d3c2fedc..c6fbd981 100644 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png and b/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png index 431efa89..5848b693 100644 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png and b/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png index 5320ce3f..cafdf773 100644 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png and b/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png index 0b43646e..1c2f52b5 100644 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png and b/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png index 9e9b40b4..0bdf294f 100644 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png and b/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/6.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/6.png index 43f2bebf..8656d749 100644 Binary files a/fastlane/metadata/android/en-US/images/phoneScreenshots/6.png and b/fastlane/metadata/android/en-US/images/phoneScreenshots/6.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/7.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/7.png new file mode 100644 index 00000000..bdf7679d Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/7.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/8.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/8.png new file mode 100644 index 00000000..804fe9b4 Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/8.png differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/9.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/9.png new file mode 100644 index 00000000..bb50290d Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/9.png differ