⚖️ Merge changes from "development"
Merge changes from another branch.
18
README.md
|
|
@ -39,12 +39,14 @@ Built using Jetpack Compose and Kotlin.
|
|||
|
||||
<img src="https://github.com/Acclorite/book-story/blob/master/assets/readme/book's-story.gif" width="35%">
|
||||
<div>
|
||||
<img src="https://github.com/Acclorite/book-story/blob/master/assets/Library Dark + Frame.png" width="30%" />
|
||||
<img src="https://github.com/Acclorite/book-story/blob/master/assets/Book-Info Dark + Frame.png" width="30%" />
|
||||
<img src="https://github.com/Acclorite/book-story/blob/master/assets/Reader Dark + Frame.png" width="30%" />
|
||||
<img src="https://github.com/Acclorite/book-story/blob/master/assets/Library Light + Frame.png" width="30%" />
|
||||
<img src="https://github.com/Acclorite/book-story/blob/master/assets/Book-Info Light + Frame.png" width="30%" />
|
||||
<img src="https://github.com/Acclorite/book-story/blob/master/assets/Reader Light + Frame.png" width="30%" />
|
||||
<img src="/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png" width="30%" />
|
||||
<img src="/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png" width="30%" />
|
||||
<img src="/fastlane/metadata/android/en-US/images/phoneScreenshots/9.png" width="30%" />
|
||||
<img src="/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png" width="30%" />
|
||||
<img src="/fastlane/metadata/android/en-US/images/phoneScreenshots/5.png" width="30%" />
|
||||
<img src="/fastlane/metadata/android/en-US/images/phoneScreenshots/8.png" width="30%" />
|
||||
<img src="/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png" width="45%" />
|
||||
<img src="/fastlane/metadata/android/en-US/images/phoneScreenshots/6.png" width="45%" />
|
||||
</div>
|
||||
</br>
|
||||
|
||||
|
|
@ -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!
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
7
app/proguard-rules.pro
vendored
|
|
@ -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(); }
|
||||
-keepnames class kotlin.reflect.jvm.internal.impl.builtins.PrimitiveType { values(); }
|
||||
-keepnames class * implements android.os.Parcelable { ** CREATOR; }
|
||||
|
|
@ -17,26 +17,6 @@
|
|||
android:name="ua.acclorite.book_story.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION"
|
||||
tools:node="remove" />
|
||||
|
||||
<queries>
|
||||
|
||||
<!-- Translator -->
|
||||
<intent>
|
||||
<action android:name="android.intent.action.PROCESS_TEXT" />
|
||||
<data android:mimeType="text/plain" />
|
||||
</intent>
|
||||
|
||||
<!-- Browser -->
|
||||
<intent>
|
||||
<action android:name="android.intent.action.WEB_SEARCH" />
|
||||
</intent>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<data android:scheme="https" />
|
||||
</intent>
|
||||
|
||||
</queries>
|
||||
|
||||
<application
|
||||
android:name=".Application"
|
||||
android:allowBackup="false"
|
||||
|
|
|
|||
|
|
@ -1,16 +1,17 @@
|
|||
package ua.acclorite.book_story.data.parser.epub
|
||||
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.BitmapFactory
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import nl.siegmann.epublib.epub.EpubReader
|
||||
import org.jsoup.Jsoup
|
||||
import ua.acclorite.book_story.data.parser.FileParser
|
||||
import ua.acclorite.book_story.domain.model.Book
|
||||
import ua.acclorite.book_story.domain.model.Category
|
||||
import ua.acclorite.book_story.domain.util.CoverImage
|
||||
import ua.acclorite.book_story.domain.util.UIText
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.util.zip.ZipFile
|
||||
import javax.inject.Inject
|
||||
|
||||
class EpubFileParser @Inject constructor() : FileParser {
|
||||
|
|
@ -21,45 +22,81 @@ class EpubFileParser @Inject constructor() : FileParser {
|
|||
}
|
||||
|
||||
try {
|
||||
val epubReader = EpubReader()
|
||||
val book = withContext(Dispatchers.IO) {
|
||||
FileInputStream(file).use {
|
||||
epubReader.readEpub(it)
|
||||
var book: Pair<Book, CoverImage?>? = 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
|
||||
}
|
||||
|
|
@ -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<String>()
|
||||
|
||||
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<String>()
|
||||
|
||||
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(
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<ID, ReaderLine> {
|
||||
return repository.getBookText(textPath = textPath).withIndex().associate {
|
||||
it.index to ReaderLine(line = it.value)
|
||||
}
|
||||
suspend fun execute(textPath: String): List<String> {
|
||||
return repository.getBookText(textPath = textPath)
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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?,
|
||||
|
|
|
|||
|
|
@ -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?,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ fun BrowseAddingDialogItem(result: Pair<NullableBook, Selected>, onClick: (Boole
|
|||
Spacer(modifier = Modifier.width(24.dp))
|
||||
CustomCheckbox(
|
||||
selected = result.second,
|
||||
MaterialTheme.colorScheme.surfaceContainerHigh
|
||||
containerColor = MaterialTheme.colorScheme.surfaceContainerHigh
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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<ID, ReaderLine> = emptyMap(),
|
||||
val text: List<String> = emptyList(),
|
||||
val words: Int = 0,
|
||||
val letters: Int = 0,
|
||||
val listState: LazyListState = LazyListState(),
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<ButtonItem>,
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
}
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
}
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
)
|
||||
}
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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 = {
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,22 +58,6 @@ fun DefaultTransition(
|
|||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun <T> 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,
|
||||
|
|
|
|||
BIN
app/src/main/res/drawable/browse_screen_filled.png
Normal file
|
After Width: | Height: | Size: 2 KiB |
|
|
@ -1,12 +0,0 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:autoMirrored="true"
|
||||
android:height="24dp"
|
||||
android:viewportHeight="960"
|
||||
android:viewportWidth="960"
|
||||
android:width="24dp">
|
||||
|
||||
<path
|
||||
android:fillColor="#e8eaed"
|
||||
android:pathData="m260,700 l300,-140 140,-300 -300,140 -140,300ZM480,520q-17,0 -28.5,-11.5T440,480q0,-17 11.5,-28.5T480,440q17,0 28.5,11.5T520,480q0,17 -11.5,28.5T480,520ZM480,880q-83,0 -156,-31.5T197,763q-54,-54 -85.5,-127T80,480q0,-83 31.5,-156T197,197q54,-54 127,-85.5T480,80q83,0 156,31.5T763,197q54,54 85.5,127T880,480q0,83 -31.5,156T763,763q-54,54 -127,85.5T480,880Z" />
|
||||
|
||||
</vector>
|
||||
BIN
app/src/main/res/drawable/browse_screen_outlined.png
Normal file
|
After Width: | Height: | Size: 2.5 KiB |
|
|
@ -1,12 +0,0 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:autoMirrored="true"
|
||||
android:height="24dp"
|
||||
android:viewportHeight="960"
|
||||
android:viewportWidth="960"
|
||||
android:width="24dp">
|
||||
|
||||
<path
|
||||
android:fillColor="#e8eaed"
|
||||
android:pathData="m260,700 l300,-140 140,-300 -300,140 -140,300ZM480,520q-17,0 -28.5,-11.5T440,480q0,-17 11.5,-28.5T480,440q17,0 28.5,11.5T520,480q0,17 -11.5,28.5T480,520ZM480,880q-83,0 -156,-31.5T197,763q-54,-54 -85.5,-127T80,480q0,-83 31.5,-156T197,197q54,-54 127,-85.5T480,80q83,0 156,31.5T763,197q54,54 85.5,127T880,480q0,83 -31.5,156T763,763q-54,54 -127,85.5T480,880ZM480,800q134,0 227,-93t93,-227q0,-134 -93,-227t-227,-93q-134,0 -227,93t-93,227q0,134 93,227t227,93ZM480,480Z" />
|
||||
|
||||
</vector>
|
||||
BIN
app/src/main/res/drawable/empty_browse.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
|
|
@ -1,12 +0,0 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:autoMirrored="true"
|
||||
android:height="128dp"
|
||||
android:viewportHeight="960"
|
||||
android:viewportWidth="960"
|
||||
android:width="128dp">
|
||||
|
||||
<path
|
||||
android:fillColor="#e8eaed"
|
||||
android:pathData="M226,788q-21.75,0 -37.87,-16.13Q172,755.75 172,734v-508q0,-21.75 16.13,-37.88Q204.25,172 226,172h205q-13,-27.5 2.95,-53.75 15.94,-26.25 46,-26.25Q511,92 527,118.25q16,26.25 2,53.75h205q21.75,0 37.88,16.12Q788,204.25 788,226v508q0,21.75 -16.12,37.87Q755.75,788 734,788L226,788ZM226,766h508q12,0 22,-10t10,-22v-508q0,-12 -10,-22t-22,-10L226,194q-12,0 -22,10t-10,22v508q0,12 10,22t22,10ZM306,642h226v-22L306,620v22ZM306,491h348v-22L306,469v22ZM306,340h348v-22L306,318v22ZM479.79,178q12.79,0 22.5,-9t9.71,-22.5q0,-13.5 -9.71,-22.5t-22.5,-9Q467,115 458,124t-9,22.5q0,13.5 9,22.5t21.79,9ZM194,766v-572,572Z" />
|
||||
|
||||
</vector>
|
||||
BIN
app/src/main/res/drawable/empty_history.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
|
|
@ -1,14 +0,0 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:autoMirrored="true"
|
||||
android:height="128dp"
|
||||
android:viewportHeight="960"
|
||||
android:viewportWidth="960"
|
||||
android:width="128dp">
|
||||
|
||||
<path
|
||||
android:fillColor="#e8eaed"
|
||||
android:pathData="M479.99,839q-49.99,0 -84.49,-34.51Q361,769.98 361,720q0,-47 31,-80.5t77,-38.5v-110L255,491v-136h-98v-218h218v218h-98v114h406v-114q-46,-5 -77,-38.29 -31,-33.3 -31,-80.71 0,-49.98 34.51,-84.49Q644.03,117 694.01,117q49.99,0 84.49,34.51Q813,186.02 813,236q0,47 -31,80.5T705,355v136L491,491v110q46,5 77,38.29 31,33.3 31,80.71 0,49.98 -34.51,84.49Q529.97,839 479.99,839ZM693.87,333q40.13,0 68.63,-28.37 28.5,-28.37 28.5,-68.5 0,-40.13 -28.37,-68.63 -28.37,-28.5 -68.5,-28.5 -40.13,0 -68.63,28.37 -28.5,28.37 -28.5,68.5 0,40.13 28.37,68.63 28.37,28.5 68.5,28.5ZM179,333h174v-174L179,159v174ZM479.87,817q40.13,0 68.63,-28.37 28.5,-28.37 28.5,-68.5 0,-40.13 -28.37,-68.63 -28.37,-28.5 -68.5,-28.5 -40.13,0 -68.63,28.37 -28.5,28.37 -28.5,68.5 0,40.13 28.37,68.63 28.37,28.5 68.5,28.5ZM266,246ZM694,236ZM480,720Z"
|
||||
tools:ignore="VectorPath" />
|
||||
|
||||
</vector>
|
||||
BIN
app/src/main/res/drawable/empty_library.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
|
|
@ -1,12 +0,0 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:autoMirrored="true"
|
||||
android:height="128dp"
|
||||
android:viewportHeight="960"
|
||||
android:viewportWidth="960"
|
||||
android:width="128dp">
|
||||
|
||||
<path
|
||||
android:fillColor="#e8eaed"
|
||||
android:pathData="M278,566v22h-92q-21.75,0 -37.87,-16.13Q132,555.75 132,534v-348q0,-21.75 16.13,-37.88Q164.25,132 186,132h348q21.75,0 37.88,16.12Q588,164.25 588,186v92h-22v-92q0,-14 -9,-23t-23,-9L186,154q-14,0 -23,9t-9,23v348q0,14 9,23t23,9h92ZM426,828q-21.75,0 -37.87,-16.13Q372,795.75 372,774v-348q0,-21.75 16.13,-37.88Q404.25,372 426,372h348q21.75,0 37.88,16.12Q828,404.25 828,426v348q0,21.75 -16.12,37.87Q795.75,828 774,828L426,828ZM426,806h348q14,0 23,-9t9,-23v-348q0,-14 -9,-23t-23,-9L426,394q-14,0 -23,9t-9,23v348q0,14 9,23t23,9ZM600,600Z" />
|
||||
|
||||
</vector>
|
||||
BIN
app/src/main/res/drawable/error.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
|
|
@ -1,12 +0,0 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:autoMirrored="true"
|
||||
android:height="128dp"
|
||||
android:viewportHeight="960"
|
||||
android:viewportWidth="960"
|
||||
android:width="128dp">
|
||||
|
||||
<path
|
||||
android:fillColor="#e8eaed"
|
||||
android:pathData="M480,680q14,0 23.5,-9.5T513,647q0,-14 -9.5,-23.5T480,614q-14,0 -23.5,9.5T447,647q0,14 9.5,23.5T480,680ZM453,527h60v-253h-60v253ZM481,931L346,800L160,800v-186L26,480l134,-134v-186h186l135,-134 133,134h186v186l134,134 -134,134v186L614,800L481,931Z" />
|
||||
|
||||
</vector>
|
||||
BIN
app/src/main/res/drawable/github.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
|
|
@ -1,14 +0,0 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:height="24dp"
|
||||
android:viewportHeight="1024"
|
||||
android:viewportWidth="1024"
|
||||
android:width="24dp">
|
||||
|
||||
<path
|
||||
android:fillColor="#e8eaed"
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M512,0C229.1,0 0,229.1 0,512C0,738.6 146.6,929.9 350.1,997.8C375.7,1002.2 385.3,986.9 385.3,973.4C385.3,961.3 384.6,921 384.6,878.1C256,901.8 222.7,846.7 212.5,817.9C206.7,803.2 181.8,757.8 160,745.6C142.1,736 116.5,712.3 159.4,711.7C199.7,711 228.5,748.8 238.1,764.2C284.2,841.6 357.8,819.8 387.2,806.4C391.7,773.1 405.1,750.7 419.8,737.9C305.9,725.1 186.9,681 186.9,485.1C186.9,429.4 206.7,383.4 239.4,347.5C234.2,334.7 216.3,282.2 244.5,211.8C244.5,211.8 287.4,198.4 385.3,264.3C426.2,252.8 469.8,247 513.3,247C556.8,247 600.3,252.8 641.3,264.3C739.2,197.8 782.1,211.8 782.1,211.8C810.2,282.2 792.3,334.7 787.2,347.5C819.8,383.4 839.7,428.8 839.7,485.1C839.7,681.6 720,725.1 606.1,737.9C624.6,753.9 640.6,784.6 640.6,832.6C640.6,901.1 640,956.2 640,973.4C640,986.9 649.6,1002.9 675.2,997.8C877.4,929.9 1024,737.9 1024,512C1024,229.1 794.9,0 512,0Z"
|
||||
tools:ignore="VectorPath" />
|
||||
|
||||
</vector>
|
||||
BIN
app/src/main/res/drawable/history_screen_filled.png
Normal file
|
After Width: | Height: | Size: 1.7 KiB |
|
|
@ -1,12 +0,0 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:autoMirrored="true"
|
||||
android:height="24dp"
|
||||
android:viewportHeight="960"
|
||||
android:viewportWidth="960"
|
||||
android:width="24dp">
|
||||
|
||||
<path
|
||||
android:fillColor="#e8eaed"
|
||||
android:pathData="m612,668 l56,-56 -148,-148v-184h-80v216l172,172ZM480,880q-83,0 -156,-31.5T197,763q-54,-54 -85.5,-127T80,480q0,-83 31.5,-156T197,197q54,-54 127,-85.5T480,80q83,0 156,31.5T763,197q54,54 85.5,127T880,480q0,83 -31.5,156T763,763q-54,54 -127,85.5T480,880Z" />
|
||||
|
||||
</vector>
|
||||
BIN
app/src/main/res/drawable/history_screen_outlined.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
|
|
@ -1,12 +0,0 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:autoMirrored="true"
|
||||
android:height="24dp"
|
||||
android:viewportHeight="960"
|
||||
android:viewportWidth="960"
|
||||
android:width="24dp">
|
||||
|
||||
<path
|
||||
android:fillColor="#e8eaed"
|
||||
android:pathData="m612,668 l56,-56 -148,-148v-184h-80v216l172,172ZM480,880q-83,0 -156,-31.5T197,763q-54,-54 -85.5,-127T80,480q0,-83 31.5,-156T197,197q54,-54 127,-85.5T480,80q83,0 156,31.5T763,197q54,54 85.5,127T880,480q0,83 -31.5,156T763,763q-54,54 -127,85.5T480,880ZM480,480ZM480,800q133,0 226.5,-93.5T800,480q0,-133 -93.5,-226.5T480,160q-133,0 -226.5,93.5T160,480q0,133 93.5,226.5T480,800Z" />
|
||||
|
||||
</vector>
|
||||
BIN
app/src/main/res/drawable/library_screen_filled.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
|
|
@ -1,12 +0,0 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:autoMirrored="true"
|
||||
android:height="24dp"
|
||||
android:viewportHeight="960"
|
||||
android:viewportWidth="960"
|
||||
android:width="24dp">
|
||||
|
||||
<path
|
||||
android:fillColor="#e8eaed"
|
||||
android:pathData="M160,880q-33,0 -56.5,-23.5T80,800v-560h80v560h560v80L160,880ZM320,720q-33,0 -56.5,-23.5T240,640v-480q0,-33 23.5,-56.5T320,80h480q33,0 56.5,23.5T880,160v480q0,33 -23.5,56.5T800,720L320,720ZM520,440 L620,380 720,440v-280L520,160v280Z" />
|
||||
|
||||
</vector>
|
||||
BIN
app/src/main/res/drawable/library_screen_outlined.png
Normal file
|
After Width: | Height: | Size: 1 KiB |
|
|
@ -1,12 +0,0 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:autoMirrored="true"
|
||||
android:height="24dp"
|
||||
android:viewportHeight="960"
|
||||
android:viewportWidth="960"
|
||||
android:width="24dp">
|
||||
|
||||
<path
|
||||
android:fillColor="#e8eaed"
|
||||
android:pathData="M320,640h480v-480h-80v280l-100,-60 -100,60v-280L320,160v480ZM320,720q-33,0 -56.5,-23.5T240,640v-480q0,-33 23.5,-56.5T320,80h480q33,0 56.5,23.5T880,160v480q0,33 -23.5,56.5T800,720L320,720ZM160,880q-33,0 -56.5,-23.5T80,800v-560h80v560h560v80L160,880ZM520,160h200,-200ZM320,160h480,-480Z" />
|
||||
|
||||
</vector>
|
||||
BIN
app/src/main/res/drawable/reddit.png
Normal file
|
After Width: | Height: | Size: 3.4 KiB |
|
|
@ -1,45 +0,0 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:height="24dp"
|
||||
android:viewportHeight="256"
|
||||
android:viewportWidth="256"
|
||||
android:width="24dp">
|
||||
|
||||
<path
|
||||
android:fillColor="#e8eaed"
|
||||
android:fillType="nonZero"
|
||||
android:pathData="M111.07,140.52c0,-6.98 -5.69,-12.67 -12.67,-12.67c-6.98,0 -12.67,5.69 -12.67,12.67c0,6.98 5.66,12.6 12.67,12.67C105.38,153.19 111.07,147.5 111.07,140.52z"
|
||||
android:strokeColor="#00000000"
|
||||
android:strokeLineCap="butt"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="2.81" />
|
||||
|
||||
<path
|
||||
android:fillColor="#e8eaed"
|
||||
android:fillType="nonZero"
|
||||
android:pathData="M154.26,170.73c-7.7,5.5 -17,8.2 -26.43,7.7c-9.43,0.44 -18.7,-2.39 -26.31,-7.95c-1.29,-1.04 -3.08,-1.04 -4.34,0c-1.45,1.19 -1.67,3.36 -0.47,4.81c8.99,6.76 19.99,10.22 31.24,9.75c11.25,0.47 22.26,-2.99 31.24,-9.75l0,0.5c1.38,-1.35 1.41,-3.61 0.06,-5C157.9,169.41 155.64,169.38 154.26,170.73z"
|
||||
android:strokeColor="#00000000"
|
||||
android:strokeLineCap="butt"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="2.81" />
|
||||
|
||||
<path
|
||||
android:fillColor="#e8eaed"
|
||||
android:fillType="nonZero"
|
||||
android:pathData="M156.93,128.29c-6.98,0 -12.67,5.69 -12.67,12.67c0,6.98 5.69,12.67 12.67,12.67l-0.1,0.47c0.22,0 0.41,0 0.63,0c6.98,-0.28 12.41,-6.16 12.13,-13.14C169.6,133.99 163.91,128.29 156.93,128.29z"
|
||||
android:strokeColor="#00000000"
|
||||
android:strokeLineCap="butt"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="2.81" />
|
||||
|
||||
<path
|
||||
android:fillColor="#e8eaed"
|
||||
android:fillType="nonZero"
|
||||
android:pathData="M127.86,1.41C58.02,1.41 1.41,58.02 1.41,127.86c0,69.84 56.61,126.45 126.45,126.45c69.84,0 126.45,-56.61 126.45,-126.45C254.31,58.02 197.69,1.41 127.86,1.41zM201.97,144.67c0.13,1.85 0.13,3.71 0,5.56c0,28.35 -33,51.36 -73.71,51.36s-73.71,-23.04 -73.71,-51.36c-0.13,-1.85 -0.13,-3.71 0,-5.56c-1.85,-0.82 -3.52,-1.98 -4.97,-3.33c-7.45,-6.98 -7.79,-18.67 -0.82,-26.09c6.98,-7.45 18.67,-7.79 26.09,-0.82c14.58,-9.87 31.71,-15.28 49.32,-15.56l9.33,-43.88c0,-0.03 0,-0.03 0,-0.06c0.47,-2.14 2.58,-3.46 4.68,-2.99l30.99,6.19c1.98,-3.46 5.56,-5.79 9.55,-6.19c6.95,-0.76 13.17,4.27 13.92,11.22s-4.27,13.17 -11.22,13.92c-6.95,0.76 -13.17,-4.27 -13.92,-11.22l-27.06,-5.69l-8.2,39.45c17.38,0.38 34.29,5.75 48.69,15.56c3.27,-3.11 7.57,-4.97 12.1,-5.12c10.21,-0.35 18.77,7.61 19.14,17.79C212.31,134.96 208.32,141.5 201.97,144.67z"
|
||||
android:strokeColor="#00000000"
|
||||
android:strokeLineCap="butt"
|
||||
android:strokeLineJoin="miter"
|
||||
android:strokeWidth="2.81"
|
||||
tools:ignore="VectorPath" />
|
||||
|
||||
</vector>
|
||||
BIN
app/src/main/res/drawable/start_done.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
|
|
@ -1,12 +0,0 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:autoMirrored="true"
|
||||
android:height="128dp"
|
||||
android:viewportHeight="960"
|
||||
android:viewportWidth="960"
|
||||
android:width="128dp">
|
||||
|
||||
<path
|
||||
android:fillColor="#e8eaed"
|
||||
android:pathData="M184.69,780v-30.77h81.69v-234L84.62,240h395.07L297.15,515.23v234h82.47L379.62,780L184.69,780ZM194.62,353.69h174.3l54.23,-82.92h-282l53.47,82.92ZM613.55,780q-42.4,0 -70.28,-27.79 -27.89,-27.8 -27.89,-70.2 0,-41.63 27.83,-69.51 27.82,-27.88 70.25,-27.88 16.84,0 33.27,6.07 16.42,6.08 33.27,18.77L680,240h155.38v61.31L710.77,301.31v380.61q0,42.44 -27.79,70.26Q655.18,780 613.55,780Z" />
|
||||
|
||||
</vector>
|
||||
BIN
app/src/main/res/drawable/tryzub.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
|
|
@ -1,13 +0,0 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:height="39.867767dp"
|
||||
android:viewportHeight="402"
|
||||
android:viewportWidth="242"
|
||||
android:width="24dp">
|
||||
|
||||
<path
|
||||
android:fillColor="#e8eaed"
|
||||
android:pathData="m121,1c-9.32,10.58 -15,24.42 -15,39.62 0.55,33.37 4.64,66.7 5,100 0.74,31.1 -8.58,60.3 -19.92,88.8 -3.78,7.87 -7.86,15.57 -11.95,23.29l-12,-2.42c-10.83,-2.16 -17.87,-12.69 -15.7,-23.52 1.89,-9.47 10.23,-16.1 19.53,-16.1l4.38,0.47l-9.77,-81.6c-3.19,-36.3 -21.93,-68.2 -49.5,-88.9 -4.74,-3.56 -9.77,-6.82 -15,-9.69v289.7h66.88c5,27.1 19.94,50.8 40.94,67 4.99,3.44 9.18,7.98 12.19,13.28 3,-5.3 7.19,-9.84 12.19,-13.28 21,-16.24 35.94,-39.89 40.94,-67h66.88v-289.7c-5.23,2.87 -10.26,6.13 -15,9.69 -27.6,20.73 -46.3,52.6 -49.5,88.9l-9.77,81.6l4.38,-0.47c9.3,0.04 17.64,6.62 19.53,16.1 2.16,10.83 -4.88,21.35 -15.7,23.52l-12,2.42c-4.09,-7.71 -8.18,-15.41 -11.95,-23.29 -11.35,-28.48 -20.67,-57.68 -19.93,-88.8 0.36,-33.4 4.45,-66.7 5,-100 0,-15.2 -5.68,-29 -15,-39.62zM21,72.7c12.94,15.19 21.63,34.1 24.2,54.93l8,67.3c-10.25,5.1 -18,14.46 -20.94,25.79h-11.33v-148zM221,72.7v148h-11.33c-2.92,-11.32 -10.69,-20.68 -20.94,-25.79l8,-67.3c2.59,-20.81 11.28,-39.74 24.2,-54.94zM121,208c5.37,17.57 12.76,34.3 21.95,49.78 -8.74,2.65 -16.34,7.81 -21.95,14.76 -5.61,-6.95 -13.21,-12.1 -21.95,-14.77 9.19,-15.51 16.58,-32.2 21.95,-49.77zM21,240.74h11.33c3.57,13.81 14.35,24.71 28,28.52l9.61,2.19c-2.57,9.32 -3.98,19.1 -3.98,29.3h-45v-60zM209.7,240.74h11.33v60h-45c0,-10.14 -1.41,-19.97 -3.98,-29.3l9.61,-2.19c13.7,-3.81 24.47,-14.72 28,-28.52zM89.5,276.04c12.13,1.71 21.48,12.1 21.48,24.69h-25c0,-8.57 1.27,-16.84 3.52,-24.69zM152.47,276.04c2.25,7.85 3.52,16.1 3.52,24.69h-25c0,-12.61 9.35,-22.98 21.48,-24.69zM88.27,320.74h22.73v42.1c-11,-11.55 -19,-26 -22.73,-42.1zM131,320.74h22.73c-3.68,16.1 -11.7,30.57 -22.73,42.1v-42.1z"
|
||||
tools:ignore="VectorPath" />
|
||||
|
||||
</vector>
|
||||
BIN
app/src/main/res/drawable/x_logo.png
Normal file
|
After Width: | Height: | Size: 3 KiB |
|
|
@ -1,11 +0,0 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:height="21.68dp"
|
||||
android:viewportHeight="271"
|
||||
android:viewportWidth="300"
|
||||
android:width="24dp">
|
||||
|
||||
<path
|
||||
android:fillColor="#e8eaed"
|
||||
android:pathData="m236,0h46l-101,115 118,156h-92.6l-72.5,-94.8 -83,94.8h-46l107,-123 -113,-148h94.9l65.5,86.6zM219.9,244h25.5l-165,-218h-27.4z" />
|
||||
|
||||
</vector>
|
||||
|
|
@ -101,7 +101,9 @@
|
|||
<string name="error_query">Помилка: %1$s</string>
|
||||
<string name="error_file_empty">Цей файл пустий або пошкоджений.</string>
|
||||
<string name="error_no_translator">Не знайдено жодного перекладача.</string>
|
||||
<string name="error_no_dictionary">Не знайдено жодного словника.</string>
|
||||
<string name="error_no_browser">Не знайдено жодного браузера.</string>
|
||||
<string name="error_no_share_app">Не знайдено застосунка щоб поділитися.</string>
|
||||
<string name="error_no_text">
|
||||
Вміст книги пустий. Будь ласка, перевірте вміст книги та оновіть її.
|
||||
</string>
|
||||
|
|
@ -160,6 +162,8 @@
|
|||
<string name="download">Завантажити</string>
|
||||
<string name="done">Готово</string>
|
||||
<string name="enable">Увімкнути</string>
|
||||
<string name="web_search">Веб-пошук</string>
|
||||
<string name="share">Поділитися</string>
|
||||
|
||||
<!-- Items -->
|
||||
<string name="files">файли</string>
|
||||
|
|
|
|||
|
|
@ -118,7 +118,9 @@
|
|||
<string name="error_query">Error: %1$s</string>
|
||||
<string name="error_file_empty">This file is empty or corrupted.</string>
|
||||
<string name="error_no_translator">No translator app found.</string>
|
||||
<string name="error_no_dictionary">No dictionary app found.</string>
|
||||
<string name="error_no_browser">No browser app found.</string>
|
||||
<string name="error_no_share_app">No app to share found.</string>
|
||||
<string name="error_no_text">
|
||||
The book content is empty. Please, make sure that the book content is not empty and update it.
|
||||
</string>
|
||||
|
|
@ -176,6 +178,8 @@
|
|||
<string name="download">Download</string>
|
||||
<string name="done">Done</string>
|
||||
<string name="enable">Enable</string>
|
||||
<string name="web_search">Web search</string>
|
||||
<string name="share">Share</string>
|
||||
|
||||
<!-- Items -->
|
||||
<string name="files">files</string>
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 591 KiB |
|
Before Width: | Height: | Size: 587 KiB |
|
Before Width: | Height: | Size: 921 KiB |
|
Before Width: | Height: | Size: 923 KiB |
|
Before Width: | Height: | Size: 288 KiB |
|
Before Width: | Height: | Size: 283 KiB |
|
Before Width: | Height: | Size: 207 KiB |
|
Before Width: | Height: | Size: 170 KiB |
8
fastlane/metadata/android/en-US/changelogs/6.txt
Normal file
|
|
@ -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
|
||||
|
Before Width: | Height: | Size: 921 KiB After Width: | Height: | Size: 983 KiB |
|
Before Width: | Height: | Size: 923 KiB After Width: | Height: | Size: 947 KiB |
|
Before Width: | Height: | Size: 288 KiB After Width: | Height: | Size: 2.2 MiB |
|
Before Width: | Height: | Size: 283 KiB After Width: | Height: | Size: 985 KiB |
|
Before Width: | Height: | Size: 591 KiB After Width: | Height: | Size: 947 KiB |
|
Before Width: | Height: | Size: 587 KiB After Width: | Height: | Size: 2.2 MiB |
BIN
fastlane/metadata/android/en-US/images/phoneScreenshots/7.png
Normal file
|
After Width: | Height: | Size: 361 KiB |
BIN
fastlane/metadata/android/en-US/images/phoneScreenshots/8.png
Normal file
|
After Width: | Height: | Size: 416 KiB |
BIN
fastlane/metadata/android/en-US/images/phoneScreenshots/9.png
Normal file
|
After Width: | Height: | Size: 397 KiB |