From f84c962b3ebeb8b048ae90a470a6e3b82da69e2d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C9=91rry=20Shiv=C9=91m?= Date: Sun, 21 Apr 2024 12:28:53 +0530 Subject: [PATCH] Add ability to import external epub books (#155) Commits: * Add ability to import external epub books * Handle some edge cases in epu parser and add initial import UI * Add complete UI for epub import & some other improvements * Export strings and cleanup * Some more cleanup and improvements * Add Some missing copyright headers --------- Signed-off-by: starry-shivam --- app/build.gradle | 20 +- .../3.json | 109 + .../4.json | 109 + .../com/starry/myne/database/MyneDatabase.kt | 14 +- .../myne/database/library/LibraryDao.kt | 5 +- .../myne/database/library/LibraryItem.kt | 5 +- .../starry/myne/database/reader/ReaderDao.kt | 16 +- .../starry/myne/database/reader/ReaderData.kt | 2 +- .../java/com/starry/myne/epub/EpubParser.kt | 46 +- .../java/com/starry/myne/epub/EpubUtils.kt | 2 +- .../com/starry/myne/epub/EpubXMLFileParser.kt | 2 +- .../com/starry/myne/epub/models/EpubBook.kt | 13 + .../starry/myne/epub/models/EpubChapter.kt | 7 + .../com/starry/myne/epub/models/EpubImage.kt | 6 + .../starry/myne/ui/common/BookDetailTopUI.kt | 16 + .../myne/ui/common/BookLanguageButton.kt | 75 + .../com/starry/myne/ui/common/NetworkError.kt | 6 +- .../starry/myne/ui/common/NoBooksAvailable.kt | 16 + .../starry/myne/ui/common/SlideInContainer.kt | 16 + .../com/starry/myne/ui/navigation/NavGraph.kt | 6 +- .../com/starry/myne/ui/navigation/Screens.kt | 7 +- .../composables/CategoriesScreen.kt | 4 +- .../composables/CategoryDetailScreen.kt | 4 +- .../viewmodels/CategoryViewModel.kt | 3 +- .../detail/composables/BookDetailScreen.kt | 371 +- .../detail/viewmodels/BookDetailViewModel.kt | 23 +- .../ui/screens/home/composables/HomeScreen.kt | 295 +- .../screens/home/viewmodels/HomeViewModel.kt | 3 +- .../library/composables/LibraryScreen.kt | 460 +- .../library/viewmodels/LibraryViewModel.kt | 82 +- .../starry/myne/ui/screens/main/MainScreen.kt | 4 +- .../reader/activities/ReaderActivity.kt | 20 +- .../reader/composables/ReaderContent.kt | 2 +- .../reader/composables/ReaderDetailScreen.kt | 182 +- .../reader/composables/ReaderScreen.kt | 14 +- .../viewmodels/ReaderDetailViewModel.kt | 25 +- .../reader/viewmodels/ReaderViewModel.kt | 45 +- .../settings/composables/AboutScreen.kt | 10 +- .../settings/composables/SettingsItems.kt | 6 +- .../settings/composables/SettingsScreen.kt | 37 +- .../welcome/composables/WelcomeScreen.kt | 2 +- .../java/com/starry/myne/utils/Constants.kt | 4 + .../java/com/starry/myne/utils/Extensions.kt | 25 +- .../java/com/starry/myne/utils/Paginator.kt | 18 + .../main/java/com/starry/myne/utils/Utils.kt | 2 +- .../starry/myne/utils/book/BookDownloader.kt | 46 +- .../res/drawable/ic_library_external_item.xml | 20 + .../res/raw/epub_import_error_lottie.json | 2225 ++++++++ .../res/raw/epub_import_success_lottie.json | 2298 ++++++++ .../main/res/raw/epub_importing_lottie.json | 5002 +++++++++++++++++ app/src/main/res/values-ar/strings.xml | 8 +- app/src/main/res/values-cs/strings.xml | 8 +- app/src/main/res/values-de/strings.xml | 10 +- app/src/main/res/values-es/strings.xml | 8 +- app/src/main/res/values-it/strings.xml | 8 +- app/src/main/res/values-pt-rBR/strings.xml | 8 +- app/src/main/res/values-ro/strings.xml | 8 +- app/src/main/res/values-ru/strings.xml | 10 +- app/src/main/res/values-zh-rCN/strings.xml | 8 +- app/src/main/res/values/strings.xml | 12 +- 60 files changed, 11049 insertions(+), 769 deletions(-) create mode 100644 app/schemas/com.starry.myne.database.MyneDatabase/3.json create mode 100644 app/schemas/com.starry.myne.database.MyneDatabase/4.json create mode 100644 app/src/main/java/com/starry/myne/ui/common/BookLanguageButton.kt create mode 100644 app/src/main/res/drawable/ic_library_external_item.xml create mode 100644 app/src/main/res/raw/epub_import_error_lottie.json create mode 100644 app/src/main/res/raw/epub_import_success_lottie.json create mode 100644 app/src/main/res/raw/epub_importing_lottie.json diff --git a/app/build.gradle b/app/build.gradle index aaff6df1..85cba6e7 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -3,7 +3,7 @@ plugins { id 'org.jetbrains.kotlin.android' id 'dagger.hilt.android.plugin' id 'com.google.devtools.ksp' - id "com.mikepenz.aboutlibraries.plugin" version "10.5.2" + id "com.mikepenz.aboutlibraries.plugin" version "11.1.3" } apply plugin: 'com.mikepenz.aboutlibraries.plugin' @@ -88,14 +88,14 @@ aboutLibraries { dependencies { - def composeBom = platform('androidx.compose:compose-bom:2024.04.00') + def composeBom = platform('androidx.compose:compose-bom:2024.04.01') implementation composeBom androidTestImplementation composeBom // Android core components. - implementation 'androidx.core:core-ktx:1.12.0' + implementation 'androidx.core:core-ktx:1.13.0' implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.7.0' - implementation 'androidx.activity:activity-compose:1.8.2' + implementation 'androidx.activity:activity-compose:1.9.0' implementation "androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0" implementation "androidx.navigation:navigation-compose:2.7.7" // Jetpack compose. @@ -108,7 +108,7 @@ dependencies { // Accompanist compose. implementation "com.google.accompanist:accompanist-systemuicontroller:0.28.0" // Material icons. - implementation 'androidx.compose.material:material-icons-extended:1.6.4' + implementation 'androidx.compose.material:material-icons-extended:1.6.6' // Material theme for main activity. implementation 'com.google.android.material:material:1.11.0' // Android 12+ splash API. @@ -131,12 +131,12 @@ dependencies { // Jsoup HTML Parser. implementation "org.jsoup:jsoup:1.17.2" // Lottie animations. - implementation "com.airbnb.android:lottie-compose:4.1.0" - // DataStore Preferences - implementation("androidx.datastore:datastore-preferences:1.0.0") + implementation "com.airbnb.android:lottie-compose:6.4.0" + // DataStore Preferences. + implementation("androidx.datastore:datastore-preferences:1.1.0") // Open Source Libraries Screen. - implementation "com.mikepenz:aboutlibraries-core:10.5.2" - implementation "com.mikepenz:aboutlibraries-compose:10.5.2" + implementation "com.mikepenz:aboutlibraries-core:11.1.3" + implementation "com.mikepenz:aboutlibraries-compose:11.1.3" // Swipe actions. implementation "me.saket.swipe:swipe:1.2.0" // Crash Handler. diff --git a/app/schemas/com.starry.myne.database.MyneDatabase/3.json b/app/schemas/com.starry.myne.database.MyneDatabase/3.json new file mode 100644 index 00000000..4f7b26c3 --- /dev/null +++ b/app/schemas/com.starry.myne.database.MyneDatabase/3.json @@ -0,0 +1,109 @@ +{ + "formatVersion": 1, + "database": { + "version": 3, + "identityHash": "c623113d83374eb42b85d35d0ce0118e", + "entities": [ + { + "tableName": "book_library", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`book_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `authors` TEXT NOT NULL, `file_path` TEXT NOT NULL, `created_at` INTEGER NOT NULL, `is_external_book` INTEGER NOT NULL DEFAULT false, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "bookId", + "columnName": "book_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "authors", + "columnName": "authors", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "filePath", + "columnName": "file_path", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "created_at", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isExternalBook", + "columnName": "is_external_book", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "false" + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "reader_table", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`book_id` INTEGER NOT NULL, `last_chapter_index` INTEGER NOT NULL, `last_chapter_offset` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "bookId", + "columnName": "book_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastChapterIndex", + "columnName": "last_chapter_index", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastChapterOffset", + "columnName": "last_chapter_offset", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'c623113d83374eb42b85d35d0ce0118e')" + ] + } +} \ No newline at end of file diff --git a/app/schemas/com.starry.myne.database.MyneDatabase/4.json b/app/schemas/com.starry.myne.database.MyneDatabase/4.json new file mode 100644 index 00000000..2f9538af --- /dev/null +++ b/app/schemas/com.starry.myne.database.MyneDatabase/4.json @@ -0,0 +1,109 @@ +{ + "formatVersion": 1, + "database": { + "version": 4, + "identityHash": "73dddecebf5b45bc31c3d462376c1251", + "entities": [ + { + "tableName": "book_library", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`book_id` INTEGER NOT NULL, `title` TEXT NOT NULL, `authors` TEXT NOT NULL, `file_path` TEXT NOT NULL, `created_at` INTEGER NOT NULL, `is_external_book` INTEGER NOT NULL DEFAULT false, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "bookId", + "columnName": "book_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "authors", + "columnName": "authors", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "filePath", + "columnName": "file_path", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "createdAt", + "columnName": "created_at", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "isExternalBook", + "columnName": "is_external_book", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "false" + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + }, + { + "tableName": "reader_table", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`library_item_id` INTEGER NOT NULL, `last_chapter_index` INTEGER NOT NULL, `last_chapter_offset` INTEGER NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL)", + "fields": [ + { + "fieldPath": "libraryItemId", + "columnName": "library_item_id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastChapterIndex", + "columnName": "last_chapter_index", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "lastChapterOffset", + "columnName": "last_chapter_offset", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + }, + "indices": [], + "foreignKeys": [] + } + ], + "views": [], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '73dddecebf5b45bc31c3d462376c1251')" + ] + } +} \ No newline at end of file diff --git a/app/src/main/java/com/starry/myne/database/MyneDatabase.kt b/app/src/main/java/com/starry/myne/database/MyneDatabase.kt index 25b0388c..3a05ddcd 100644 --- a/app/src/main/java/com/starry/myne/database/MyneDatabase.kt +++ b/app/src/main/java/com/starry/myne/database/MyneDatabase.kt @@ -21,6 +21,7 @@ import androidx.room.AutoMigration import androidx.room.Database import androidx.room.Room import androidx.room.RoomDatabase +import androidx.room.migration.Migration import com.starry.myne.database.library.LibraryDao import com.starry.myne.database.library.LibraryItem import com.starry.myne.database.reader.ReaderDao @@ -29,9 +30,12 @@ import com.starry.myne.utils.Constants @Database( entities = [LibraryItem::class, ReaderData::class], - version = 2, + version = 4, exportSchema = true, - autoMigrations = [AutoMigration(from = 1, to = 2)] + autoMigrations = [ + AutoMigration(from = 1, to = 2), + AutoMigration(from = 2, to = 3), + ] ) abstract class MyneDatabase : RoomDatabase() { @@ -40,6 +44,10 @@ abstract class MyneDatabase : RoomDatabase() { companion object { + private val migration3to4 = Migration(3, 4) { database -> + database.execSQL("ALTER TABLE reader_table RENAME COLUMN book_id TO library_item_id") + } + @Volatile private var INSTANCE: MyneDatabase? = null @@ -54,7 +62,7 @@ abstract class MyneDatabase : RoomDatabase() { context.applicationContext, MyneDatabase::class.java, Constants.DATABASE_NAME - ).build() + ).addMigrations(migration3to4).build() INSTANCE = instance // return instance instance diff --git a/app/src/main/java/com/starry/myne/database/library/LibraryDao.kt b/app/src/main/java/com/starry/myne/database/library/LibraryDao.kt index 63663ef7..8927f789 100644 --- a/app/src/main/java/com/starry/myne/database/library/LibraryDao.kt +++ b/app/src/main/java/com/starry/myne/database/library/LibraryDao.kt @@ -35,6 +35,9 @@ interface LibraryDao { @Query("SELECT * FROM book_library ORDER BY id ASC") fun getAllItems(): LiveData> + @Query("SELECT * FROM book_library WHERE id = :id") + fun getItemById(id: Int): LibraryItem? + @Query("SELECT * FROM book_library WHERE book_id = :bookId") - fun getItemById(bookId: Int): LibraryItem? + fun getItemByBookId(bookId: Int): LibraryItem? } \ No newline at end of file diff --git a/app/src/main/java/com/starry/myne/database/library/LibraryItem.kt b/app/src/main/java/com/starry/myne/database/library/LibraryItem.kt index cf120a2f..95e55ea9 100644 --- a/app/src/main/java/com/starry/myne/database/library/LibraryItem.kt +++ b/app/src/main/java/com/starry/myne/database/library/LibraryItem.kt @@ -38,7 +38,10 @@ data class LibraryItem( @ColumnInfo(name = "file_path") val filePath: String, @ColumnInfo(name = "created_at") - val createdAt: Long + val createdAt: Long, + // Added in database schema version 3 + @ColumnInfo(name = "is_external_book", defaultValue = "false") + val isExternalBook: Boolean = false ) { @PrimaryKey(autoGenerate = true) var id: Int = 0 diff --git a/app/src/main/java/com/starry/myne/database/reader/ReaderDao.kt b/app/src/main/java/com/starry/myne/database/reader/ReaderDao.kt index 14058658..9744bf06 100644 --- a/app/src/main/java/com/starry/myne/database/reader/ReaderDao.kt +++ b/app/src/main/java/com/starry/myne/database/reader/ReaderDao.kt @@ -30,20 +30,20 @@ interface ReaderDao { @Insert(onConflict = OnConflictStrategy.REPLACE) fun insert(readerData: ReaderData) - @Query("DELETE FROM reader_table WHERE book_id = :bookId") - fun delete(bookId: Int) + @Query("DELETE FROM reader_table WHERE library_item_id = :libraryItemId") + fun delete(libraryItemId: Int) @Query( "UPDATE reader_table SET " + "last_chapter_index = :lastChapterIndex," + "last_chapter_offset = :lastChapterOffset" - + " WHERE book_id = :bookId" + + " WHERE library_item_id = :libraryItemId" ) - fun update(bookId: Int, lastChapterIndex: Int, lastChapterOffset: Int) + fun update(libraryItemId: Int, lastChapterIndex: Int, lastChapterOffset: Int) - @Query("SELECT * FROM reader_table WHERE book_id = :bookId") - fun getReaderData(bookId: Int): ReaderData? + @Query("SELECT * FROM reader_table WHERE library_item_id = :libraryItemId") + fun getReaderData(libraryItemId: Int): ReaderData? - @Query("SELECT * FROM reader_table WHERE book_id = :bookId") - fun getReaderDataAsFlow(bookId: Int): Flow + @Query("SELECT * FROM reader_table WHERE library_item_id = :libraryItemId") + fun getReaderDataAsFlow(libraryItemId: Int): Flow } \ No newline at end of file diff --git a/app/src/main/java/com/starry/myne/database/reader/ReaderData.kt b/app/src/main/java/com/starry/myne/database/reader/ReaderData.kt index 5c55b064..83db53db 100644 --- a/app/src/main/java/com/starry/myne/database/reader/ReaderData.kt +++ b/app/src/main/java/com/starry/myne/database/reader/ReaderData.kt @@ -23,7 +23,7 @@ import androidx.room.PrimaryKey @Entity(tableName = "reader_table") data class ReaderData( - @ColumnInfo(name = "book_id") val bookId: Int, + @ColumnInfo(name = "library_item_id") val libraryItemId: Int, @ColumnInfo(name = "last_chapter_index") val lastChapterIndex: Int, @ColumnInfo(name = "last_chapter_offset") val lastChapterOffset: Int ) { diff --git a/app/src/main/java/com/starry/myne/epub/EpubParser.kt b/app/src/main/java/com/starry/myne/epub/EpubParser.kt index ba7ce227..f06b7534 100644 --- a/app/src/main/java/com/starry/myne/epub/EpubParser.kt +++ b/app/src/main/java/com/starry/myne/epub/EpubParser.kt @@ -89,6 +89,9 @@ class EpubParser { /** * Creates an [EpubBook] object from an EPUB file. + * + * Note: The caller is responsible for closing the input stream. + * * @param inputStream The input stream of the EPUB file. * @param shouldUseToc Whether to use the table of contents to parse chapters. * @return The [EpubBook] object. @@ -99,15 +102,14 @@ class EpubParser { /** * Creates an [EpubBook] object from an EPUB file. + * * @param filePath The file path of the EPUB file. * @param shouldUseToc Whether to use the table of contents to parse chapters. * @return The [EpubBook] object. */ suspend fun createEpubBook(filePath: String, shouldUseToc: Boolean = true): EpubBook { - val inputStream = withContext(Dispatchers.IO) { - FileInputStream(filePath) - } - return parseAndCreateEbook(inputStream, shouldUseToc) + val inputStream = withContext(Dispatchers.IO) { FileInputStream(filePath) } + inputStream.use { return parseAndCreateEbook(it, shouldUseToc) } } private suspend fun parseAndCreateEbook( @@ -138,6 +140,10 @@ class EpubParser { val metadataTitle = metadata.selectFirstChildTag("dc:title")?.textContent ?: "Unknown Title" + val metadataAuthor = metadata.selectFirstChildTag("dc:creator")?.textContent + ?: "Unknown Author" + val metadataLanguage = metadata.selectFirstChildTag("dc:language")?.textContent + ?: "en" val metadataCoverId = metadata .selectChildTag("meta") @@ -155,17 +161,10 @@ class EpubParser { ) }.associateBy { it.id } - /** - * Find the table of contents (toc.ncx) file. - * or the first file with the "navMap" property. - */ - val tocFileItem = - manifestItems.values.firstOrNull { - it.absPath.endsWith( - "toc.ncx", - ignoreCase = true - ) - } ?: manifestItems.values.firstOrNull { it.properties.contains("navMap") } + // Find the table of contents (toc.ncx) file. + val tocFileItem = manifestItems.values.firstOrNull { + it.absPath.endsWith(".ncx", ignoreCase = true) + } /** * Parse chapters based on the table of contents (toc.ncx) file. @@ -188,6 +187,8 @@ class EpubParser { return@withContext EpubBook( fileName = metadataTitle.asFileName(), title = metadataTitle, + author = metadataAuthor, + language = metadataLanguage, coverImage = coverImage, chapters = chapters, images = images @@ -198,7 +199,7 @@ class EpubParser { private suspend fun getZipFiles( inputStream: InputStream ): Map = withContext(Dispatchers.IO) { - ZipInputStream(inputStream).use { zipInputStream -> + ZipInputStream(inputStream).let { zipInputStream -> zipInputStream .entries() .filterNot { it.isDirectory } @@ -241,12 +242,19 @@ class EpubParser { ?.hrefAbsolutePath(hrefRootPath) if (chapterSrc != null) { - val (fragmentPath, fragmentId) = chapterSrc.split("#", limit = 2) + // Check if the chapter source contains a fragment ID + val (fragmentPath, fragmentId) = if ('#' in chapterSrc) { + val (path, id) = chapterSrc.split("#", limit = 2) + path to id + } else { + chapterSrc to null + } + val nextNavPoint = navPoint.nextSibling val nextFragmentId = nextNavPoint?.nextSibling?.selectFirstChildTag("content") ?.getAttributeValue("src") - ?.split("#") - ?.lastOrNull() + ?.let { src -> src.takeIf { '#' in it }?.substringAfterLast('#') } + val chapterFile = files[fragmentPath] val parser = chapterFile?.let { diff --git a/app/src/main/java/com/starry/myne/epub/EpubUtils.kt b/app/src/main/java/com/starry/myne/epub/EpubUtils.kt index e115e114..6718d648 100644 --- a/app/src/main/java/com/starry/myne/epub/EpubUtils.kt +++ b/app/src/main/java/com/starry/myne/epub/EpubUtils.kt @@ -20,7 +20,6 @@ package com.starry.myne.epub import org.w3c.dom.Document import org.w3c.dom.Element import org.w3c.dom.Node -import org.jsoup.nodes.Node as JsoupNode import org.w3c.dom.NodeList import org.xml.sax.InputSource import java.io.File @@ -29,6 +28,7 @@ import java.net.URLDecoder import java.util.zip.ZipInputStream import javax.xml.parsers.DocumentBuilderFactory import kotlin.io.path.invariantSeparatorsPathString +import org.jsoup.nodes.Node as JsoupNode fun parseXMLText(text: String): Document? = text.reader().runCatching { DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(InputSource(this)) diff --git a/app/src/main/java/com/starry/myne/epub/EpubXMLFileParser.kt b/app/src/main/java/com/starry/myne/epub/EpubXMLFileParser.kt index 3d3180b1..7304222e 100644 --- a/app/src/main/java/com/starry/myne/epub/EpubXMLFileParser.kt +++ b/app/src/main/java/com/starry/myne/epub/EpubXMLFileParser.kt @@ -20,9 +20,9 @@ package com.starry.myne.epub import android.graphics.BitmapFactory import android.util.Log import org.jsoup.Jsoup +import org.jsoup.nodes.Node import org.jsoup.nodes.TextNode import java.io.File -import org.jsoup.nodes.Node import kotlin.io.path.invariantSeparatorsPathString /** diff --git a/app/src/main/java/com/starry/myne/epub/models/EpubBook.kt b/app/src/main/java/com/starry/myne/epub/models/EpubBook.kt index c97d4ba8..786849eb 100644 --- a/app/src/main/java/com/starry/myne/epub/models/EpubBook.kt +++ b/app/src/main/java/com/starry/myne/epub/models/EpubBook.kt @@ -19,9 +19,22 @@ package com.starry.myne.epub.models import android.graphics.Bitmap +/** + * Represents an epub book. + * + * @param fileName The name of the epub file. + * @param title The title of the book. + * @param author The author of the book. + * @param language The language code of the book. + * @param coverImage The cover image of the book. + * @param chapters The list of chapters in the book. + * @param images The list of images in the book. + */ data class EpubBook( val fileName: String, val title: String, + val author: String, + val language: String, val coverImage: Bitmap?, val chapters: List, val images: List diff --git a/app/src/main/java/com/starry/myne/epub/models/EpubChapter.kt b/app/src/main/java/com/starry/myne/epub/models/EpubChapter.kt index c2d3bd24..b23fbfb9 100644 --- a/app/src/main/java/com/starry/myne/epub/models/EpubChapter.kt +++ b/app/src/main/java/com/starry/myne/epub/models/EpubChapter.kt @@ -17,6 +17,13 @@ package com.starry.myne.epub.models +/** + * Represents a chapter in an epub book. + * + * @param absPath The absolute path of the chapter. + * @param title The title of the chapter. + * @param body The body of the chapter. + */ data class EpubChapter( val absPath: String, val title: String, diff --git a/app/src/main/java/com/starry/myne/epub/models/EpubImage.kt b/app/src/main/java/com/starry/myne/epub/models/EpubImage.kt index 37397a9c..225bf55a 100644 --- a/app/src/main/java/com/starry/myne/epub/models/EpubImage.kt +++ b/app/src/main/java/com/starry/myne/epub/models/EpubImage.kt @@ -17,6 +17,12 @@ package com.starry.myne.epub.models +/** + * Represents an image in an epub book. + * + * @param absPath The absolute path of the image. + * @param image The image data. + */ data class EpubImage(val absPath: String, val image: ByteArray) { override fun equals(other: Any?): Boolean { if (this === other) return true diff --git a/app/src/main/java/com/starry/myne/ui/common/BookDetailTopUI.kt b/app/src/main/java/com/starry/myne/ui/common/BookDetailTopUI.kt index 5724fd78..0dadd17a 100644 --- a/app/src/main/java/com/starry/myne/ui/common/BookDetailTopUI.kt +++ b/app/src/main/java/com/starry/myne/ui/common/BookDetailTopUI.kt @@ -1,3 +1,19 @@ +/** + * Copyright (c) [2022 - Present] Stɑrry Shivɑm + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.starry.myne.ui.common import androidx.compose.foundation.background diff --git a/app/src/main/java/com/starry/myne/ui/common/BookLanguageButton.kt b/app/src/main/java/com/starry/myne/ui/common/BookLanguageButton.kt new file mode 100644 index 00000000..0ba2f889 --- /dev/null +++ b/app/src/main/java/com/starry/myne/ui/common/BookLanguageButton.kt @@ -0,0 +1,75 @@ +/** + * Copyright (c) [2022 - Present] Stɑrry Shivɑm + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.starry.myne.ui.common + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.Card +import androidx.compose.material3.CardDefaults +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.starry.myne.ui.theme.figeronaFont +import com.starry.myne.utils.book.BookLanguage + +@Composable +fun BookLanguageButton(language: BookLanguage, isSelected: Boolean, onClick: () -> Unit) { + val buttonColor: Color + val textColor: Color + if (isSelected) { + buttonColor = MaterialTheme.colorScheme.primary + textColor = MaterialTheme.colorScheme.onPrimary + } else { + buttonColor = MaterialTheme.colorScheme.secondaryContainer + textColor = MaterialTheme.colorScheme.onSecondaryContainer + } + + Card( + modifier = Modifier + .height(60.dp) + .width(70.dp) + .padding(6.dp), + colors = CardDefaults.cardColors(containerColor = buttonColor), + shape = RoundedCornerShape(14.dp), + onClick = onClick + ) { + Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { + Text( + modifier = Modifier.padding(2.dp), + text = language.name, + fontSize = 18.sp, + fontStyle = MaterialTheme.typography.headlineMedium.fontStyle, + fontFamily = figeronaFont, + fontWeight = FontWeight.Bold, + maxLines = 1, + overflow = TextOverflow.Ellipsis, + color = textColor, + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/starry/myne/ui/common/NetworkError.kt b/app/src/main/java/com/starry/myne/ui/common/NetworkError.kt index e85f288d..6377e0c9 100644 --- a/app/src/main/java/com/starry/myne/ui/common/NetworkError.kt +++ b/app/src/main/java/com/starry/myne/ui/common/NetworkError.kt @@ -51,7 +51,7 @@ import com.starry.myne.ui.theme.figeronaFont @Composable -fun NetworkError(onRetryClicked: () -> Unit) { +fun NetworkError(errorMessage: String? = null, onRetryClicked: () -> Unit) { Column( modifier = Modifier .fillMaxSize() @@ -75,7 +75,7 @@ fun NetworkError(onRetryClicked: () -> Unit) { LottieAnimation( composition = compositionResult.value, - progress = progressAnimation, + progress = { progressAnimation }, modifier = Modifier.size(280.dp), enableMergePaths = true ) @@ -83,7 +83,7 @@ fun NetworkError(onRetryClicked: () -> Unit) { Spacer(modifier = Modifier.height(8.dp)) Text( - text = stringResource(id = R.string.network_error), + text = errorMessage ?: stringResource(id = R.string.network_error), modifier = Modifier .padding(top = 10.dp, bottom = 18.dp) .fillMaxWidth(), diff --git a/app/src/main/java/com/starry/myne/ui/common/NoBooksAvailable.kt b/app/src/main/java/com/starry/myne/ui/common/NoBooksAvailable.kt index 0ced631c..680fdef2 100644 --- a/app/src/main/java/com/starry/myne/ui/common/NoBooksAvailable.kt +++ b/app/src/main/java/com/starry/myne/ui/common/NoBooksAvailable.kt @@ -1,3 +1,19 @@ +/** + * Copyright (c) [2022 - Present] Stɑrry Shivɑm + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.starry.myne.ui.common import androidx.compose.foundation.background diff --git a/app/src/main/java/com/starry/myne/ui/common/SlideInContainer.kt b/app/src/main/java/com/starry/myne/ui/common/SlideInContainer.kt index d8ee6bb2..7f6f0a25 100644 --- a/app/src/main/java/com/starry/myne/ui/common/SlideInContainer.kt +++ b/app/src/main/java/com/starry/myne/ui/common/SlideInContainer.kt @@ -1,3 +1,19 @@ +/** + * Copyright (c) [2022 - Present] Stɑrry Shivɑm + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package com.starry.myne.ui.common import androidx.compose.animation.AnimatedVisibility diff --git a/app/src/main/java/com/starry/myne/ui/navigation/NavGraph.kt b/app/src/main/java/com/starry/myne/ui/navigation/NavGraph.kt index 79af03cd..a8d3adca 100644 --- a/app/src/main/java/com/starry/myne/ui/navigation/NavGraph.kt +++ b/app/src/main/java/com/starry/myne/ui/navigation/NavGraph.kt @@ -180,7 +180,7 @@ fun NavGraph( composable( route = Screens.ReaderDetailScreen.route, arguments = listOf(navArgument( - BOOK_ID_ARG_KEY + LIBRARY_ITEM_ID_ARG_KEY ) { type = NavType.StringType }), @@ -189,9 +189,9 @@ fun NavGraph( popEnterTransition = { popEnterTransition() }, popExitTransition = { popExitTransition() }, ) { backStackEntry -> - val bookId = backStackEntry.arguments!!.getString(BOOK_ID_ARG_KEY)!! + val bookId = backStackEntry.arguments!!.getString(LIBRARY_ITEM_ID_ARG_KEY)!! ReaderDetailScreen( - bookId = bookId, navController = navController, networkStatus = networkStatus + libraryItemId = bookId, navController = navController, networkStatus = networkStatus ) } diff --git a/app/src/main/java/com/starry/myne/ui/navigation/Screens.kt b/app/src/main/java/com/starry/myne/ui/navigation/Screens.kt index 5d22e00b..d8571ab9 100644 --- a/app/src/main/java/com/starry/myne/ui/navigation/Screens.kt +++ b/app/src/main/java/com/starry/myne/ui/navigation/Screens.kt @@ -17,6 +17,7 @@ package com.starry.myne.ui.navigation const val BOOK_ID_ARG_KEY = "bookId" +const val LIBRARY_ITEM_ID_ARG_KEY = "libraryItemId" const val CATEGORY_DETAIL_ARG_KEY = "category" sealed class Screens(val route: String) { @@ -34,9 +35,9 @@ sealed class Screens(val route: String) { } } - data object ReaderDetailScreen : Screens("reader_detail_screen/{$BOOK_ID_ARG_KEY}") { - fun withBookId(id: String): String { - return this.route.replace("{$BOOK_ID_ARG_KEY}", id) + data object ReaderDetailScreen : Screens("reader_detail_screen/{$LIBRARY_ITEM_ID_ARG_KEY}") { + fun withLibraryItemId(id: String): String { + return this.route.replace("{$LIBRARY_ITEM_ID_ARG_KEY}", id) } } diff --git a/app/src/main/java/com/starry/myne/ui/screens/categories/composables/CategoriesScreen.kt b/app/src/main/java/com/starry/myne/ui/screens/categories/composables/CategoriesScreen.kt index 0f6a2654..b08c6529 100644 --- a/app/src/main/java/com/starry/myne/ui/screens/categories/composables/CategoriesScreen.kt +++ b/app/src/main/java/com/starry/myne/ui/screens/categories/composables/CategoriesScreen.kt @@ -53,7 +53,6 @@ import com.starry.myne.ui.theme.figeronaFont import java.util.Locale -@OptIn(ExperimentalMaterial3Api::class) @Composable fun CategoriesScreen(navController: NavController) { Scaffold( @@ -103,9 +102,8 @@ fun CategoriesScreen(navController: NavController) { } -@ExperimentalMaterial3Api @Composable -fun CategoriesItem(category: String, onClick: () -> Unit) { +private fun CategoriesItem(category: String, onClick: () -> Unit) { Card( modifier = Modifier .height(90.dp) diff --git a/app/src/main/java/com/starry/myne/ui/screens/categories/composables/CategoryDetailScreen.kt b/app/src/main/java/com/starry/myne/ui/screens/categories/composables/CategoryDetailScreen.kt index 69f3f1c9..b07efd35 100644 --- a/app/src/main/java/com/starry/myne/ui/screens/categories/composables/CategoryDetailScreen.kt +++ b/app/src/main/java/com/starry/myne/ui/screens/categories/composables/CategoryDetailScreen.kt @@ -56,13 +56,13 @@ import androidx.navigation.NavController import coil.annotation.ExperimentalCoilApi import com.starry.myne.R import com.starry.myne.ui.common.BookItemCard +import com.starry.myne.ui.common.BookLanguageButton import com.starry.myne.ui.common.CustomTopAppBar import com.starry.myne.ui.common.NetworkError import com.starry.myne.ui.common.NoBooksAvailable import com.starry.myne.ui.common.ProgressDots import com.starry.myne.ui.navigation.Screens import com.starry.myne.ui.screens.categories.viewmodels.CategoryViewModel -import com.starry.myne.ui.screens.home.composables.LanguageItem import com.starry.myne.ui.theme.pacificoFont import com.starry.myne.utils.NetworkObserver import com.starry.myne.utils.book.BookLanguage @@ -118,7 +118,7 @@ fun CategoryDetailScreen( ) { items(languages.size) { idx -> val language = languages[idx] - LanguageItem(language = language, + BookLanguageButton(language = language, isSelected = language == viewModel.language.value, onClick = { haptic.performHapticFeedback(HapticFeedbackType.LongPress) diff --git a/app/src/main/java/com/starry/myne/ui/screens/categories/viewmodels/CategoryViewModel.kt b/app/src/main/java/com/starry/myne/ui/screens/categories/viewmodels/CategoryViewModel.kt index ae6abef0..8fa57420 100644 --- a/app/src/main/java/com/starry/myne/ui/screens/categories/viewmodels/CategoryViewModel.kt +++ b/app/src/main/java/com/starry/myne/ui/screens/categories/viewmodels/CategoryViewModel.kt @@ -27,6 +27,7 @@ import androidx.lifecycle.viewModelScope import com.starry.myne.repo.BookRepository import com.starry.myne.repo.models.Book import com.starry.myne.repo.models.BookSet +import com.starry.myne.utils.Constants import com.starry.myne.utils.Paginator import com.starry.myne.utils.PreferenceUtil import com.starry.myne.utils.book.BookLanguage @@ -97,7 +98,7 @@ class CategoryViewModel @Inject constructor( state.page + 1L }, onError = { - state = state.copy(error = it?.localizedMessage ?: "unknown-error") + state = state.copy(error = it?.localizedMessage ?: Constants.UNKNOWN_ERR) }, onSuccess = { bookSet, newPage -> diff --git a/app/src/main/java/com/starry/myne/ui/screens/detail/composables/BookDetailScreen.kt b/app/src/main/java/com/starry/myne/ui/screens/detail/composables/BookDetailScreen.kt index 6b90c6f2..775bbd1c 100644 --- a/app/src/main/java/com/starry/myne/ui/screens/detail/composables/BookDetailScreen.kt +++ b/app/src/main/java/com/starry/myne/ui/screens/detail/composables/BookDetailScreen.kt @@ -19,6 +19,7 @@ package com.starry.myne.ui.screens.detail.composables import android.app.DownloadManager import android.content.Intent import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.Crossfade import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.background import androidx.compose.foundation.clickable @@ -36,13 +37,11 @@ import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll -import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.outlined.ArrowBack import androidx.compose.material.icons.outlined.Share import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults -import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.LinearProgressIndicator import androidx.compose.material3.MaterialTheme @@ -62,7 +61,6 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment -import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.vector.ImageVector @@ -77,7 +75,6 @@ import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.viewModelScope import androidx.navigation.NavController import androidx.navigation.compose.rememberNavController -import coil.annotation.ExperimentalCoilApi import com.airbnb.lottie.compose.LottieAnimation import com.airbnb.lottie.compose.LottieCompositionResult import com.airbnb.lottie.compose.LottieCompositionSpec @@ -100,22 +97,14 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -@OptIn( - ExperimentalMaterialApi::class, ExperimentalComposeUiApi::class, - ExperimentalMaterial3Api::class, ExperimentalCoilApi::class -) @Composable fun BookDetailScreen( bookId: String, navController: NavController ) { + val context = LocalContext.current val viewModel: BookDetailViewModel = hiltViewModel() val state = viewModel.state - val context = LocalContext.current - val settingsVM = (context.getActivity() as MainActivity).settingsViewModel - - val coroutineScope = rememberCoroutineScope() - val snackBarHostState = remember { SnackbarHostState() } Scaffold( snackbarHost = { SnackbarHost(snackBarHostState) }, @@ -144,182 +133,207 @@ fun BookDetailScreen( context.startActivity(chooser) }) - if (state.isLoading) { - Box( - modifier = Modifier - .fillMaxSize() - .padding(bottom = 65.dp), - contentAlignment = Alignment.Center - ) { - ProgressDots() + Crossfade( + targetState = state.isLoading, + label = "BookDetailLoadingCrossFade" + ) { isLoading -> + if (isLoading) { + Box( + modifier = Modifier + .fillMaxSize() + .padding(bottom = 65.dp), + contentAlignment = Alignment.Center + ) { + ProgressDots() + } + } else { + if (state.error != null) { + NetworkError(onRetryClicked = { + viewModel.getBookDetails(bookId) + }) + } else { + BookDetailContents( + viewModel = viewModel, + navController = navController, + snackBarHostState = snackBarHostState + ) + } } - } else if (state.error != null) { - NetworkError(onRetryClicked = { - viewModel.getBookDetails(bookId) - }) - } else { - // Get book details for this bookId. - val book = remember { state.bookSet.books.first() } - - Column( - Modifier - .fillMaxSize() - .background(MaterialTheme.colorScheme.background) - .verticalScroll(rememberScrollState()) - ) { - val authors = remember { BookUtils.getAuthorsAsString(book.authors) } - - BookDetailTopUI( - title = book.title, - authors = authors, - imageData = state.extraInfo.coverImage.ifEmpty { book.formats.imagejpeg }, - currentThemeMode = settingsVM.getCurrentTheme() - ) - val pageCount = remember { - if (state.extraInfo.pageCount > 0) { - state.extraInfo.pageCount.toString() - } else { - context.getString(R.string.not_applicable) - } - } + } + } + }) +} - // Check if this book is in downloadQueue. - val buttonTextValue = - if (viewModel.bookDownloader.isBookCurrentlyDownloading(book.id)) { - stringResource(id = R.string.cancel) - } else { - if (state.bookLibraryItem != null) stringResource(id = R.string.read_book_button) else stringResource( - id = R.string.download_book_button - ) - } +@Composable +private fun BookDetailContents( + viewModel: BookDetailViewModel, + navController: NavController, + snackBarHostState: SnackbarHostState +) { + val context = LocalContext.current + val settingsVM = (context.getActivity() as MainActivity).settingsViewModel - var buttonText by remember { mutableStateOf(buttonTextValue) } - var progressState by remember { mutableFloatStateOf(0f) } - var showProgressBar by remember { mutableStateOf(false) } - - // Callable which updates book details screen button. - val updateBtnText: (Int?) -> Unit = { downloadStatus -> - buttonText = when (downloadStatus) { - DownloadManager.STATUS_RUNNING -> { - showProgressBar = true - context.getString(R.string.cancel) - } - - DownloadManager.STATUS_SUCCESSFUL -> { - showProgressBar = false - context.getString(R.string.read_book_button) - } - - else -> { - showProgressBar = false - context.getString(R.string.download_book_button) - } - } - } + val state = viewModel.state + val coroutineScope = rememberCoroutineScope() + // Get book details for this bookId. + val book = remember { state.bookSet.books.first() } - // Check if this book is in downloadQueue. - if (viewModel.bookDownloader.isBookCurrentlyDownloading(book.id)) { - progressState = - viewModel.bookDownloader.getRunningDownload(book.id)?.progress?.collectAsState()?.value!! - LaunchedEffect(key1 = progressState, block = { - updateBtnText(viewModel.bookDownloader.getRunningDownload(book.id)?.status) - }) - } + Column( + Modifier + .fillMaxSize() + .background(MaterialTheme.colorScheme.background) + .verticalScroll(rememberScrollState()) + ) { + val authors = remember { BookUtils.getAuthorsAsString(book.authors) } - MiddleBar( - bookLang = BookUtils.getLanguagesAsString(book.languages), - pageCount = pageCount, - downloadCount = Utils.prettyCount(book.downloadCount), - progressValue = progressState, - buttonText = buttonText, - showProgressBar = showProgressBar - ) { - when (buttonText) { - context.getString(R.string.read_book_button) -> { - val bookLibraryItem = state.bookLibraryItem - /** - * Library item could be null if we reload the screen - * while some download was running, in that case we'll - * de-attach from our old state where download function - * will update library item and our new state will have - * no library item, i.e. null. - */ - if (bookLibraryItem == null) { - viewModel.viewModelScope.launch(Dispatchers.IO) { - val libraryItem = - viewModel.libraryDao.getItemById(book.id)!! - withContext(Dispatchers.Main) { - Utils.openBookFile( - context = context, - internalReader = viewModel.getInternalReaderSetting(), - libraryItem = libraryItem, - navController = navController - ) - } - } - } else { - Utils.openBookFile( - context = context, - internalReader = viewModel.getInternalReaderSetting(), - libraryItem = bookLibraryItem, - navController = navController - ) - } - } - - context.getString(R.string.download_book_button) -> { - viewModel.downloadBook( - book = book, - downloadProgressListener = { downloadProgress, downloadStatus -> - progressState = downloadProgress - updateBtnText(downloadStatus) - }) - coroutineScope.launch { - snackBarHostState.showSnackbar( - message = context.getString(R.string.download_started), - ) - } - } - - context.getString(R.string.cancel) -> { - viewModel.bookDownloader.cancelDownload( - viewModel.bookDownloader.getRunningDownload(book.id)?.downloadId - ) - } + BookDetailTopUI( + title = book.title, + authors = authors, + imageData = state.extraInfo.coverImage.ifEmpty { book.formats.imagejpeg }, + currentThemeMode = settingsVM.getCurrentTheme() + ) + + val pageCount = remember { + if (state.extraInfo.pageCount > 0) { + state.extraInfo.pageCount.toString() + } else { + context.getString(R.string.not_applicable) + } + } + + // Check if this book is in downloadQueue. + val buttonTextValue = + if (viewModel.bookDownloader.isBookCurrentlyDownloading(book.id)) { + stringResource(id = R.string.cancel) + } else { + if (state.bookLibraryItem != null) stringResource(id = R.string.read_book_button) else stringResource( + id = R.string.download_book_button + ) + } + + var buttonText by remember { mutableStateOf(buttonTextValue) } + var progressState by remember { mutableFloatStateOf(0f) } + var showProgressBar by remember { mutableStateOf(false) } + + // Callable which updates book details screen button. + val updateBtnText: (Int?) -> Unit = { downloadStatus -> + buttonText = when (downloadStatus) { + DownloadManager.STATUS_RUNNING -> { + showProgressBar = true + context.getString(R.string.cancel) + } + + DownloadManager.STATUS_SUCCESSFUL -> { + showProgressBar = false + context.getString(R.string.read_book_button) + } + + else -> { + showProgressBar = false + context.getString(R.string.download_book_button) + } + } + } + + // Check if this book is in downloadQueue. + if (viewModel.bookDownloader.isBookCurrentlyDownloading(book.id)) { + progressState = + viewModel.bookDownloader.getRunningDownload(book.id)?.progress?.collectAsState()?.value!! + LaunchedEffect(key1 = progressState, block = { + updateBtnText(viewModel.bookDownloader.getRunningDownload(book.id)?.status) + }) + } + + MiddleBar( + bookLang = BookUtils.getLanguagesAsString(book.languages), + pageCount = pageCount, + downloadCount = Utils.prettyCount(book.downloadCount), + progressValue = progressState, + buttonText = buttonText, + showProgressBar = showProgressBar + ) { + when (buttonText) { + context.getString(R.string.read_book_button) -> { + val bookLibraryItem = state.bookLibraryItem + /** + * Library item could be null if we reload the screen + * while some download was running, in that case we'll + * de-attach from our old state where download function + * will update library item and our new state will have + * no library item, i.e. null. + */ + if (bookLibraryItem == null) { + viewModel.viewModelScope.launch(Dispatchers.IO) { + val libraryItem = + viewModel.libraryDao.getItemByBookId(book.id)!! + withContext(Dispatchers.Main) { + Utils.openBookFile( + context = context, + internalReader = viewModel.getInternalReaderSetting(), + libraryItem = libraryItem, + navController = navController + ) } } - - Text( - text = stringResource(id = R.string.book_synopsis), - modifier = Modifier.padding(start = 12.dp, end = 8.dp), - fontSize = 20.sp, - fontFamily = figeronaFont, - fontWeight = FontWeight.Bold, - color = MaterialTheme.colorScheme.onBackground, + } else { + Utils.openBookFile( + context = context, + internalReader = viewModel.getInternalReaderSetting(), + libraryItem = bookLibraryItem, + navController = navController ) + } + } - val synopsis = state.extraInfo.description.ifEmpty { null } - if (synopsis != null) { - Text( - text = synopsis, - modifier = Modifier.padding(14.dp), - fontFamily = figeronaFont, - fontWeight = FontWeight.Medium, - color = MaterialTheme.colorScheme.onBackground, - ) - } else { - NoSynopsisUI() - } + context.getString(R.string.download_book_button) -> { + viewModel.downloadBook( + book = book, + downloadProgressListener = { downloadProgress, downloadStatus -> + progressState = downloadProgress + updateBtnText(downloadStatus) + }) + coroutineScope.launch { + snackBarHostState.showSnackbar( + message = context.getString(R.string.download_started), + ) } } + + context.getString(R.string.cancel) -> { + viewModel.bookDownloader.cancelDownload( + viewModel.bookDownloader.getRunningDownload(book.id)?.downloadId + ) + } } - }) + } + + Text( + text = stringResource(id = R.string.book_synopsis), + modifier = Modifier.padding(start = 12.dp, end = 8.dp), + fontSize = 20.sp, + fontFamily = figeronaFont, + fontWeight = FontWeight.Bold, + color = MaterialTheme.colorScheme.onBackground, + ) + + val synopsis = state.extraInfo.description.ifEmpty { null } + if (synopsis != null) { + Text( + text = synopsis, + modifier = Modifier.padding(14.dp), + fontFamily = figeronaFont, + fontWeight = FontWeight.Medium, + color = MaterialTheme.colorScheme.onBackground, + ) + } else { + NoSynopsisUI() + } + } } -@ExperimentalMaterial3Api @Composable -fun MiddleBar( +private fun MiddleBar( bookLang: String, pageCount: String, downloadCount: String, @@ -478,7 +492,7 @@ fun MiddleBar( } @Composable -fun BookDetailTopBar( +private fun BookDetailTopBar( onBackClicked: () -> Unit, onShareClicked: () -> Unit ) { Row(modifier = Modifier.fillMaxWidth(), verticalAlignment = Alignment.CenterVertically) { @@ -524,7 +538,7 @@ fun BookDetailTopBar( } @Composable -fun NoSynopsisUI() { +private fun NoSynopsisUI() { Column( modifier = Modifier.fillMaxWidth(), horizontalAlignment = Alignment.CenterHorizontally @@ -543,7 +557,7 @@ fun NoSynopsisUI() { Spacer(modifier = Modifier.weight(2f)) LottieAnimation( composition = compositionResult.value, - progress = progressAnimation, + progress = { progressAnimation }, modifier = Modifier .fillMaxWidth(0.85f) .height(200.dp), @@ -561,10 +575,7 @@ fun NoSynopsisUI() { } } -@ExperimentalCoilApi -@ExperimentalComposeUiApi -@ExperimentalMaterialApi -@ExperimentalMaterial3Api + @Composable @Preview(showBackground = true) fun BookDetailScreenPreview() { diff --git a/app/src/main/java/com/starry/myne/ui/screens/detail/viewmodels/BookDetailViewModel.kt b/app/src/main/java/com/starry/myne/ui/screens/detail/viewmodels/BookDetailViewModel.kt index 6ed82f42..c482bbce 100644 --- a/app/src/main/java/com/starry/myne/ui/screens/detail/viewmodels/BookDetailViewModel.kt +++ b/app/src/main/java/com/starry/myne/ui/screens/detail/viewmodels/BookDetailViewModel.kt @@ -16,21 +16,18 @@ package com.starry.myne.ui.screens.detail.viewmodels -import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue -import androidx.compose.ui.ExperimentalComposeUiApi import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import coil.annotation.ExperimentalCoilApi import com.starry.myne.database.library.LibraryDao import com.starry.myne.database.library.LibraryItem import com.starry.myne.repo.BookRepository import com.starry.myne.repo.models.Book import com.starry.myne.repo.models.BookSet import com.starry.myne.repo.models.ExtraInfo +import com.starry.myne.utils.Constants import com.starry.myne.utils.PreferenceUtil import com.starry.myne.utils.book.BookDownloader import com.starry.myne.utils.book.BookUtils @@ -47,10 +44,7 @@ data class BookDetailScreenState( val error: String? = null ) -@ExperimentalMaterialApi -@ExperimentalCoilApi -@ExperimentalComposeUiApi -@ExperimentalMaterial3Api + @HiltViewModel class BookDetailViewModel @Inject constructor( private val bookRepository: BookRepository, @@ -66,22 +60,25 @@ class BookDetailViewModel @Inject constructor( fun getBookDetails(bookId: String) { viewModelScope.launch(Dispatchers.IO) { - // Reset Screen state. - state = BookDetailScreenState() try { val bookSet = bookRepository.getBookById(bookId).getOrNull()!! val extraInfo = bookRepository.getExtraInfo(bookSet.books.first().title) + // This function is called again when user clicks on retry + // button. So, we need to reset the state to default values + state = BookDetailScreenState() state = if (extraInfo != null) { state.copy(bookSet = bookSet, extraInfo = extraInfo) } else { state.copy(bookSet = bookSet) } state = state.copy( - bookLibraryItem = libraryDao.getItemById(bookId.toInt()), isLoading = false + bookLibraryItem = libraryDao.getItemByBookId(bookId.toInt()), isLoading = false ) } catch (exc: Exception) { - state = - state.copy(error = exc.localizedMessage ?: "unknown-error", isLoading = false) + state = state.copy( + error = exc.localizedMessage ?: Constants.UNKNOWN_ERR, + isLoading = false + ) } } } diff --git a/app/src/main/java/com/starry/myne/ui/screens/home/composables/HomeScreen.kt b/app/src/main/java/com/starry/myne/ui/screens/home/composables/HomeScreen.kt index 6016b147..b258d56d 100644 --- a/app/src/main/java/com/starry/myne/ui/screens/home/composables/HomeScreen.kt +++ b/app/src/main/java/com/starry/myne/ui/screens/home/composables/HomeScreen.kt @@ -23,14 +23,13 @@ import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize 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.grid.GridCells import androidx.compose.foundation.lazy.grid.LazyVerticalGrid import androidx.compose.foundation.shape.RoundedCornerShape @@ -45,8 +44,6 @@ import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Close import androidx.compose.material.icons.filled.Search import androidx.compose.material.rememberModalBottomSheetState -import androidx.compose.material3.Card -import androidx.compose.material3.CardDefaults import androidx.compose.material3.CircularProgressIndicator import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.HorizontalDivider @@ -69,7 +66,6 @@ import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester import androidx.compose.ui.focus.focusRequester -import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.hapticfeedback.HapticFeedbackType import androidx.compose.ui.platform.LocalFocusManager @@ -81,7 +77,6 @@ import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.style.TextAlign -import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp @@ -91,6 +86,7 @@ import androidx.navigation.compose.rememberNavController import coil.annotation.ExperimentalCoilApi import com.starry.myne.R import com.starry.myne.ui.common.BookItemCard +import com.starry.myne.ui.common.BookLanguageButton import com.starry.myne.ui.common.NetworkError import com.starry.myne.ui.common.ProgressDots import com.starry.myne.ui.navigation.Screens @@ -165,7 +161,7 @@ fun HomeScreen(navController: NavController, networkStatus: NetworkObserver.Stat ) { items(languages.size) { idx -> val language = languages[idx] - LanguageItem( + BookLanguageButton( language = language, isSelected = language == viewModel.language.value, onClick = { @@ -200,7 +196,7 @@ fun HomeScreen(navController: NavController, networkStatus: NetworkObserver.Stat @ExperimentalMaterial3Api @ExperimentalComposeUiApi @Composable -fun HomeScreenScaffold( +private fun HomeScreenScaffold( viewModel: HomeViewModel, networkStatus: NetworkObserver.Status, navController: NavController, @@ -210,14 +206,13 @@ fun HomeScreenScaffold( ) { val keyboardController = LocalSoftwareKeyboardController.current val focusManager = LocalFocusManager.current - - val allBooksState = viewModel.allBooksState val topBarState = viewModel.topBarState Scaffold( modifier = Modifier .fillMaxSize() - .background(MaterialTheme.colorScheme.background), + .background(MaterialTheme.colorScheme.background) + .padding(bottom = 70.dp), topBar = { Column( modifier = Modifier @@ -263,138 +258,156 @@ fun HomeScreenScaffold( ) } }, + ) { paddingValues -> + HomeScreenContents( + viewModel = viewModel, + networkStatus = networkStatus, + navController = navController, + paddingValues = paddingValues + ) + } +} + +@Composable +fun HomeScreenContents( + viewModel: HomeViewModel, + networkStatus: NetworkObserver.Status, + navController: NavController, + paddingValues: PaddingValues +) { + val topBarState = viewModel.topBarState + val allBooksState = viewModel.allBooksState + + + Column( + Modifier + .fillMaxSize() + .background(MaterialTheme.colorScheme.background) + .padding(paddingValues) ) { - Box(modifier = Modifier.padding(it)) { - Column( - Modifier - .fillMaxSize() - .background(MaterialTheme.colorScheme.background) - .padding(bottom = 70.dp) - ) { - // If search text is empty show list of all books. - if (topBarState.searchText.isBlank()) { - // show fullscreen progress indicator when loading the first page. - if (allBooksState.page == 1L && allBooksState.isLoading) { - Box( - modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center + // If search text is empty show list of all books. + if (topBarState.searchText.isBlank()) { + // show fullscreen progress indicator when loading the first page. + if (allBooksState.page == 1L && allBooksState.isLoading) { + Box( + modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center + ) { + CircularProgressIndicator(color = MaterialTheme.colorScheme.primary) + } + } else if (!allBooksState.isLoading && allBooksState.error != null) { + NetworkError(onRetryClicked = { viewModel.reloadItems() }) + } else { + LazyVerticalGrid( + modifier = Modifier + .fillMaxSize() + .background(MaterialTheme.colorScheme.background) + .padding(start = 8.dp, end = 8.dp), + columns = GridCells.Adaptive(295.dp) + ) { + items(allBooksState.items.size) { i -> + val item = allBooksState.items[i] + if (networkStatus == NetworkObserver.Status.Available + && i >= allBooksState.items.size - 1 + && !allBooksState.endReached + && !allBooksState.isLoading ) { - CircularProgressIndicator(color = MaterialTheme.colorScheme.primary) + viewModel.loadNextItems() } - } else if (!allBooksState.isLoading && allBooksState.error != null) { - NetworkError(onRetryClicked = { viewModel.reloadItems() }) - } else { - LazyVerticalGrid( + Box( modifier = Modifier - .fillMaxSize() - .background(MaterialTheme.colorScheme.background) - .padding(start = 8.dp, end = 8.dp), - columns = GridCells.Adaptive(295.dp) + .padding(4.dp) + .fillMaxWidth(), + contentAlignment = Alignment.Center ) { - items(allBooksState.items.size) { i -> - val item = allBooksState.items[i] - if (networkStatus == NetworkObserver.Status.Available - && i >= allBooksState.items.size - 1 - && !allBooksState.endReached - && !allBooksState.isLoading - ) { - viewModel.loadNextItems() - } - Box( - modifier = Modifier - .padding(4.dp) - .fillMaxWidth(), - contentAlignment = Alignment.Center - ) { - BookItemCard( - title = item.title, - author = BookUtils.getAuthorsAsString(item.authors), - language = BookUtils.getLanguagesAsString(item.languages), - subjects = BookUtils.getSubjectsAsString( - item.subjects, 3 - ), - coverImageUrl = item.formats.imagejpeg - ) { - navController.navigate( - Screens.BookDetailScreen.withBookId( - item.id.toString() - ) - ) - } - } - + BookItemCard( + title = item.title, + author = BookUtils.getAuthorsAsString(item.authors), + language = BookUtils.getLanguagesAsString(item.languages), + subjects = BookUtils.getSubjectsAsString( + item.subjects, 3 + ), + coverImageUrl = item.formats.imagejpeg + ) { + navController.navigate( + Screens.BookDetailScreen.withBookId( + item.id.toString() + ) + ) } - item { - if (allBooksState.isLoading) { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(8.dp), - horizontalArrangement = Arrangement.Center - ) { - ProgressDots() - } - } + } + + } + item { + if (allBooksState.isLoading) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(8.dp), + horizontalArrangement = Arrangement.Center + ) { + ProgressDots() } } } + } + } - // Else show the search results. - } else { - LazyVerticalGrid( - modifier = Modifier - .fillMaxSize() - .background(MaterialTheme.colorScheme.background) - .padding(start = 8.dp, end = 8.dp), - columns = GridCells.Adaptive(295.dp) - ) { - if (topBarState.isSearching) { - item { - Row( - modifier = Modifier - .fillMaxWidth() - .padding(8.dp), - horizontalArrangement = Arrangement.Center - ) { - ProgressDots() - } - } + // Else show the search results. + } else { + LazyVerticalGrid( + modifier = Modifier + .fillMaxSize() + .background(MaterialTheme.colorScheme.background) + .padding(start = 8.dp, end = 8.dp), + columns = GridCells.Adaptive(295.dp) + ) { + if (topBarState.isSearching) { + item { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(8.dp), + horizontalArrangement = Arrangement.Center + ) { + ProgressDots() } + } + } - items(topBarState.searchResults.size) { i -> - val item = topBarState.searchResults[i] - Box( - modifier = Modifier - .padding(4.dp) - .fillMaxWidth(), - contentAlignment = Alignment.Center - ) { - BookItemCard( - title = item.title, - author = BookUtils.getAuthorsAsString(item.authors), - language = BookUtils.getLanguagesAsString(item.languages), - subjects = BookUtils.getSubjectsAsString( - item.subjects, 3 - ), - coverImageUrl = item.formats.imagejpeg - ) { - navController.navigate( - Screens.BookDetailScreen.withBookId( - item.id.toString() - ) - ) - } - } + items(topBarState.searchResults.size) { i -> + val item = topBarState.searchResults[i] + Box( + modifier = Modifier + .padding(4.dp) + .fillMaxWidth(), + contentAlignment = Alignment.Center + ) { + BookItemCard( + title = item.title, + author = BookUtils.getAuthorsAsString(item.authors), + language = BookUtils.getLanguagesAsString(item.languages), + subjects = BookUtils.getSubjectsAsString( + item.subjects, 3 + ), + coverImageUrl = item.formats.imagejpeg + ) { + navController.navigate( + Screens.BookDetailScreen.withBookId( + item.id.toString() + ) + ) } } } } } } + } @Composable -fun HomeTopAppBar( +private fun HomeTopAppBar( bookLanguage: BookLanguage, onSearchIconClicked: () -> Unit, onLanguageIconClicked: () -> Unit, @@ -435,7 +448,7 @@ fun HomeTopAppBar( @ExperimentalMaterial3Api @Composable -fun SearchAppBar( +private fun SearchAppBar( onCloseIconClicked: () -> Unit, onInputValueChange: (String) -> Unit, text: String, @@ -500,44 +513,6 @@ fun SearchAppBar( } } -@ExperimentalMaterial3Api -@Composable -fun LanguageItem(language: BookLanguage, isSelected: Boolean, onClick: () -> Unit) { - val buttonColor: Color - val textColor: Color - if (isSelected) { - buttonColor = MaterialTheme.colorScheme.primary - textColor = MaterialTheme.colorScheme.onPrimary - } else { - buttonColor = MaterialTheme.colorScheme.secondaryContainer - textColor = MaterialTheme.colorScheme.onSecondaryContainer - } - - Card( - modifier = Modifier - .height(60.dp) - .width(70.dp) - .padding(6.dp), - colors = CardDefaults.cardColors(containerColor = buttonColor), - shape = RoundedCornerShape(14.dp), - onClick = onClick - ) { - Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) { - Text( - modifier = Modifier.padding(2.dp), - text = language.name, - fontSize = 18.sp, - fontStyle = MaterialTheme.typography.headlineMedium.fontStyle, - fontFamily = figeronaFont, - fontWeight = FontWeight.Bold, - maxLines = 1, - overflow = TextOverflow.Ellipsis, - color = textColor, - ) - } - } -} - @ExperimentalMaterialApi @ExperimentalCoilApi @ExperimentalComposeUiApi diff --git a/app/src/main/java/com/starry/myne/ui/screens/home/viewmodels/HomeViewModel.kt b/app/src/main/java/com/starry/myne/ui/screens/home/viewmodels/HomeViewModel.kt index 0c53347a..9ac199b6 100644 --- a/app/src/main/java/com/starry/myne/ui/screens/home/viewmodels/HomeViewModel.kt +++ b/app/src/main/java/com/starry/myne/ui/screens/home/viewmodels/HomeViewModel.kt @@ -26,6 +26,7 @@ import androidx.lifecycle.viewModelScope import com.starry.myne.repo.BookRepository import com.starry.myne.repo.models.Book import com.starry.myne.repo.models.BookSet +import com.starry.myne.utils.Constants import com.starry.myne.utils.NetworkObserver import com.starry.myne.utils.Paginator import com.starry.myne.utils.PreferenceUtil @@ -87,7 +88,7 @@ class HomeViewModel @Inject constructor( }, getNextPage = { allBooksState.page + 1L }, onError = { - allBooksState = allBooksState.copy(error = it?.localizedMessage ?: "unknown-error") + allBooksState = allBooksState.copy(error = it?.localizedMessage ?: Constants.UNKNOWN_ERR) }, onSuccess = { bookSet, newPage -> /** * usually bookSet.books is not nullable and API simply returns empty list diff --git a/app/src/main/java/com/starry/myne/ui/screens/library/composables/LibraryScreen.kt b/app/src/main/java/com/starry/myne/ui/screens/library/composables/LibraryScreen.kt index e2dabf2e..07e53ebd 100644 --- a/app/src/main/java/com/starry/myne/ui/screens/library/composables/LibraryScreen.kt +++ b/app/src/main/java/com/starry/myne/ui/screens/library/composables/LibraryScreen.kt @@ -17,30 +17,44 @@ package com.starry.myne.ui.screens.library.composables import android.content.Intent +import androidx.activity.compose.rememberLauncherForActivityResult +import androidx.activity.result.contract.ActivityResultContracts +import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.Crossfade +import androidx.compose.animation.core.keyframes +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.slideInVertically import androidx.compose.foundation.ExperimentalFoundationApi import androidx.compose.foundation.background import androidx.compose.foundation.border import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.offset import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.width import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Add import androidx.compose.material.icons.outlined.Delete import androidx.compose.material3.AlertDialog import androidx.compose.material3.ButtonDefaults import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.ExtendedFloatingActionButton import androidx.compose.material3.FilledTonalButton import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme @@ -65,6 +79,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.vectorResource @@ -87,31 +102,41 @@ import com.airbnb.lottie.compose.rememberLottieComposition import com.starry.myne.BuildConfig import com.starry.myne.MainActivity import com.starry.myne.R +import com.starry.myne.database.library.LibraryItem import com.starry.myne.ui.common.CustomTopAppBar import com.starry.myne.ui.navigation.Screens +import com.starry.myne.ui.screens.library.viewmodels.ImportStatus import com.starry.myne.ui.screens.library.viewmodels.LibraryViewModel import com.starry.myne.ui.screens.settings.viewmodels.ThemeMode import com.starry.myne.ui.theme.figeronaFont import com.starry.myne.utils.Utils import com.starry.myne.utils.getActivity +import com.starry.myne.utils.isScrollingUp import kotlinx.coroutines.delay import kotlinx.coroutines.launch import me.saket.swipe.SwipeAction import me.saket.swipe.SwipeableActionsBox import java.io.File +import java.io.FileInputStream -@OptIn(ExperimentalFoundationApi::class) @Composable fun LibraryScreen(navController: NavController) { val viewModel: LibraryViewModel = hiltViewModel() - val state = viewModel.allItems.observeAsState(listOf()).value + val state = viewModel.state val context = LocalContext.current - val settingsViewModel = (context.getActivity() as MainActivity).settingsViewModel - val snackBarHostState = remember { SnackbarHostState() } - val coroutineScope = rememberCoroutineScope() + val lazyListState = rememberLazyListState() + + val importBookLauncher = + rememberLauncherForActivityResult(ActivityResultContracts.OpenDocument()) { uri -> + uri?.let { + (context as MainActivity).contentResolver.openInputStream(uri)?.let { ips -> + viewModel.importBook(context, ips as FileInputStream) + } + } + } Scaffold( snackbarHost = { SnackbarHost(snackBarHostState) }, @@ -125,162 +150,256 @@ fun LibraryScreen(navController: NavController) { iconRes = R.drawable.ic_nav_library ) }, + floatingActionButton = { + val density = LocalDensity.current + AnimatedVisibility( + visible = !state.showImportUI && lazyListState.isScrollingUp(), + enter = slideInVertically { + with(density) { 40.dp.roundToPx() } + } + fadeIn(), + exit = fadeOut( + animationSpec = keyframes { + this.durationMillis = 120 + } + ) + ) { + ExtendedFloatingActionButton(onClick = { + importBookLauncher.launch(arrayOf("application/epub+zip")) + }) { + Icon( + imageVector = Icons.Outlined.Add, + contentDescription = stringResource(id = R.string.import_button_desc) + ) + Spacer(modifier = Modifier.width(8.dp)) + Text( + text = stringResource(id = R.string.import_button_text), + fontWeight = FontWeight.Medium, + fontFamily = figeronaFont, + fontSize = 14.sp, + color = MaterialTheme.colorScheme.onSurface + ) + + } + } + } + ) { paddingValues -> + Crossfade( + targetState = state.showImportUI, + label = "ImportCrossFade" + ) { isImporting -> + if (isImporting) { + ImportingEpubAnimation(state.importStatus) + } else { + LibraryContents( + viewModel = viewModel, + lazyListState = lazyListState, + snackBarHostState = snackBarHostState, + navController = navController, + paddingValues = paddingValues + ) + } + } + + } +} + +@OptIn(ExperimentalFoundationApi::class) +@Composable +private fun LibraryContents( + viewModel: LibraryViewModel, + lazyListState: LazyListState, + snackBarHostState: SnackbarHostState, + navController: NavController, + paddingValues: PaddingValues +) { + val context = LocalContext.current + val libraryItems = viewModel.allItems.observeAsState(listOf()).value + + // Show tooltip for library screen. + LaunchedEffect(key1 = true) { + if (viewModel.shouldShowLibraryTooltip()) { + val result = snackBarHostState.showSnackbar( + message = context.getString(R.string.library_tooltip), + actionLabel = context.getString(R.string.got_it), + duration = SnackbarDuration.Indefinite + ) + + when (result) { + SnackbarResult.ActionPerformed -> { + viewModel.libraryTooltipDismissed() + } + + SnackbarResult.Dismissed -> {} + } + } + } + + Column( + modifier = Modifier + .fillMaxSize() + .background(MaterialTheme.colorScheme.background) + .padding(paddingValues) ) { - Box(modifier = Modifier.padding(it)) { - Column( + if (libraryItems.isEmpty()) { + NoLibraryItemAnimation() + } else { + LazyColumn( modifier = Modifier .fillMaxSize() - .background(MaterialTheme.colorScheme.background) + .background(MaterialTheme.colorScheme.background), + state = lazyListState ) { - if (state.isEmpty()) { - NoLibraryItemAnimation() - } else { - LazyColumn( - modifier = Modifier - .fillMaxSize() - .background(MaterialTheme.colorScheme.background) - ) { - items( - count = state.size, - key = { i -> state[i].bookId } - ) { i -> - val item = state[i] - if (item.fileExist()) { - val openDeleteDialog = remember { mutableStateOf(false) } - - val detailsAction = SwipeAction(icon = painterResource( - id = if (settingsViewModel.getCurrentTheme() == ThemeMode.Dark) R.drawable.ic_info else R.drawable.ic_info_white - ), background = MaterialTheme.colorScheme.primary, onSwipe = { - viewModel.viewModelScope.launch { - delay(250L) - navController.navigate( - Screens.BookDetailScreen.withBookId( - item.bookId.toString() - ) - ) - } - }) - - val shareAction = SwipeAction(icon = painterResource( - id = if (settingsViewModel.getCurrentTheme() == ThemeMode.Dark) R.drawable.ic_share else R.drawable.ic_share_white - ), background = MaterialTheme.colorScheme.primary, onSwipe = { - val uri = FileProvider.getUriForFile( - context, - BuildConfig.APPLICATION_ID + ".provider", - File(item.filePath) - ) - val intent = Intent(Intent.ACTION_SEND) - intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) - intent.type = context.contentResolver.getType(uri) - intent.putExtra(Intent.EXTRA_STREAM, uri) - context.startActivity( - Intent.createChooser( - intent, - context.getString(R.string.share_app_chooser) - ) - ) - }) - - SwipeableActionsBox( - modifier = Modifier - .padding(top = 4.dp, bottom = 4.dp) - .animateItemPlacement(), - startActions = listOf(shareAction), - endActions = listOf(detailsAction), - swipeThreshold = 85.dp - ) { - LibraryCard(title = item.title, - author = item.authors, - item.getFileSize(), - item.getDownloadDate(), - onReadClick = { - Utils.openBookFile( - context = context, - internalReader = viewModel.getInternalReaderSetting(), - libraryItem = item, - navController = navController - ) - }, - onDeleteClick = { openDeleteDialog.value = true }) - } - - if (openDeleteDialog.value) { - AlertDialog(onDismissRequest = { - openDeleteDialog.value = false - }, title = { - Text( - text = stringResource(id = R.string.library_delete_dialog_title), - color = MaterialTheme.colorScheme.onSurface, - ) - }, confirmButton = { - FilledTonalButton( - onClick = { - openDeleteDialog.value = false - val fileDeleted = item.deleteFile() - if (fileDeleted) { - viewModel.deleteItemFromDB(item) - } else { - coroutineScope.launch { - snackBarHostState.showSnackbar( - message = context.getString(R.string.error), - actionLabel = context.getString(R.string.ok), - duration = SnackbarDuration.Short - ) - } - } - }, - colors = ButtonDefaults.filledTonalButtonColors( - contentColor = MaterialTheme.colorScheme.onErrorContainer, - containerColor = MaterialTheme.colorScheme.errorContainer - ) - ) { - Text(stringResource(id = R.string.confirm)) - } - }, dismissButton = { - TextButton(onClick = { - openDeleteDialog.value = false - }) { - Text(stringResource(id = R.string.cancel)) - } - }) - } - - } else { - viewModel.deleteItemFromDB(item) - } - } + items( + count = libraryItems.size, + key = { i -> libraryItems[i].id } + ) { i -> + val item = libraryItems[i] + if (item.fileExist()) { + LibraryLazyItem( + modifier = Modifier.animateItemPlacement(), + item = item, + snackBarHostState = snackBarHostState, + navController = navController, + viewModel = viewModel + ) + } else { + viewModel.deleteItemFromDB(item) } + } + } - // Show tooltip for library screen. - LaunchedEffect(key1 = true) { - if (viewModel.shouldShowLibraryTooltip()) { - val result = snackBarHostState.showSnackbar( - message = context.getString(R.string.library_tooltip), - actionLabel = context.getString(R.string.got_it), - duration = SnackbarDuration.Indefinite - ) + } + } +} + +@Composable +private fun LibraryLazyItem( + modifier: Modifier, + item: LibraryItem, + snackBarHostState: SnackbarHostState, + navController: NavController, + viewModel: LibraryViewModel, +) { + val context = LocalContext.current + val settingsViewModel = (context.getActivity() as MainActivity).settingsViewModel + + val coroutineScope = rememberCoroutineScope() + val openDeleteDialog = remember { mutableStateOf(false) } + + // Swipe actions to show book details. + val detailsAction = SwipeAction(icon = painterResource( + id = if (settingsViewModel.getCurrentTheme() == ThemeMode.Dark) R.drawable.ic_info else R.drawable.ic_info_white + ), background = MaterialTheme.colorScheme.primary, onSwipe = { + viewModel.viewModelScope.launch { + delay(250L) + if (item.isExternalBook) { + snackBarHostState.showSnackbar( + message = context.getString(R.string.external_book_info_unavailable), + actionLabel = context.getString(R.string.ok), + duration = SnackbarDuration.Short + ) + } else { + navController.navigate( + Screens.BookDetailScreen.withBookId( + item.bookId.toString() + ) + ) + } + } + }) - when (result) { - SnackbarResult.ActionPerformed -> { - viewModel.libraryTooltipDismissed() - } + // Swipe actions to share book. + val shareAction = SwipeAction(icon = painterResource( + id = if (settingsViewModel.getCurrentTheme() == ThemeMode.Dark) R.drawable.ic_share else R.drawable.ic_share_white + ), background = MaterialTheme.colorScheme.primary, onSwipe = { + val uri = FileProvider.getUriForFile( + context, + BuildConfig.APPLICATION_ID + ".provider", + File(item.filePath) + ) + val intent = Intent(Intent.ACTION_SEND) + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) + intent.type = context.contentResolver.getType(uri) + intent.putExtra(Intent.EXTRA_STREAM, uri) + context.startActivity( + Intent.createChooser( + intent, + context.getString(R.string.share_app_chooser) + ) + ) + }) - SnackbarResult.Dismissed -> {} - } + SwipeableActionsBox( + modifier = modifier.padding(vertical = 4.dp), + startActions = listOf(shareAction), + endActions = listOf(detailsAction), + swipeThreshold = 85.dp + ) { + LibraryCard(title = item.title, + author = item.authors, + item.getFileSize(), + item.getDownloadDate(), + isExternalBook = item.isExternalBook, + onReadClick = { + Utils.openBookFile( + context = context, + internalReader = viewModel.getInternalReaderSetting(), + libraryItem = item, + navController = navController + ) + }, + onDeleteClick = { openDeleteDialog.value = true }) + } + + if (openDeleteDialog.value) { + AlertDialog(onDismissRequest = { + openDeleteDialog.value = false + }, title = { + Text( + text = stringResource(id = R.string.library_delete_dialog_title), + color = MaterialTheme.colorScheme.onSurface, + ) + }, confirmButton = { + FilledTonalButton( + onClick = { + openDeleteDialog.value = false + val fileDeleted = item.deleteFile() + if (fileDeleted) { + viewModel.deleteItemFromDB(item) + } else { + coroutineScope.launch { + snackBarHostState.showSnackbar( + message = context.getString(R.string.error), + actionLabel = context.getString(R.string.ok), + duration = SnackbarDuration.Short + ) } } - } + }, + colors = ButtonDefaults.filledTonalButtonColors( + contentColor = MaterialTheme.colorScheme.onErrorContainer, + containerColor = MaterialTheme.colorScheme.errorContainer + ) + ) { + Text(stringResource(id = R.string.confirm)) } - } + }, dismissButton = { + TextButton(onClick = { + openDeleteDialog.value = false + }) { + Text(stringResource(id = R.string.cancel)) + } + }) } } @Composable -fun LibraryCard( +private fun LibraryCard( title: String, author: String, fileSize: String, date: String, + isExternalBook: Boolean, onReadClick: () -> Unit, onDeleteClick: () -> Unit ) { @@ -305,7 +424,10 @@ fun LibraryCard( contentAlignment = Alignment.Center ) { Icon( - imageVector = ImageVector.vectorResource(id = R.drawable.ic_library_item), + imageVector = ImageVector.vectorResource( + id = if (isExternalBook) R.drawable.ic_library_external_item + else R.drawable.ic_library_item + ), contentDescription = stringResource(id = R.string.back_button_desc), tint = MaterialTheme.colorScheme.onPrimary, modifier = Modifier.size(32.dp) @@ -382,7 +504,7 @@ fun LibraryCard( } @Composable -fun LibraryCardButton( +private fun LibraryCardButton( text: String, icon: ImageVector, onClick: () -> Unit, @@ -417,7 +539,7 @@ fun LibraryCardButton( } @Composable -fun NoLibraryItemAnimation() { +private fun NoLibraryItemAnimation() { Column( modifier = Modifier.fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally ) { @@ -434,7 +556,7 @@ fun NoLibraryItemAnimation() { Spacer(modifier = Modifier.weight(1f)) LottieAnimation( composition = compositionResult.value, - progress = progressAnimation, + progress = { progressAnimation }, modifier = Modifier.size(300.dp), enableMergePaths = true ) @@ -452,6 +574,51 @@ fun NoLibraryItemAnimation() { } } +@Composable +private fun ImportingEpubAnimation(importStatus: ImportStatus) { + val composition = rememberLottieComposition( + spec = when (importStatus) { + ImportStatus.IMPORTING -> LottieCompositionSpec.RawRes(R.raw.epub_importing_lottie) + ImportStatus.SUCCESS -> LottieCompositionSpec.RawRes(R.raw.epub_import_success_lottie) + ImportStatus.ERROR -> LottieCompositionSpec.RawRes(R.raw.epub_import_error_lottie) + ImportStatus.IDLE -> LottieCompositionSpec.RawRes(R.raw.epub_importing_lottie) + } + ) + + Column( + modifier = Modifier.fillMaxSize(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Spacer(modifier = Modifier.weight(1f)) + LottieAnimation( + composition = composition.value, + modifier = Modifier.size(280.dp), + enableMergePaths = true, + isPlaying = true, + iterations = if (importStatus == ImportStatus.IMPORTING) + LottieConstants.IterateForever else 1, + ) + + val text = when (importStatus) { + ImportStatus.IMPORTING -> stringResource(id = R.string.epub_importing) + ImportStatus.SUCCESS -> stringResource(id = R.string.epub_imported) + ImportStatus.ERROR -> stringResource(id = R.string.epub_import_error) + ImportStatus.IDLE -> "" + } + + Text( + text = text, + style = MaterialTheme.typography.bodyLarge, + textAlign = TextAlign.Center, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 12.dp) + .offset(y = (-34).dp), + fontFamily = figeronaFont + ) + Spacer(modifier = Modifier.weight(1f)) + } +} @ExperimentalMaterial3Api @Composable @@ -461,6 +628,7 @@ fun LibraryScreenPreview() { author = "Fyodor Dostoevsky", fileSize = "5.9MB", date = "01- Jan -2020", + isExternalBook = false, onReadClick = {}, onDeleteClick = {}) } diff --git a/app/src/main/java/com/starry/myne/ui/screens/library/viewmodels/LibraryViewModel.kt b/app/src/main/java/com/starry/myne/ui/screens/library/viewmodels/LibraryViewModel.kt index 5abd4bb9..8f8a6c44 100644 --- a/app/src/main/java/com/starry/myne/ui/screens/library/viewmodels/LibraryViewModel.kt +++ b/app/src/main/java/com/starry/myne/ui/screens/library/viewmodels/LibraryViewModel.kt @@ -16,23 +16,45 @@ package com.starry.myne.ui.screens.library.viewmodels +import android.content.Context +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.setValue import androidx.lifecycle.LiveData import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import com.starry.myne.database.library.LibraryDao import com.starry.myne.database.library.LibraryItem +import com.starry.myne.epub.EpubParser import com.starry.myne.utils.PreferenceUtil +import com.starry.myne.utils.book.BookDownloader import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import java.io.File +import java.io.FileInputStream import javax.inject.Inject +enum class ImportStatus { + IMPORTING, SUCCESS, ERROR, IDLE +} + +data class LibraryScreenState( + val showImportUI: Boolean = false, + val importStatus: ImportStatus = ImportStatus.IDLE +) + @HiltViewModel class LibraryViewModel @Inject constructor( private val libraryDao: LibraryDao, + private val epubParser: EpubParser, private val preferenceUtil: PreferenceUtil ) : ViewModel() { + val allItems: LiveData> = libraryDao.getAllItems() + var state by mutableStateOf(LibraryScreenState()) fun deleteItemFromDB(item: LibraryItem) { viewModelScope.launch(Dispatchers.IO) { libraryDao.delete(item) } @@ -42,11 +64,65 @@ class LibraryViewModel @Inject constructor( PreferenceUtil.INTERNAL_READER_BOOL, true ) - fun shouldShowLibraryTooltip() = preferenceUtil.getBoolean( - PreferenceUtil.SHOW_LIBRARY_TOOLTIP_BOOL, true - ) + fun shouldShowLibraryTooltip(): Boolean { + return preferenceUtil.getBoolean(PreferenceUtil.SHOW_LIBRARY_TOOLTIP_BOOL, true) + && allItems.value?.isNotEmpty() == true + && allItems.value?.any { !it.isExternalBook } == true + } fun libraryTooltipDismissed() = preferenceUtil.putBoolean( PreferenceUtil.SHOW_LIBRARY_TOOLTIP_BOOL, false ) + + fun importBook(context: Context, fileStream: FileInputStream) { + viewModelScope.launch(Dispatchers.IO) { + try { + state = state.copy(showImportUI = true, importStatus = ImportStatus.IMPORTING) + fileStream.use { fis -> + val epubBook = epubParser.createEpubBook(fis) + // reset the stream position to 0 so that it can be read again + fis.channel.position(0) + // copy the book to internal storage + val filePath = copyBookToInternalStorage( + context, fis, + BookDownloader.createFileName(epubBook.title) + ) + // insert the book into the database + val libraryItem = LibraryItem( + bookId = 0, + title = epubBook.title, + authors = epubBook.author, + filePath = filePath, + createdAt = System.currentTimeMillis(), + isExternalBook = true + ) + libraryDao.insert(libraryItem) + delay(500) + state = state.copy(importStatus = ImportStatus.SUCCESS) + } + } catch (exc: Exception) { + delay(500) + state = state.copy(importStatus = ImportStatus.ERROR) + exc.printStackTrace() + } finally { + delay(1500) // delay to show either success or error message + state = state.copy(showImportUI = false) + delay(150) // Hide import Ui before setting the state to idle + state = state.copy(importStatus = ImportStatus.IDLE) + } + } + } + + private suspend fun copyBookToInternalStorage( + context: Context, + fileStream: FileInputStream, + filename: String + ): String = withContext(Dispatchers.IO) { + val booksFolder = File(context.filesDir, BookDownloader.BOOKS_FOLDER) + if (!booksFolder.exists()) booksFolder.mkdirs() + val bookFile = File(booksFolder, filename) + // write the file to the internal storage + bookFile.outputStream().use { fileStream.copyTo(it) } + bookFile.absolutePath + } } \ No newline at end of file diff --git a/app/src/main/java/com/starry/myne/ui/screens/main/MainScreen.kt b/app/src/main/java/com/starry/myne/ui/screens/main/MainScreen.kt index a6d018cc..9141914b 100644 --- a/app/src/main/java/com/starry/myne/ui/screens/main/MainScreen.kt +++ b/app/src/main/java/com/starry/myne/ui/screens/main/MainScreen.kt @@ -91,7 +91,7 @@ fun MainScreen( } @Composable -fun BottomBar( +private fun BottomBar( navController: NavHostController, systemUiController: SystemUiController, settingsViewModel: SettingsViewModel @@ -146,7 +146,7 @@ fun BottomBar( } @Composable -fun CustomBottomNavigationItem( +private fun CustomBottomNavigationItem( screen: BottomBarScreen, isSelected: Boolean, onClick: () -> Unit, diff --git a/app/src/main/java/com/starry/myne/ui/screens/reader/activities/ReaderActivity.kt b/app/src/main/java/com/starry/myne/ui/screens/reader/activities/ReaderActivity.kt index d17f3950..349148ee 100644 --- a/app/src/main/java/com/starry/myne/ui/screens/reader/activities/ReaderActivity.kt +++ b/app/src/main/java/com/starry/myne/ui/screens/reader/activities/ReaderActivity.kt @@ -26,8 +26,6 @@ import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.rememberLazyListState -import androidx.compose.material.ExperimentalMaterialApi -import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.snapshotFlow @@ -48,19 +46,17 @@ import kotlinx.coroutines.launch import java.io.FileInputStream object ReaderConstants { - const val EXTRA_BOOK_ID = "reader_book_id" + const val EXTRA_LIBRARY_ITEM_ID = "reader_book_id" const val EXTRA_CHAPTER_IDX = "reader_chapter_index" const val DEFAULT_NONE = -100000 } data class IntentData( - val bookId: Int?, val chapterIndex: Int?, val isExternalBook: Boolean + val libraryItemId: Int?, val chapterIndex: Int?, val isExternalBook: Boolean ) @AndroidEntryPoint -@ExperimentalMaterial3Api -@ExperimentalMaterialApi class ReaderActivity : AppCompatActivity() { private lateinit var settingsViewModel: SettingsViewModel @@ -127,7 +123,7 @@ class ReaderActivity : AppCompatActivity() { viewModel.updateReaderProgress( // Book ID is not null here since we are not opening // an external book. - bookId = intentData.bookId!!, + libraryItemId = intentData.libraryItemId!!, chapterIndex = visibleChapterIdx, chapterOffset = visibleChapterOffset ) @@ -163,8 +159,8 @@ fun handleIntent( scrollToPosition: (index: Int, offset: Int) -> Unit, onError: () -> Unit ): IntentData { - val bookId = intent.extras?.getInt( - ReaderConstants.EXTRA_BOOK_ID, ReaderConstants.DEFAULT_NONE + val libraryItemId = intent.extras?.getInt( + ReaderConstants.EXTRA_LIBRARY_ITEM_ID, ReaderConstants.DEFAULT_NONE ) val chapterIndex = intent.extras?.getInt( ReaderConstants.EXTRA_CHAPTER_IDX, ReaderConstants.DEFAULT_NONE @@ -172,10 +168,10 @@ fun handleIntent( val isExternalBook = intent.type == "application/epub+zip" // Internal book - if (bookId != null && bookId != ReaderConstants.DEFAULT_NONE) { + if (libraryItemId != null && libraryItemId != ReaderConstants.DEFAULT_NONE) { // Load epub book from given id and set chapters as items in // reader's recycler view adapter. - viewModel.loadEpubBook(bookId = bookId, onLoaded = { + viewModel.loadEpubBook(libraryItemId = libraryItemId, onLoaded = { // if there is saved progress for this book, then scroll to // last page at exact position were used had left. if (it.readerData != null && chapterIndex == ReaderConstants.DEFAULT_NONE) { @@ -199,7 +195,7 @@ fun handleIntent( onError() // If no book id is provided, then show error. } - return IntentData(bookId, chapterIndex, isExternalBook) + return IntentData(libraryItemId, chapterIndex, isExternalBook) } /** diff --git a/app/src/main/java/com/starry/myne/ui/screens/reader/composables/ReaderContent.kt b/app/src/main/java/com/starry/myne/ui/screens/reader/composables/ReaderContent.kt index 59a4d652..601e4c97 100644 --- a/app/src/main/java/com/starry/myne/ui/screens/reader/composables/ReaderContent.kt +++ b/app/src/main/java/com/starry/myne/ui/screens/reader/composables/ReaderContent.kt @@ -91,7 +91,7 @@ private fun ChapterLazyItemItem( ) { val epubBook = state.epubBook val paragraphs = remember { chunkText(chapter.body) } - val targetFontSize = (state.fontSize / 10) * 1.8f + val targetFontSize = (state.fontSize / 10) * 2.1f val fontSize by animateFloatAsState( targetValue = targetFontSize, animationSpec = tween(durationMillis = 300), diff --git a/app/src/main/java/com/starry/myne/ui/screens/reader/composables/ReaderDetailScreen.kt b/app/src/main/java/com/starry/myne/ui/screens/reader/composables/ReaderDetailScreen.kt index 0ca4ae38..8fca31f4 100644 --- a/app/src/main/java/com/starry/myne/ui/screens/reader/composables/ReaderDetailScreen.kt +++ b/app/src/main/java/com/starry/myne/ui/screens/reader/composables/ReaderDetailScreen.kt @@ -18,6 +18,7 @@ package com.starry.myne.ui.screens.reader.composables import android.content.Intent +import androidx.compose.animation.Crossfade import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -60,12 +61,14 @@ import androidx.navigation.compose.rememberNavController import coil.annotation.ExperimentalCoilApi import com.starry.myne.MainActivity import com.starry.myne.R +import com.starry.myne.database.reader.ReaderData import com.starry.myne.ui.common.BookDetailTopUI import com.starry.myne.ui.common.CustomTopAppBar import com.starry.myne.ui.common.ProgressDots import com.starry.myne.ui.common.simpleVerticalScrollbar import com.starry.myne.ui.screens.reader.activities.ReaderActivity import com.starry.myne.ui.screens.reader.activities.ReaderConstants +import com.starry.myne.ui.screens.reader.viewmodels.ReaderDetailScreenState import com.starry.myne.ui.screens.reader.viewmodels.ReaderDetailViewModel import com.starry.myne.ui.theme.figeronaFont import com.starry.myne.utils.NetworkObserver @@ -73,109 +76,128 @@ import com.starry.myne.utils.getActivity import com.starry.myne.utils.toToast -@OptIn(ExperimentalMaterial3Api::class, ExperimentalMaterialApi::class) @Composable fun ReaderDetailScreen( - bookId: String, - navController: NavController, - networkStatus: NetworkObserver.Status + libraryItemId: String, navController: NavController, networkStatus: NetworkObserver.Status ) { + val context = LocalContext.current val viewModel: ReaderDetailViewModel = hiltViewModel() val state = viewModel.state - LaunchedEffect(key1 = true) { viewModel.loadEbookData(bookId, networkStatus) } + LaunchedEffect(key1 = true) { viewModel.loadEbookData(libraryItemId, networkStatus) } + + Crossfade(targetState = state.isLoading, label = "ReaderDetailLoadingCrossFade") { isLoading -> + if (isLoading) { + Box( + modifier = Modifier + .fillMaxSize() + .padding(bottom = 65.dp), + contentAlignment = Alignment.Center + ) { + ProgressDots() + } + } else { + if (state.error != null) { + stringResource(id = R.string.error).toToast(context) + navController.navigateUp() + } else { + // Collect saved reader progress for the current book. + val readerData = viewModel.readerData?.collectAsState(initial = null)?.value + ReaderDetailScaffold( + libraryItemId = libraryItemId, + readerData = readerData, + state = state, + navController = navController + ) + } + + } + } +} +@Composable +private fun ReaderDetailScaffold( + libraryItemId: String, + readerData: ReaderData?, + state: ReaderDetailScreenState, + navController: NavController +) { val context = LocalContext.current val settingsVM = (context.getActivity() as MainActivity).settingsViewModel - if (state.isLoading) { - Box( - modifier = Modifier - .fillMaxSize() - .padding(bottom = 65.dp), - contentAlignment = Alignment.Center - ) { - ProgressDots() + Scaffold(topBar = { + CustomTopAppBar(headerText = stringResource(id = R.string.reader_detail_header)) { + navController.navigateUp() } - } else if (state.error != null) { - stringResource(id = R.string.error).toToast(context) - } else { - // Collect saved reader progress for the current book. - val readerItem = viewModel.readerData?.collectAsState(initial = null)?.value - - Scaffold( - topBar = { - CustomTopAppBar(headerText = stringResource(id = R.string.reader_detail_header)) { - navController.navigateUp() - } + }, floatingActionButton = { + ExtendedFloatingActionButton( + text = { Text(text = stringResource(id = if (readerData != null) R.string.continue_reading_button else R.string.start_reading_button)) }, + onClick = { + val intent = Intent(context, ReaderActivity::class.java) + intent.putExtra( + ReaderConstants.EXTRA_LIBRARY_ITEM_ID, libraryItemId.toInt() + ) + context.startActivity(intent) }, - floatingActionButton = { - ExtendedFloatingActionButton( - text = { Text(text = stringResource(id = if (readerItem != null) R.string.continue_reading_button else R.string.start_reading_button)) }, - onClick = { - val intent = Intent(context, ReaderActivity::class.java) - intent.putExtra(ReaderConstants.EXTRA_BOOK_ID, bookId.toInt()) - context.startActivity(intent) - }, - icon = { - Icon( - imageVector = ImageVector.vectorResource(id = R.drawable.ic_reader_fab_button), - contentDescription = null - ) - }, - modifier = Modifier.padding(end = 10.dp, bottom = 8.dp), + icon = { + Icon( + imageVector = ImageVector.vectorResource(id = R.drawable.ic_reader_fab_button), + contentDescription = null ) - } + }, + modifier = Modifier.padding(end = 10.dp, bottom = 8.dp), + ) + }) { + Column( + modifier = Modifier + .fillMaxSize() + .background(MaterialTheme.colorScheme.background) + .padding(it) ) { - Column( - modifier = Modifier - .fillMaxSize() - .background(MaterialTheme.colorScheme.background) - .padding(it) - ) { - // Get the cover image data. - val imageData = state.ebookData!!.coverImage ?: state.ebookData.epubBook.coverImage - - BookDetailTopUI( - title = state.ebookData.title, - authors = state.ebookData.authors, - imageData = imageData, - currentThemeMode = settingsVM.getCurrentTheme(), - progressPercent = readerItem?.getProgressPercent(state.ebookData.epubBook.chapters.size), - ) + // Get the cover image data. + val imageData = + state.ebookData!!.coverImage ?: state.ebookData.epubBook.coverImage + + BookDetailTopUI( + title = state.ebookData.title, + authors = state.ebookData.authors, + imageData = imageData, + currentThemeMode = settingsVM.getCurrentTheme(), + progressPercent = readerData?.getProgressPercent(state.ebookData.epubBook.chapters.size), + ) - HorizontalDivider( - modifier = Modifier.padding( - start = 20.dp, end = 20.dp, top = 2.dp, bottom = 2.dp - ), - thickness = 2.dp, - color = MaterialTheme.colorScheme.surfaceColorAtElevation(4.dp) - ) + HorizontalDivider( + modifier = Modifier.padding( + start = 20.dp, end = 20.dp, top = 2.dp, bottom = 2.dp + ), + thickness = 2.dp, + color = MaterialTheme.colorScheme.surfaceColorAtElevation(4.dp) + ) - val lazyListState = rememberLazyListState() - LazyColumn( - state = lazyListState, modifier = Modifier.simpleVerticalScrollbar( - lazyListState, color = MaterialTheme.colorScheme.primary - ) - ) { - items(state.ebookData.epubBook.chapters.size) { idx -> - val chapter = state.ebookData.epubBook.chapters[idx] - ChapterItem(chapterTitle = chapter.title, onClick = { - val intent = Intent(context, ReaderActivity::class.java) - intent.putExtra(ReaderConstants.EXTRA_BOOK_ID, bookId.toInt()) - intent.putExtra(ReaderConstants.EXTRA_CHAPTER_IDX, idx) - context.startActivity(intent) - }) - } + val lazyListState = rememberLazyListState() + LazyColumn( + state = lazyListState, modifier = Modifier.simpleVerticalScrollbar( + lazyListState, color = MaterialTheme.colorScheme.primary + ) + ) { + items(state.ebookData.epubBook.chapters.size) { idx -> + val chapter = state.ebookData.epubBook.chapters[idx] + ChapterItem(chapterTitle = chapter.title, onClick = { + val intent = Intent(context, ReaderActivity::class.java) + intent.putExtra( + ReaderConstants.EXTRA_LIBRARY_ITEM_ID, libraryItemId.toInt() + ) + intent.putExtra(ReaderConstants.EXTRA_CHAPTER_IDX, idx) + context.startActivity(intent) + }) } } } } } -@ExperimentalMaterial3Api @Composable -fun ChapterItem(chapterTitle: String, onClick: () -> Unit) { +private fun ChapterItem(chapterTitle: String, onClick: () -> Unit) { Card( onClick = { onClick() }, colors = CardDefaults.cardColors( diff --git a/app/src/main/java/com/starry/myne/ui/screens/reader/composables/ReaderScreen.kt b/app/src/main/java/com/starry/myne/ui/screens/reader/composables/ReaderScreen.kt index 887e500f..329c618b 100644 --- a/app/src/main/java/com/starry/myne/ui/screens/reader/composables/ReaderScreen.kt +++ b/app/src/main/java/com/starry/myne/ui/screens/reader/composables/ReaderScreen.kt @@ -45,7 +45,6 @@ import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.selection.selectable import androidx.compose.foundation.selection.selectableGroup import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Menu import androidx.compose.material3.AlertDialog @@ -105,8 +104,7 @@ import kotlinx.coroutines.launch enum class TextScaleButtonType { INCREASE, DECREASE } -@ExperimentalMaterialApi -@ExperimentalMaterial3Api +@OptIn(ExperimentalMaterial3Api::class) @Composable fun ReaderScreen( viewModel: ReaderViewModel, @@ -307,7 +305,7 @@ private fun FontChooserDialog( showFontDialog.value = false }, title = { Text( - text = stringResource(id = R.string.reader_font_style_chooer), + text = stringResource(id = R.string.reader_font_style_chooser), color = MaterialTheme.colorScheme.onSurface, ) }, text = { @@ -364,8 +362,6 @@ private fun FontChooserDialog( } -@ExperimentalMaterialApi -@ExperimentalMaterial3Api @Composable private fun BottomSheetContents( viewModel: ReaderViewModel, @@ -393,8 +389,7 @@ private fun BottomSheetContents( } } -@ExperimentalMaterialApi -@ExperimentalMaterial3Api + @Composable private fun TextScaleControls( viewModel: ReaderViewModel, @@ -467,8 +462,7 @@ private fun FontSelectionButton( } } -@ExperimentalMaterialApi -@ExperimentalMaterial3Api + @Composable private fun ReaderTextScaleButton( buttonType: TextScaleButtonType, diff --git a/app/src/main/java/com/starry/myne/ui/screens/reader/viewmodels/ReaderDetailViewModel.kt b/app/src/main/java/com/starry/myne/ui/screens/reader/viewmodels/ReaderDetailViewModel.kt index d99c182d..c3ed37dc 100644 --- a/app/src/main/java/com/starry/myne/ui/screens/reader/viewmodels/ReaderDetailViewModel.kt +++ b/app/src/main/java/com/starry/myne/ui/screens/reader/viewmodels/ReaderDetailViewModel.kt @@ -17,6 +17,7 @@ package com.starry.myne.ui.screens.reader.viewmodels +import android.util.Log import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.setValue @@ -64,25 +65,33 @@ class ReaderDetailViewModel @Inject constructor( get() = _readerData private var _readerData: Flow? = null - fun loadEbookData(bookId: String, networkStatus: NetworkObserver.Status) { + fun loadEbookData(libraryItemId: String, networkStatus: NetworkObserver.Status) { viewModelScope.launch(Dispatchers.IO) { // Library item is not null as this screen is only accessible from the library. - val libraryItem = libraryDao.getItemById(bookId.toInt())!! + val libraryItem = libraryDao.getItemById(libraryItemId.toInt())!! // Get reader data if it exists. - _readerData = readerDao.getReaderDataAsFlow(bookId.toInt()) + _readerData = readerDao.getReaderDataAsFlow(libraryItemId.toInt()) val coverImage: String? = try { - if (networkStatus == NetworkObserver.Status.Available) bookRepository.getExtraInfo( - libraryItem.title - )?.coverImage else null + if (!libraryItem.isExternalBook + && networkStatus == NetworkObserver.Status.Available + ) bookRepository.getExtraInfo(libraryItem.title)?.coverImage else null } catch (exc: Exception) { null } - // Create EbookData object. + // Gutenberg for some reason don't include proper navMap in chinese books + // in toc, so we need to parse the book based on spine, instead of toc. + // This is a workaround for internal chinese books. + var epubBook = epubParser.createEpubBook(libraryItem.filePath) + if (epubBook.language == "zh" && !libraryItem.isExternalBook) { + Log.d("ReaderDetailViewModel", "Parsing book without toc for chinese book.") + epubBook = epubParser.createEpubBook(libraryItem.filePath, shouldUseToc = false) + } + // Create ebook data. val ebookData = EbookData( coverImage = coverImage, title = libraryItem.title, authors = libraryItem.authors, - epubBook = epubParser.createEpubBook(libraryItem.filePath) + epubBook = epubBook ) delay(500) // Add delay to avoid flickering. state = state.copy(isLoading = false, ebookData = ebookData) diff --git a/app/src/main/java/com/starry/myne/ui/screens/reader/viewmodels/ReaderViewModel.kt b/app/src/main/java/com/starry/myne/ui/screens/reader/viewmodels/ReaderViewModel.kt index eb188c93..a59bc22e 100644 --- a/app/src/main/java/com/starry/myne/ui/screens/reader/viewmodels/ReaderViewModel.kt +++ b/app/src/main/java/com/starry/myne/ui/screens/reader/viewmodels/ReaderViewModel.kt @@ -107,12 +107,18 @@ class ReaderViewModel @Inject constructor( private val _visibleChapterIndex = mutableIntStateOf(0) val visibleChapterIndex: State = _visibleChapterIndex - fun loadEpubBook(bookId: Int, onLoaded: (ReaderScreenState) -> Unit) { + fun loadEpubBook(libraryItemId: Int, onLoaded: (ReaderScreenState) -> Unit) { viewModelScope.launch(Dispatchers.IO) { - val libraryItem = libraryDao.getItemById(bookId) - val readerData = readerDao.getReaderData(bookId) + val libraryItem = libraryDao.getItemById(libraryItemId) + val readerData = readerDao.getReaderData(libraryItemId) // parse and create epub book - val epubBook = epubParser.createEpubBook(libraryItem!!.filePath) + var epubBook = epubParser.createEpubBook(libraryItem!!.filePath) + // Gutenberg for some reason don't include proper navMap in chinese books + // in toc, so we need to parse the book based on spine, instead of toc. + // This is a workaround for internal chinese books. + if (epubBook.language == "zh" && !libraryItem.isExternalBook) { + epubBook = epubParser.createEpubBook(libraryItem.filePath, shouldUseToc = false) + } state = state.copy(epubBook = epubBook, readerData = readerData) onLoaded(state) // Added some delay to avoid choppy animation. @@ -123,26 +129,33 @@ class ReaderViewModel @Inject constructor( fun loadEpubBookExternal(fileStream: FileInputStream) { viewModelScope.launch(Dispatchers.IO) { - // parse and create epub book - val epubBook = epubParser.createEpubBook(fileStream, shouldUseToc = false) - fileStream.close() // close the file stream - state = state.copy(epubBook = epubBook) - // Added some delay to avoid choppy animation. - delay(200L) - state = state.copy(isLoading = false) + fileStream.use { fis -> + // parse and create epub book + val epubBook = epubParser.createEpubBook(fis, shouldUseToc = false) + state = state.copy(epubBook = epubBook) + // Added some delay to avoid choppy animation. + delay(200L) + state = state.copy(isLoading = false) + } } } - fun updateReaderProgress(bookId: Int, chapterIndex: Int, chapterOffset: Int) { + fun updateReaderProgress(libraryItemId: Int, chapterIndex: Int, chapterOffset: Int) { viewModelScope.launch(Dispatchers.IO) { - if (readerDao.getReaderData(bookId) != null && chapterIndex != state.epubBook?.chapters!!.size - 1) { - readerDao.update(bookId, chapterIndex, chapterOffset) + if (readerDao.getReaderData(libraryItemId) != null && chapterIndex != state.epubBook?.chapters!!.size - 1) { + readerDao.update(libraryItemId, chapterIndex, chapterOffset) } else if (chapterIndex == state.epubBook?.chapters!!.size - 1) { // if the user has reached last chapter, delete this book // from reader database instead of saving it's progress . - readerDao.getReaderData(bookId)?.let { readerDao.delete(it.bookId) } + readerDao.getReaderData(libraryItemId)?.let { readerDao.delete(it.libraryItemId) } } else { - readerDao.insert(readerData = ReaderData(bookId, chapterIndex, chapterOffset)) + readerDao.insert( + readerData = ReaderData( + libraryItemId, + chapterIndex, + chapterOffset + ) + ) } } } diff --git a/app/src/main/java/com/starry/myne/ui/screens/settings/composables/AboutScreen.kt b/app/src/main/java/com/starry/myne/ui/screens/settings/composables/AboutScreen.kt index 2dff1003..b8914c4d 100644 --- a/app/src/main/java/com/starry/myne/ui/screens/settings/composables/AboutScreen.kt +++ b/app/src/main/java/com/starry/myne/ui/screens/settings/composables/AboutScreen.kt @@ -176,7 +176,7 @@ fun AboutScreen(navController: NavController) { } @Composable -fun LinkButton( +private fun LinkButton( text: String, icon: ImageVector, onClick: () -> Unit, @@ -205,7 +205,7 @@ fun LinkButton( } @Composable -fun AppInfoCard() { +private fun AppInfoCard() { Card( modifier = Modifier .fillMaxWidth() @@ -296,7 +296,7 @@ fun AppInfoCard() { } @Composable -fun DeveloperCard(context: Context) { +private fun DeveloperCard(context: Context) { Card( modifier = Modifier .height(135.dp) @@ -339,7 +339,7 @@ fun DeveloperCard(context: Context) { Spacer(modifier = Modifier.height(4.dp)) Text( - text = stringResource(id = R.string.dev_username), + text = stringResource(id = R.string.dev_email), fontSize = 16.sp, fontFamily = figeronaFont, fontWeight = FontWeight.Medium, @@ -368,7 +368,7 @@ fun DeveloperCard(context: Context) { } } -fun openWebLink(context: Context, url: String) { +private fun openWebLink(context: Context, url: String) { val uri: Uri = Uri.parse(url) val intent = Intent(Intent.ACTION_VIEW, uri) try { diff --git a/app/src/main/java/com/starry/myne/ui/screens/settings/composables/SettingsItems.kt b/app/src/main/java/com/starry/myne/ui/screens/settings/composables/SettingsItems.kt index eb5a658b..6e41d816 100644 --- a/app/src/main/java/com/starry/myne/ui/screens/settings/composables/SettingsItems.kt +++ b/app/src/main/java/com/starry/myne/ui/screens/settings/composables/SettingsItems.kt @@ -38,7 +38,6 @@ import androidx.compose.material.icons.filled.Favorite import androidx.compose.material.icons.filled.NotificationsOff import androidx.compose.material3.Card import androidx.compose.material3.CardDefaults -import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Switch @@ -62,7 +61,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import com.starry.myne.ui.theme.figeronaFont -@ExperimentalMaterial3Api + @Composable fun SettingItem(icon: ImageVector, mainText: String, subText: String, onClick: () -> Unit) { Card( @@ -134,7 +133,6 @@ fun SettingItem(icon: ImageVector, mainText: String, subText: String, onClick: ( } } -@ExperimentalMaterial3Api @Composable fun SettingItemWIthSwitch( icon: ImageVector, @@ -223,7 +221,7 @@ fun SettingItemWIthSwitch( } } -@ExperimentalMaterial3Api + @Preview(showBackground = true) @Composable private fun Preview() { diff --git a/app/src/main/java/com/starry/myne/ui/screens/settings/composables/SettingsScreen.kt b/app/src/main/java/com/starry/myne/ui/screens/settings/composables/SettingsScreen.kt index 3b76dfa1..f3fef5e1 100644 --- a/app/src/main/java/com/starry/myne/ui/screens/settings/composables/SettingsScreen.kt +++ b/app/src/main/java/com/starry/myne/ui/screens/settings/composables/SettingsScreen.kt @@ -16,7 +16,6 @@ package com.starry.myne.ui.screens.settings.composables -import android.annotation.SuppressLint import android.content.Context import android.os.Build import androidx.compose.foundation.Image @@ -38,7 +37,6 @@ import androidx.compose.foundation.selection.selectableGroup import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.verticalScroll -import androidx.compose.material.ExperimentalMaterialApi import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.BrightnessMedium import androidx.compose.material.icons.filled.Info @@ -63,7 +61,6 @@ import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment -import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color @@ -79,7 +76,6 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import androidx.navigation.NavController import androidx.navigation.compose.rememberNavController -import coil.annotation.ExperimentalCoilApi import com.starry.myne.BuildConfig import com.starry.myne.MainActivity import com.starry.myne.R @@ -91,12 +87,7 @@ import com.starry.myne.ui.theme.figeronaFont import com.starry.myne.utils.getActivity import kotlinx.coroutines.launch -@OptIn( - ExperimentalMaterial3Api::class, ExperimentalComposeUiApi::class, - ExperimentalMaterialApi::class, ExperimentalCoilApi::class -) -@SuppressLint("UnusedMaterial3ScaffoldPaddingParameter") - +@OptIn(ExperimentalMaterial3Api::class) @Composable fun SettingsScreen(navController: NavController) { val context = LocalContext.current @@ -107,11 +98,12 @@ fun SettingsScreen(navController: NavController) { Scaffold( modifier = Modifier.padding(bottom = 70.dp), snackbarHost = { SnackbarHost(snackBarHostState) }, - ) { + ) { paddingValues -> Column( modifier = Modifier .fillMaxSize() .background(MaterialTheme.colorScheme.background) + .padding(paddingValues) ) { CustomTopAppBar( headerText = stringResource(id = R.string.settings_header), @@ -129,7 +121,7 @@ fun SettingsScreen(navController: NavController) { @Composable @ExperimentalMaterial3Api -fun SettingsCard() { +private fun SettingsCard() { Card( modifier = Modifier .height(150.dp) @@ -198,9 +190,9 @@ fun SettingsCard() { } } -@ExperimentalMaterial3Api + @Composable -fun GeneralOptionsUI(viewModel: SettingsViewModel, context: Context) { +private fun GeneralOptionsUI(viewModel: SettingsViewModel, context: Context) { val internalReaderValue = when (viewModel.getInternalReaderValue()) { true -> stringResource(id = R.string.reader_option_inbuilt) false -> stringResource(id = R.string.reader_option_external) @@ -309,12 +301,8 @@ fun GeneralOptionsUI(viewModel: SettingsViewModel, context: Context) { } -@ExperimentalCoilApi -@ExperimentalComposeUiApi -@ExperimentalMaterial3Api -@ExperimentalMaterialApi @Composable -fun DisplayOptionsUI( +private fun DisplayOptionsUI( viewModel: SettingsViewModel, context: Context, snackBarHostState: SnackbarHostState, @@ -458,12 +446,8 @@ fun DisplayOptionsUI( } } -@ExperimentalCoilApi -@ExperimentalComposeUiApi -@ExperimentalMaterialApi -@ExperimentalMaterial3Api @Composable -fun InformationUI(navController: NavController) { +private fun InformationUI(navController: NavController) { Box { Column( modifier = Modifier @@ -496,10 +480,7 @@ fun InformationUI(navController: NavController) { } -@ExperimentalCoilApi -@ExperimentalComposeUiApi -@ExperimentalMaterialApi -@ExperimentalMaterial3Api + @Composable @Preview fun SettingsScreenPreview() { diff --git a/app/src/main/java/com/starry/myne/ui/screens/welcome/composables/WelcomeScreen.kt b/app/src/main/java/com/starry/myne/ui/screens/welcome/composables/WelcomeScreen.kt index 3ab75a58..a51f60bc 100644 --- a/app/src/main/java/com/starry/myne/ui/screens/welcome/composables/WelcomeScreen.kt +++ b/app/src/main/java/com/starry/myne/ui/screens/welcome/composables/WelcomeScreen.kt @@ -109,7 +109,7 @@ fun WelcomeScreen(navController: NavController) { Box(modifier = Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) { LottieAnimation( composition = compositionResult.value, - progress = progressAnimation, + progress = { progressAnimation }, modifier = Modifier.size(300.dp), enableMergePaths = true ) diff --git a/app/src/main/java/com/starry/myne/utils/Constants.kt b/app/src/main/java/com/starry/myne/utils/Constants.kt index 6f7a0403..644dd6f4 100644 --- a/app/src/main/java/com/starry/myne/utils/Constants.kt +++ b/app/src/main/java/com/starry/myne/utils/Constants.kt @@ -17,8 +17,12 @@ package com.starry.myne.utils object Constants { + // Database const val DATABASE_NAME = "myne.db" + // Error placeholder + const val UNKNOWN_ERR = "unknown-error" + // URLs const val DEV_GITHUB_URL = "https://github.com/starry-shivam" const val DEV_TELEGRAM_URL = "https://t.me/starryboi" diff --git a/app/src/main/java/com/starry/myne/utils/Extensions.kt b/app/src/main/java/com/starry/myne/utils/Extensions.kt index 288ba618..e32bf655 100644 --- a/app/src/main/java/com/starry/myne/utils/Extensions.kt +++ b/app/src/main/java/com/starry/myne/utils/Extensions.kt @@ -22,8 +22,13 @@ import android.widget.Toast import androidx.appcompat.app.AppCompatActivity import androidx.compose.foundation.clickable import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.lazy.LazyListState import androidx.compose.runtime.Composable +import androidx.compose.runtime.derivedStateOf +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableIntStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier fun Context.getActivity(): AppCompatActivity? = when (this) { @@ -43,4 +48,22 @@ fun Modifier.noRippleClickable(onClick: () -> Unit): Modifier = interactionSource = remember { MutableInteractionSource() } ) { onClick() - } \ No newline at end of file + } + +@Composable +fun LazyListState.isScrollingUp(): Boolean { + var previousIndex by remember(this) { mutableIntStateOf(firstVisibleItemIndex) } + var previousScrollOffset by remember(this) { mutableIntStateOf(firstVisibleItemScrollOffset) } + return remember(this) { + derivedStateOf { + if (previousIndex != firstVisibleItemIndex) { + previousIndex > firstVisibleItemIndex + } else { + previousScrollOffset >= firstVisibleItemScrollOffset + }.also { + previousIndex = firstVisibleItemIndex + previousScrollOffset = firstVisibleItemScrollOffset + } + } + }.value +} \ No newline at end of file diff --git a/app/src/main/java/com/starry/myne/utils/Paginator.kt b/app/src/main/java/com/starry/myne/utils/Paginator.kt index 132a9a3a..b28bd61a 100644 --- a/app/src/main/java/com/starry/myne/utils/Paginator.kt +++ b/app/src/main/java/com/starry/myne/utils/Paginator.kt @@ -16,6 +16,18 @@ package com.starry.myne.utils +/** + * A class that helps in paginating data from a remote source. + * + * @param Page The type of the page. + * @param BookSet The type of the data set. + * @property initialPage The initial page to start from. + * @property onLoadUpdated A lambda that is called when the loading state is updated. + * @property onRequest A suspend function that is called to request data from the remote source. + * @property getNextPage A suspend function that is called to get the next page. + * @property onError A suspend function that is called when an error occurs. + * @property onSuccess A suspend function that is called when the data is successfully loaded. + */ class Paginator( private val initialPage: Page, private inline val onLoadUpdated: (Boolean) -> Unit, @@ -28,6 +40,9 @@ class Paginator( private var currentPage = initialPage private var isMakingRequest = false + /** + * Loads the next items from the remote source. + */ suspend fun loadNextItems() { if (isMakingRequest) { return @@ -46,6 +61,9 @@ class Paginator( onLoadUpdated(false) } + /** + * Resets the paginator to the initial state. + */ fun reset() { currentPage = initialPage } diff --git a/app/src/main/java/com/starry/myne/utils/Utils.kt b/app/src/main/java/com/starry/myne/utils/Utils.kt index f0e4bb58..3d771de5 100644 --- a/app/src/main/java/com/starry/myne/utils/Utils.kt +++ b/app/src/main/java/com/starry/myne/utils/Utils.kt @@ -55,7 +55,7 @@ object Utils { ) { if (internalReader) { - navController.navigate(Screens.ReaderDetailScreen.withBookId(libraryItem.bookId.toString())) + navController.navigate(Screens.ReaderDetailScreen.withLibraryItemId(libraryItem.id.toString())) } else { val uri = FileProvider.getUriForFile( context, BuildConfig.APPLICATION_ID + ".provider", File(libraryItem.filePath) diff --git a/app/src/main/java/com/starry/myne/utils/book/BookDownloader.kt b/app/src/main/java/com/starry/myne/utils/book/BookDownloader.kt index 8c81b4c6..eac00f03 100644 --- a/app/src/main/java/com/starry/myne/utils/book/BookDownloader.kt +++ b/app/src/main/java/com/starry/myne/utils/book/BookDownloader.kt @@ -40,9 +40,29 @@ class BookDownloader(private val context: Context) { companion object { private const val TAG = "BookDownloader" - private const val BOOKS_FOLDER = "ebooks" - private const val TEMP_FOLDER = "temp_books" + const val BOOKS_FOLDER = "ebooks" + const val TEMP_FOLDER = "temp_books" private const val MAX_FILENAME_LENGTH = 200 + + /** + * Sanitizes book title by replacing forbidden chars which are not allowed + * as the file name and builds file name for the epub file by joining all + * of the words in the book title at the end. + * @param title title of the book for which file name is required. + * @return [String] file name for the given book. + */ + fun createFileName(title: String): String { + val sanitizedTitle = title + .replace(":", ";") + .replace("\"", "") + .replace("/", "-") + .replace("\\", "-") + .split(" ") + .joinToString(separator = "+") { word -> + word.replace(Regex("[^\\p{ASCII}]"), "") + }.take(MAX_FILENAME_LENGTH).trim() + return "$sanitizedTitle.epub" + } } private val downloadJob = Job() @@ -81,7 +101,7 @@ class BookDownloader(private val context: Context) { if (runningDownloads.containsKey(book.id)) return // Create file for the downloaded book. - val filename = getFilenameForBook(book) + val filename = createFileName(book.title) val tempFolder = File(context.getExternalFilesDir(null), TEMP_FOLDER) if (!tempFolder.exists()) tempFolder.mkdirs() val tempFile = File(tempFolder, filename) @@ -193,24 +213,4 @@ class BookDownloader(private val context: Context) { */ fun cancelDownload(downloadId: Long?) = downloadId?.let { downloadManager.remove(it) } - /** - * Sanitizes book title by replacing forbidden chars which are not allowed - * as the file name & builds file name for the epub file by joining all of - * the words in the book title at the end. - * @param book [Book] for which file name is required. - * @return [String] file name for the given book. - */ - private fun getFilenameForBook(book: Book): String { - val sanitizedTitle = book.title - .replace(":", ";") - .replace("\"", "") - .replace("/", "-") - .replace("\\", "-") - .split(" ") - .joinToString(separator = "+") { word -> - word.replace(Regex("[^\\p{ASCII}]"), "") - }.take(MAX_FILENAME_LENGTH).trim() - return "$sanitizedTitle.epub" - } - } \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_library_external_item.xml b/app/src/main/res/drawable/ic_library_external_item.xml new file mode 100644 index 00000000..96e0b260 --- /dev/null +++ b/app/src/main/res/drawable/ic_library_external_item.xml @@ -0,0 +1,20 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/raw/epub_import_error_lottie.json b/app/src/main/res/raw/epub_import_error_lottie.json new file mode 100644 index 00000000..9489da0a --- /dev/null +++ b/app/src/main/res/raw/epub_import_error_lottie.json @@ -0,0 +1,2225 @@ +{ + "nm": "verify failed", + "ddd": 0, + "h": 112, + "w": 112, + "meta": { + "g": "@lottiefiles/toolkit-js 0.33.2" + }, + "layers": [ + { + "ty": 0, + "nm": "flick 2", + "sr": 1, + "st": 34, + "op": 184, + "ip": 34, + "hd": false, + "ddd": 0, + "bm": 0, + "hasMask": false, + "ao": 0, + "ks": { + "a": { + "a": 0, + "k": [ + 56, + 56, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 31.25, + 31.25, + 100 + ], + "ix": 6 + }, + "sk": { + "a": 0, + "k": 0 + }, + "p": { + "a": 0, + "k": [ + 84, + 31, + 0 + ], + "ix": 2 + }, + "r": { + "a": 0, + "k": 45, + "ix": 10 + }, + "sa": { + "a": 0, + "k": 0 + }, + "o": { + "a": 0, + "k": 100, + "ix": 11 + } + }, + "ef": [], + "w": 112, + "h": 112, + "refId": "comp_0", + "ind": 1 + }, + { + "ty": 4, + "nm": "Shape Layer 10", + "sr": 1, + "st": -14, + "op": 136, + "ip": 29, + "hd": false, + "ddd": 0, + "bm": 0, + "hasMask": false, + "ao": 0, + "ks": { + "a": { + "a": 0, + "k": [ + -9.678, + -12.102, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 14.905, + 14.905, + 100 + ], + "ix": 6 + }, + "sk": { + "a": 0, + "k": 0 + }, + "p": { + "a": 0, + "k": [ + -14.616, + -8.795, + 0 + ], + "ix": 2 + }, + "r": { + "a": 0, + "k": -89.887, + "ix": 10 + }, + "sa": { + "a": 0, + "k": 0 + }, + "o": { + "a": 0, + "k": 100, + "ix": 11 + } + }, + "ef": [], + "shapes": [ + { + "ty": "gr", + "bm": 0, + "hd": false, + "mn": "ADBE Vector Group", + "nm": "Rectangle 1", + "ix": 1, + "cix": 2, + "np": 3, + "it": [ + { + "ty": "rc", + "bm": 0, + "hd": false, + "mn": "ADBE Vector Shape - Rect", + "nm": "Rectangle Path 1", + "d": 1, + "p": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": 2, + "ix": 4 + }, + "s": { + "a": 0, + "k": [ + 14.893, + 21.297 + ], + "ix": 2 + } + }, + { + "ty": "fl", + "bm": 0, + "hd": false, + "mn": "ADBE Vector Graphic - Fill", + "nm": "Fill 1", + "c": { + "a": 0, + "k": [ + 1, + 1, + 1 + ], + "ix": 4 + }, + "r": 1, + "o": { + "a": 0, + "k": 100, + "ix": 5 + } + }, + { + "ty": "tr", + "a": { + "a": 0, + "k": [ + -0.082, + -10.641 + ], + "ix": 1 + }, + "s": { + "a": 1, + "k": [ + { + "o": { + "x": 0.167, + "y": 0 + }, + "i": { + "x": 0.057, + "y": 1 + }, + "s": [ + 78.562, + 3.532 + ], + "t": 29 + }, + { + "s": [ + 78.562, + 147.058 + ], + "t": 37 + } + ], + "ix": 3 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 4 + }, + "p": { + "a": 0, + "k": [ + -15.207, + -20.922 + ], + "ix": 2 + }, + "r": { + "a": 0, + "k": -45, + "ix": 6 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 5 + }, + "o": { + "a": 0, + "k": 100, + "ix": 7 + } + } + ] + } + ], + "ind": 2, + "parent": 4 + }, + { + "ty": 4, + "nm": "Shape Layer 9", + "sr": 1, + "st": -18, + "op": 132, + "ip": 21, + "hd": false, + "ddd": 0, + "bm": 0, + "hasMask": false, + "ao": 0, + "ks": { + "a": { + "a": 0, + "k": [ + -9.678, + -12.102, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 14.905, + 14.905, + 100 + ], + "ix": 6 + }, + "sk": { + "a": 0, + "k": 0 + }, + "p": { + "a": 0, + "k": [ + -15.877, + -11.107, + 0 + ], + "ix": 2 + }, + "r": { + "a": 0, + "k": 0.113, + "ix": 10 + }, + "sa": { + "a": 0, + "k": 0 + }, + "o": { + "a": 0, + "k": 100, + "ix": 11 + } + }, + "ef": [], + "shapes": [ + { + "ty": "gr", + "bm": 0, + "hd": false, + "mn": "ADBE Vector Group", + "nm": "Rectangle 1", + "ix": 1, + "cix": 2, + "np": 3, + "it": [ + { + "ty": "rc", + "bm": 0, + "hd": false, + "mn": "ADBE Vector Shape - Rect", + "nm": "Rectangle Path 1", + "d": 1, + "p": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": 2, + "ix": 4 + }, + "s": { + "a": 0, + "k": [ + 14.893, + 21.297 + ], + "ix": 2 + } + }, + { + "ty": "fl", + "bm": 0, + "hd": false, + "mn": "ADBE Vector Graphic - Fill", + "nm": "Fill 1", + "c": { + "a": 0, + "k": [ + 1, + 1, + 1 + ], + "ix": 4 + }, + "r": 1, + "o": { + "a": 0, + "k": 100, + "ix": 5 + } + }, + { + "ty": "tr", + "a": { + "a": 0, + "k": [ + -0.091, + -10.492 + ], + "ix": 1 + }, + "s": { + "a": 1, + "k": [ + { + "o": { + "x": 0.167, + "y": 0 + }, + "i": { + "x": 0.26, + "y": 1 + }, + "s": [ + 76.746, + 4.265 + ], + "t": 21 + }, + { + "s": [ + 76.746, + 144.392 + ], + "t": 29 + } + ], + "ix": 3 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 4 + }, + "p": { + "a": 0, + "k": [ + -9.58, + -12.937 + ], + "ix": 2 + }, + "r": { + "a": 0, + "k": -45, + "ix": 6 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 5 + }, + "o": { + "a": 0, + "k": 100, + "ix": 7 + } + } + ] + } + ], + "ind": 3, + "parent": 4 + }, + { + "ty": 4, + "nm": "big cir", + "sr": 1, + "st": 0, + "op": 151, + "ip": 10, + "hd": false, + "ddd": 0, + "bm": 0, + "hasMask": false, + "ao": 0, + "ks": { + "a": { + "a": 0, + "k": [ + -14.253, + -9.753, + 0 + ], + "ix": 1 + }, + "s": { + "a": 1, + "k": [ + { + "o": { + "x": 0.167, + "y": 0.167 + }, + "i": { + "x": 0.833, + "y": 0.833 + }, + "s": [ + 100, + 100, + 100 + ], + "t": 15 + }, + { + "s": [ + 670.93, + 670.93, + 100 + ], + "t": 21 + } + ], + "ix": 6, + "x": "var $bm_rt;\nvar n, n, t, t, v, amp, freq, decay;\n$bm_rt = n = 0;\nif (numKeys > 0) {\n $bm_rt = n = nearestKey(time).index;\n if (key(n).time > time) {\n n--;\n }\n}\nif (n == 0) {\n $bm_rt = t = 0;\n} else {\n $bm_rt = t = $bm_sub(time, key(n).time);\n}\nif (n > 0) {\n v = velocityAtTime($bm_sub(key(n).time, $bm_div(thisComp.frameDuration, 10)));\n amp = 0.1;\n freq = 2;\n decay = 4;\n $bm_rt = $bm_sum(value, $bm_div($bm_mul($bm_mul(v, amp), Math.sin($bm_mul($bm_mul($bm_mul(freq, t), 2), Math.PI))), Math.exp($bm_mul(decay, t))));\n} else {\n $bm_rt = value;\n}" + }, + "sk": { + "a": 0, + "k": 0 + }, + "p": { + "s": true, + "x": { + "a": 0, + "k": 55.969, + "ix": 3 + }, + "y": { + "a": 0, + "k": 56, + "ix": 4, + "x": "var $bm_rt;\nvar n, n, t, t, v, amp, freq, decay;\n$bm_rt = n = 0;\nif (numKeys > 0) {\n $bm_rt = n = nearestKey(time).index;\n if (key(n).time > time) {\n n--;\n }\n}\nif (n == 0) {\n $bm_rt = t = 0;\n} else {\n $bm_rt = t = $bm_sub(time, key(n).time);\n}\nif (n > 0) {\n v = velocityAtTime($bm_sub(key(n).time, $bm_div(thisComp.frameDuration, 10)));\n amp = 0.5;\n freq = 2;\n decay = 4;\n $bm_rt = $bm_sum(value, $bm_div($bm_mul($bm_mul(v, amp), Math.sin($bm_mul($bm_mul($bm_mul(freq, t), 2), Math.PI))), Math.exp($bm_mul(decay, t))));\n} else {\n $bm_rt = value;\n}" + }, + "z": { + "a": 0, + "k": 0 + } + }, + "r": { + "a": 1, + "k": [ + { + "o": { + "x": 0.167, + "y": 0.167 + }, + "i": { + "x": 0.833, + "y": 0.833 + }, + "s": [ + -72 + ], + "t": 23 + }, + { + "s": [ + 0 + ], + "t": 39 + } + ], + "ix": 10, + "x": "var $bm_rt;\nvar n, n, t, t, v, amp, freq, decay;\n$bm_rt = n = 0;\nif (numKeys > 0) {\n $bm_rt = n = nearestKey(time).index;\n if (key(n).time > time) {\n n--;\n }\n}\nif (n == 0) {\n $bm_rt = t = 0;\n} else {\n $bm_rt = t = $bm_sub(time, key(n).time);\n}\nif (n > 0) {\n v = velocityAtTime($bm_sub(key(n).time, $bm_div(thisComp.frameDuration, 10)));\n amp = 0.2;\n freq = 2;\n decay = 4;\n $bm_rt = $bm_sum(value, $bm_div($bm_mul($bm_mul(v, amp), Math.sin($bm_mul($bm_mul($bm_mul(freq, t), 2), Math.PI))), Math.exp($bm_mul(decay, t))));\n} else {\n $bm_rt = value;\n}" + }, + "sa": { + "a": 0, + "k": 0 + }, + "o": { + "a": 0, + "k": 100, + "ix": 11 + } + }, + "ef": [], + "shapes": [ + { + "ty": "gr", + "bm": 0, + "hd": false, + "mn": "ADBE Vector Group", + "nm": "Ellipse 1", + "ix": 1, + "cix": 2, + "np": 3, + "it": [ + { + "ty": "el", + "bm": 0, + "hd": false, + "mn": "ADBE Vector Shape - Ellipse", + "nm": "Ellipse Path 1", + "d": 1, + "p": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 3 + }, + "s": { + "a": 0, + "k": [ + 4.994, + 4.994 + ], + "ix": 2 + } + }, + { + "ty": "fl", + "bm": 0, + "hd": false, + "mn": "ADBE Vector Graphic - Fill", + "nm": "Fill 1", + "c": { + "a": 0, + "k": [ + 0.9373, + 0.3569, + 0.4941 + ], + "ix": 4 + }, + "r": 1, + "o": { + "a": 0, + "k": 100, + "ix": 5 + } + }, + { + "ty": "tr", + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 192.894, + 192.894 + ], + "ix": 3 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 4 + }, + "p": { + "a": 0, + "k": [ + -14.253, + -9.753 + ], + "ix": 2 + }, + "r": { + "a": 0, + "k": 0, + "ix": 6 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 5 + }, + "o": { + "a": 0, + "k": 100, + "ix": 7 + } + } + ] + } + ], + "ind": 4 + }, + { + "ty": 4, + "nm": "Shape Layer 3", + "sr": 1, + "st": 8, + "op": 11, + "ip": 0, + "hd": false, + "ddd": 0, + "bm": 0, + "hasMask": false, + "ao": 0, + "ks": { + "a": { + "a": 0, + "k": [ + -14.253, + -9.753, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100, + 100 + ], + "ix": 6 + }, + "sk": { + "a": 0, + "k": 0 + }, + "p": { + "s": true, + "x": { + "a": 1, + "k": [ + { + "o": { + "x": 0.41, + "y": 0 + }, + "i": { + "x": 0.261, + "y": 1 + }, + "s": [ + 72.969 + ], + "t": 0 + }, + { + "s": [ + 55.969 + ], + "t": 10 + } + ], + "ix": 3 + }, + "y": { + "a": 1, + "k": [ + { + "o": { + "x": 0.167, + "y": 0.167 + }, + "i": { + "x": 0.833, + "y": 0.833 + }, + "s": [ + 56 + ], + "t": 0 + }, + { + "s": [ + 56 + ], + "t": 10 + } + ], + "ix": 4 + }, + "z": { + "a": 0, + "k": 0 + } + }, + "r": { + "a": 0, + "k": 0, + "ix": 10 + }, + "sa": { + "a": 0, + "k": 0 + }, + "o": { + "a": 0, + "k": 100, + "ix": 11 + } + }, + "ef": [], + "shapes": [ + { + "ty": "gr", + "bm": 0, + "hd": false, + "mn": "ADBE Vector Group", + "nm": "Ellipse 1", + "ix": 1, + "cix": 2, + "np": 3, + "it": [ + { + "ty": "el", + "bm": 0, + "hd": false, + "mn": "ADBE Vector Shape - Ellipse", + "nm": "Ellipse Path 1", + "d": 1, + "p": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 3 + }, + "s": { + "a": 0, + "k": [ + 4.994, + 4.994 + ], + "ix": 2 + } + }, + { + "ty": "fl", + "bm": 0, + "hd": false, + "mn": "ADBE Vector Graphic - Fill", + "nm": "Fill 1", + "c": { + "a": 0, + "k": [ + 0.9373, + 0.3569, + 0.4941 + ], + "ix": 4 + }, + "r": 1, + "o": { + "a": 0, + "k": 100, + "ix": 5 + } + }, + { + "ty": "tr", + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 192.894, + 192.894 + ], + "ix": 3 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 4 + }, + "p": { + "a": 0, + "k": [ + -14.253, + -9.753 + ], + "ix": 2 + }, + "r": { + "a": 0, + "k": 0, + "ix": 6 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 5 + }, + "o": { + "a": 0, + "k": 100, + "ix": 7 + } + } + ] + } + ], + "ind": 5 + }, + { + "ty": 4, + "nm": "Shape Layer 2", + "sr": 1, + "st": 6, + "op": 17, + "ip": 0, + "hd": false, + "ddd": 0, + "bm": 0, + "hasMask": false, + "ao": 0, + "ks": { + "a": { + "a": 0, + "k": [ + -14.253, + -9.753, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100, + 100 + ], + "ix": 6 + }, + "sk": { + "a": 0, + "k": 0 + }, + "p": { + "s": true, + "x": { + "a": 1, + "k": [ + { + "o": { + "x": 0.333, + "y": 0 + }, + "i": { + "x": 0.168, + "y": 1 + }, + "s": [ + 39.469 + ], + "t": 6 + }, + { + "s": [ + 55.969 + ], + "t": 16 + } + ], + "ix": 3 + }, + "y": { + "a": 1, + "k": [ + { + "o": { + "x": 0.167, + "y": 0.167 + }, + "i": { + "x": 0.833, + "y": 0.833 + }, + "s": [ + 56 + ], + "t": 6 + }, + { + "s": [ + 56 + ], + "t": 16 + } + ], + "ix": 4 + }, + "z": { + "a": 0, + "k": 0 + } + }, + "r": { + "a": 0, + "k": 0, + "ix": 10 + }, + "sa": { + "a": 0, + "k": 0 + }, + "o": { + "a": 1, + "k": [ + { + "o": { + "x": 0.167, + "y": 0.167 + }, + "i": { + "x": 0.833, + "y": 0.833 + }, + "s": [ + 100 + ], + "t": 6 + }, + { + "s": [ + 0 + ], + "t": 16 + } + ], + "ix": 11 + } + }, + "ef": [], + "shapes": [ + { + "ty": "gr", + "bm": 0, + "hd": false, + "mn": "ADBE Vector Group", + "nm": "Ellipse 1", + "ix": 1, + "cix": 2, + "np": 3, + "it": [ + { + "ty": "el", + "bm": 0, + "hd": false, + "mn": "ADBE Vector Shape - Ellipse", + "nm": "Ellipse Path 1", + "d": 1, + "p": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 3 + }, + "s": { + "a": 0, + "k": [ + 4.994, + 4.994 + ], + "ix": 2 + } + }, + { + "ty": "fl", + "bm": 0, + "hd": false, + "mn": "ADBE Vector Graphic - Fill", + "nm": "Fill 1", + "c": { + "a": 0, + "k": [ + 1, + 0.8667, + 0.1882 + ], + "ix": 4 + }, + "r": 1, + "o": { + "a": 0, + "k": 100, + "ix": 5 + } + }, + { + "ty": "tr", + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 192.894, + 192.894 + ], + "ix": 3 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 4 + }, + "p": { + "a": 0, + "k": [ + -14.253, + -9.753 + ], + "ix": 2 + }, + "r": { + "a": 0, + "k": 0, + "ix": 6 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 5 + }, + "o": { + "a": 0, + "k": 100, + "ix": 7 + } + } + ] + } + ], + "ind": 6 + }, + { + "ty": 4, + "nm": "Shape Layer 1", + "sr": 1, + "st": 4, + "op": 11, + "ip": 0, + "hd": false, + "ddd": 0, + "bm": 0, + "hasMask": false, + "ao": 0, + "ks": { + "a": { + "a": 0, + "k": [ + -14.253, + -9.753, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100, + 100 + ], + "ix": 6 + }, + "sk": { + "a": 0, + "k": 0 + }, + "p": { + "a": 0, + "k": [ + 55.969, + 56, + 0 + ], + "ix": 2 + }, + "r": { + "a": 0, + "k": 0, + "ix": 10 + }, + "sa": { + "a": 0, + "k": 0 + }, + "o": { + "a": 1, + "k": [ + { + "o": { + "x": 0.167, + "y": 0.167 + }, + "i": { + "x": 0.833, + "y": 0.833 + }, + "s": [ + 100 + ], + "t": 0 + }, + { + "s": [ + 0 + ], + "t": 10 + } + ], + "ix": 11 + } + }, + "ef": [], + "shapes": [ + { + "ty": "gr", + "bm": 0, + "hd": false, + "mn": "ADBE Vector Group", + "nm": "Ellipse 1", + "ix": 1, + "cix": 2, + "np": 3, + "it": [ + { + "ty": "el", + "bm": 0, + "hd": false, + "mn": "ADBE Vector Shape - Ellipse", + "nm": "Ellipse Path 1", + "d": 1, + "p": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 3 + }, + "s": { + "a": 0, + "k": [ + 4.994, + 4.994 + ], + "ix": 2 + } + }, + { + "ty": "fl", + "bm": 0, + "hd": false, + "mn": "ADBE Vector Graphic - Fill", + "nm": "Fill 1", + "c": { + "a": 0, + "k": [ + 0, + 0.7529, + 0.4824 + ], + "ix": 4 + }, + "r": 1, + "o": { + "a": 0, + "k": 100, + "ix": 5 + } + }, + { + "ty": "tr", + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 192.894, + 192.894 + ], + "ix": 3 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 4 + }, + "p": { + "a": 0, + "k": [ + -14.253, + -9.753 + ], + "ix": 2 + }, + "r": { + "a": 0, + "k": 0, + "ix": 6 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 5 + }, + "o": { + "a": 0, + "k": 100, + "ix": 7 + } + } + ] + } + ], + "ind": 7 + } + ], + "v": "5.5.5", + "fr": 30, + "op": 91, + "ip": 0, + "assets": [ + { + "nm": "", + "id": "comp_0", + "layers": [ + { + "ty": 4, + "nm": "Shape Layer 13", + "sr": 1, + "st": 0, + "op": 150, + "ip": 0, + "hd": false, + "ddd": 0, + "bm": 0, + "hasMask": false, + "ao": 0, + "ks": { + "a": { + "a": 0, + "k": [ + 0, + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100, + 100 + ], + "ix": 6 + }, + "sk": { + "a": 0, + "k": 0 + }, + "p": { + "a": 0, + "k": [ + 46, + 56, + 0 + ], + "ix": 2 + }, + "r": { + "a": 0, + "k": -45, + "ix": 10 + }, + "sa": { + "a": 0, + "k": 0 + }, + "o": { + "a": 0, + "k": 100, + "ix": 11 + } + }, + "ef": [], + "shapes": [ + { + "ty": "gr", + "bm": 0, + "hd": false, + "mn": "ADBE Vector Group", + "nm": "Shape 1", + "ix": 1, + "cix": 2, + "np": 3, + "it": [ + { + "ty": "sh", + "bm": 0, + "hd": false, + "mn": "ADBE Vector Shape - Group", + "nm": "Path 1", + "ix": 1, + "d": 1, + "ks": { + "a": 0, + "k": { + "c": false, + "i": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "o": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "v": [ + [ + 0, + -41.5 + ], + [ + 0, + -20.625 + ] + ] + }, + "ix": 2 + } + }, + { + "ty": "st", + "bm": 0, + "hd": false, + "mn": "ADBE Vector Graphic - Stroke", + "nm": "Stroke 1", + "lc": 2, + "lj": 1, + "ml": 4, + "o": { + "a": 0, + "k": 100, + "ix": 4 + }, + "w": { + "a": 0, + "k": 10, + "ix": 5 + }, + "c": { + "a": 0, + "k": [ + 0.9373, + 0.3569, + 0.4941 + ], + "ix": 3 + } + }, + { + "ty": "tr", + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100 + ], + "ix": 3 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 4 + }, + "p": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 2 + }, + "r": { + "a": 0, + "k": 0, + "ix": 6 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 5 + }, + "o": { + "a": 0, + "k": 100, + "ix": 7 + } + } + ] + }, + { + "ty": "tm", + "bm": 0, + "hd": false, + "mn": "ADBE Vector Filter - Trim", + "nm": "Trim Paths 1", + "ix": 2, + "e": { + "a": 1, + "k": [ + { + "o": { + "x": 0.167, + "y": 0.167 + }, + "i": { + "x": 0.833, + "y": 0.833 + }, + "s": [ + 100 + ], + "t": 0 + }, + { + "o": { + "x": 0.167, + "y": 0.167 + }, + "i": { + "x": 0.833, + "y": 0.833 + }, + "s": [ + 100 + ], + "t": 7.5 + }, + { + "s": [ + 0 + ], + "t": 15 + } + ], + "ix": 2 + }, + "o": { + "a": 0, + "k": 0, + "ix": 3 + }, + "s": { + "a": 1, + "k": [ + { + "o": { + "x": 0.167, + "y": 0.167 + }, + "i": { + "x": 0.833, + "y": 0.833 + }, + "s": [ + 100 + ], + "t": 0 + }, + { + "o": { + "x": 0.167, + "y": 0.167 + }, + "i": { + "x": 0.833, + "y": 0.833 + }, + "s": [ + 0 + ], + "t": 7.5 + }, + { + "s": [ + 0 + ], + "t": 15 + } + ], + "ix": 1 + }, + "m": 1 + } + ], + "ind": 1 + }, + { + "ty": 4, + "nm": "Shape Layer 12", + "sr": 1, + "st": 0, + "op": 150, + "ip": 0, + "hd": false, + "ddd": 0, + "bm": 0, + "hasMask": false, + "ao": 0, + "ks": { + "a": { + "a": 0, + "k": [ + 0, + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100, + 100 + ], + "ix": 6 + }, + "sk": { + "a": 0, + "k": 0 + }, + "p": { + "a": 0, + "k": [ + 63.5, + 56, + 0 + ], + "ix": 2 + }, + "r": { + "a": 0, + "k": 45, + "ix": 10 + }, + "sa": { + "a": 0, + "k": 0 + }, + "o": { + "a": 0, + "k": 100, + "ix": 11 + } + }, + "ef": [], + "shapes": [ + { + "ty": "gr", + "bm": 0, + "hd": false, + "mn": "ADBE Vector Group", + "nm": "Shape 1", + "ix": 1, + "cix": 2, + "np": 3, + "it": [ + { + "ty": "sh", + "bm": 0, + "hd": false, + "mn": "ADBE Vector Shape - Group", + "nm": "Path 1", + "ix": 1, + "d": 1, + "ks": { + "a": 0, + "k": { + "c": false, + "i": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "o": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "v": [ + [ + 0, + -41.5 + ], + [ + 0, + -20.625 + ] + ] + }, + "ix": 2 + } + }, + { + "ty": "st", + "bm": 0, + "hd": false, + "mn": "ADBE Vector Graphic - Stroke", + "nm": "Stroke 1", + "lc": 2, + "lj": 1, + "ml": 4, + "o": { + "a": 0, + "k": 100, + "ix": 4 + }, + "w": { + "a": 0, + "k": 10, + "ix": 5 + }, + "c": { + "a": 0, + "k": [ + 0.9373, + 0.3569, + 0.4941 + ], + "ix": 3 + } + }, + { + "ty": "tr", + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100 + ], + "ix": 3 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 4 + }, + "p": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 2 + }, + "r": { + "a": 0, + "k": 0, + "ix": 6 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 5 + }, + "o": { + "a": 0, + "k": 100, + "ix": 7 + } + } + ] + }, + { + "ty": "tm", + "bm": 0, + "hd": false, + "mn": "ADBE Vector Filter - Trim", + "nm": "Trim Paths 1", + "ix": 2, + "e": { + "a": 1, + "k": [ + { + "o": { + "x": 0.167, + "y": 0.167 + }, + "i": { + "x": 0.833, + "y": 0.833 + }, + "s": [ + 100 + ], + "t": 0 + }, + { + "o": { + "x": 0.167, + "y": 0.167 + }, + "i": { + "x": 0.833, + "y": 0.833 + }, + "s": [ + 100 + ], + "t": 7.5 + }, + { + "s": [ + 0 + ], + "t": 15 + } + ], + "ix": 2 + }, + "o": { + "a": 0, + "k": 0, + "ix": 3 + }, + "s": { + "a": 1, + "k": [ + { + "o": { + "x": 0.167, + "y": 0.167 + }, + "i": { + "x": 0.833, + "y": 0.833 + }, + "s": [ + 100 + ], + "t": 0 + }, + { + "o": { + "x": 0.167, + "y": 0.167 + }, + "i": { + "x": 0.833, + "y": 0.833 + }, + "s": [ + 0 + ], + "t": 7.5 + }, + { + "s": [ + 0 + ], + "t": 15 + } + ], + "ix": 1 + }, + "m": 1 + } + ], + "ind": 2 + }, + { + "ty": 4, + "nm": "Shape Layer 11", + "sr": 1, + "st": 0, + "op": 150, + "ip": 0, + "hd": false, + "ddd": 0, + "bm": 0, + "hasMask": false, + "ao": 0, + "ks": { + "a": { + "a": 0, + "k": [ + 0, + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100, + 100 + ], + "ix": 6 + }, + "sk": { + "a": 0, + "k": 0 + }, + "p": { + "a": 0, + "k": [ + 56, + 56, + 0 + ], + "ix": 2 + }, + "r": { + "a": 0, + "k": 0, + "ix": 10 + }, + "sa": { + "a": 0, + "k": 0 + }, + "o": { + "a": 0, + "k": 100, + "ix": 11 + } + }, + "ef": [], + "shapes": [ + { + "ty": "gr", + "bm": 0, + "hd": false, + "mn": "ADBE Vector Group", + "nm": "Shape 1", + "ix": 1, + "cix": 2, + "np": 3, + "it": [ + { + "ty": "sh", + "bm": 0, + "hd": false, + "mn": "ADBE Vector Shape - Group", + "nm": "Path 1", + "ix": 1, + "d": 1, + "ks": { + "a": 0, + "k": { + "c": false, + "i": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "o": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "v": [ + [ + 0, + -41.5 + ], + [ + 0, + -20.625 + ] + ] + }, + "ix": 2 + } + }, + { + "ty": "st", + "bm": 0, + "hd": false, + "mn": "ADBE Vector Graphic - Stroke", + "nm": "Stroke 1", + "lc": 2, + "lj": 1, + "ml": 4, + "o": { + "a": 0, + "k": 100, + "ix": 4 + }, + "w": { + "a": 0, + "k": 10, + "ix": 5 + }, + "c": { + "a": 0, + "k": [ + 0.9373, + 0.3569, + 0.4941 + ], + "ix": 3 + } + }, + { + "ty": "tr", + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100 + ], + "ix": 3 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 4 + }, + "p": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 2 + }, + "r": { + "a": 0, + "k": 0, + "ix": 6 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 5 + }, + "o": { + "a": 0, + "k": 100, + "ix": 7 + } + } + ] + }, + { + "ty": "tm", + "bm": 0, + "hd": false, + "mn": "ADBE Vector Filter - Trim", + "nm": "Trim Paths 1", + "ix": 2, + "e": { + "a": 1, + "k": [ + { + "o": { + "x": 0.167, + "y": 0.167 + }, + "i": { + "x": 0.833, + "y": 0.833 + }, + "s": [ + 100 + ], + "t": 0 + }, + { + "o": { + "x": 0.167, + "y": 0.167 + }, + "i": { + "x": 0.833, + "y": 0.833 + }, + "s": [ + 100 + ], + "t": 7.5 + }, + { + "s": [ + 0 + ], + "t": 15 + } + ], + "ix": 2 + }, + "o": { + "a": 0, + "k": 0, + "ix": 3 + }, + "s": { + "a": 1, + "k": [ + { + "o": { + "x": 0.167, + "y": 0.167 + }, + "i": { + "x": 0.833, + "y": 0.833 + }, + "s": [ + 100 + ], + "t": 0 + }, + { + "o": { + "x": 0.167, + "y": 0.167 + }, + "i": { + "x": 0.833, + "y": 0.833 + }, + "s": [ + 0 + ], + "t": 7.5 + }, + { + "s": [ + 0 + ], + "t": 15 + } + ], + "ix": 1 + }, + "m": 1 + } + ], + "ind": 3 + } + ] + } + ] +} \ No newline at end of file diff --git a/app/src/main/res/raw/epub_import_success_lottie.json b/app/src/main/res/raw/epub_import_success_lottie.json new file mode 100644 index 00000000..fe831215 --- /dev/null +++ b/app/src/main/res/raw/epub_import_success_lottie.json @@ -0,0 +1,2298 @@ +{ + "v": "5.5.5", + "fr": 30, + "ip": 0, + "op": 90, + "w": 112, + "h": 112, + "nm": "success", + "ddd": 0, + "assets": [ + { + "id": "comp_0", + "layers": [ + { + "ddd": 0, + "ind": 1, + "ty": 4, + "nm": "Shape Layer 13", + "sr": 1, + "ks": { + "o": { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": { + "a": 0, + "k": -45, + "ix": 10 + }, + "p": { + "a": 0, + "k": [ + 46, + 56, + 0 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100, + 100 + ], + "ix": 6 + } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "ind": 0, + "ty": "sh", + "ix": 1, + "ks": { + "a": 0, + "k": { + "i": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "o": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "v": [ + [ + 0, + -41.5 + ], + [ + 0, + -20.625 + ] + ], + "c": false + }, + "ix": 2 + }, + "nm": "Path 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "st", + "c": { + "a": 0, + "k": [ + 0, + 0.752941176471, + 0.482352941176, + 1 + ], + "ix": 3 + }, + "o": { + "a": 0, + "k": 100, + "ix": 4 + }, + "w": { + "a": 0, + "k": 10, + "ix": 5 + }, + "lc": 2, + "lj": 1, + "ml": 4, + "bm": 0, + "nm": "Stroke 1", + "mn": "ADBE Vector Graphic - Stroke", + "hd": false + }, + { + "ty": "tr", + "p": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100 + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": 0, + "ix": 6 + }, + "o": { + "a": 0, + "k": 100, + "ix": 7 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 4 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 5 + }, + "nm": "Transform" + } + ], + "nm": "Shape 1", + "np": 3, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + }, + { + "ty": "tm", + "s": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 0, + "s": [ + 100 + ] + }, + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 7.5, + "s": [ + 0 + ] + }, + { + "t": 15, + "s": [ + 0 + ] + } + ], + "ix": 1 + }, + "e": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 0, + "s": [ + 100 + ] + }, + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 7.5, + "s": [ + 100 + ] + }, + { + "t": 15, + "s": [ + 0 + ] + } + ], + "ix": 2 + }, + "o": { + "a": 0, + "k": 0, + "ix": 3 + }, + "m": 1, + "ix": 2, + "nm": "Trim Paths 1", + "mn": "ADBE Vector Filter - Trim", + "hd": false + } + ], + "ip": 0, + "op": 150, + "st": 0, + "bm": 0 + }, + { + "ddd": 0, + "ind": 2, + "ty": 4, + "nm": "Shape Layer 12", + "sr": 1, + "ks": { + "o": { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": { + "a": 0, + "k": 45, + "ix": 10 + }, + "p": { + "a": 0, + "k": [ + 63.5, + 56, + 0 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100, + 100 + ], + "ix": 6 + } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "ind": 0, + "ty": "sh", + "ix": 1, + "ks": { + "a": 0, + "k": { + "i": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "o": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "v": [ + [ + 0, + -41.5 + ], + [ + 0, + -20.625 + ] + ], + "c": false + }, + "ix": 2 + }, + "nm": "Path 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "st", + "c": { + "a": 0, + "k": [ + 0, + 0.752941176471, + 0.482352941176, + 1 + ], + "ix": 3 + }, + "o": { + "a": 0, + "k": 100, + "ix": 4 + }, + "w": { + "a": 0, + "k": 10, + "ix": 5 + }, + "lc": 2, + "lj": 1, + "ml": 4, + "bm": 0, + "nm": "Stroke 1", + "mn": "ADBE Vector Graphic - Stroke", + "hd": false + }, + { + "ty": "tr", + "p": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100 + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": 0, + "ix": 6 + }, + "o": { + "a": 0, + "k": 100, + "ix": 7 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 4 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 5 + }, + "nm": "Transform" + } + ], + "nm": "Shape 1", + "np": 3, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + }, + { + "ty": "tm", + "s": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 0, + "s": [ + 100 + ] + }, + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 7.5, + "s": [ + 0 + ] + }, + { + "t": 15, + "s": [ + 0 + ] + } + ], + "ix": 1 + }, + "e": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 0, + "s": [ + 100 + ] + }, + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 7.5, + "s": [ + 100 + ] + }, + { + "t": 15, + "s": [ + 0 + ] + } + ], + "ix": 2 + }, + "o": { + "a": 0, + "k": 0, + "ix": 3 + }, + "m": 1, + "ix": 2, + "nm": "Trim Paths 1", + "mn": "ADBE Vector Filter - Trim", + "hd": false + } + ], + "ip": 0, + "op": 150, + "st": 0, + "bm": 0 + }, + { + "ddd": 0, + "ind": 3, + "ty": 4, + "nm": "Shape Layer 11", + "sr": 1, + "ks": { + "o": { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": { + "a": 0, + "k": [ + 56, + 56, + 0 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100, + 100 + ], + "ix": 6 + } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "ind": 0, + "ty": "sh", + "ix": 1, + "ks": { + "a": 0, + "k": { + "i": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "o": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "v": [ + [ + 0, + -41.5 + ], + [ + 0, + -20.625 + ] + ], + "c": false + }, + "ix": 2 + }, + "nm": "Path 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "st", + "c": { + "a": 0, + "k": [ + 0, + 0.752941176471, + 0.482352941176, + 1 + ], + "ix": 3 + }, + "o": { + "a": 0, + "k": 100, + "ix": 4 + }, + "w": { + "a": 0, + "k": 10, + "ix": 5 + }, + "lc": 2, + "lj": 1, + "ml": 4, + "bm": 0, + "nm": "Stroke 1", + "mn": "ADBE Vector Graphic - Stroke", + "hd": false + }, + { + "ty": "tr", + "p": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100 + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": 0, + "ix": 6 + }, + "o": { + "a": 0, + "k": 100, + "ix": 7 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 4 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 5 + }, + "nm": "Transform" + } + ], + "nm": "Shape 1", + "np": 3, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + }, + { + "ty": "tm", + "s": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 0, + "s": [ + 100 + ] + }, + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 7.5, + "s": [ + 0 + ] + }, + { + "t": 15, + "s": [ + 0 + ] + } + ], + "ix": 1 + }, + "e": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 0, + "s": [ + 100 + ] + }, + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 7.5, + "s": [ + 100 + ] + }, + { + "t": 15, + "s": [ + 0 + ] + } + ], + "ix": 2 + }, + "o": { + "a": 0, + "k": 0, + "ix": 3 + }, + "m": 1, + "ix": 2, + "nm": "Trim Paths 1", + "mn": "ADBE Vector Filter - Trim", + "hd": false + } + ], + "ip": 0, + "op": 150, + "st": 0, + "bm": 0 + } + ] + } + ], + "layers": [ + { + "ddd": 0, + "ind": 1, + "ty": 0, + "nm": "flick", + "refId": "comp_0", + "sr": 1, + "ks": { + "o": { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": { + "a": 0, + "k": 45, + "ix": 10 + }, + "p": { + "a": 0, + "k": [ + 84.5, + 31, + 0 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 56, + 56, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 27.272, + 27.272, + 100 + ], + "ix": 6 + } + }, + "ao": 0, + "w": 112, + "h": 112, + "ip": 31, + "op": 181, + "st": 31, + "bm": 0 + }, + { + "ddd": 0, + "ind": 2, + "ty": 4, + "nm": "Shape Layer 10", + "parent": 9, + "sr": 1, + "ks": { + "o": { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": { + "a": 0, + "k": -89.887, + "ix": 10 + }, + "p": { + "a": 0, + "k": [ + -15.397, + -7.665, + 0 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + -9.678, + -12.102, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 14.905, + 14.905, + 100 + ], + "ix": 6 + } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "ty": "rc", + "d": 1, + "s": { + "a": 0, + "k": [ + 14.893, + 21.297 + ], + "ix": 2 + }, + "p": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": 2, + "ix": 4 + }, + "nm": "Rectangle Path 1", + "mn": "ADBE Vector Shape - Rect", + "hd": false + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [ + 1, + 1, + 1, + 1 + ], + "ix": 4 + }, + "o": { + "a": 0, + "k": 100, + "ix": 5 + }, + "r": 1, + "bm": 0, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill", + "hd": false + }, + { + "ty": "tr", + "p": { + "a": 0, + "k": [ + -9.832, + -12.103 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + -0.082, + -10.641 + ], + "ix": 1 + }, + "s": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.057, + 0.057 + ], + "y": [ + 1, + 1 + ] + }, + "o": { + "x": [ + 0.333, + 0.333 + ], + "y": [ + 0, + 0 + ] + }, + "t": 25, + "s": [ + 92.606, + 0.914 + ] + }, + { + "t": 33, + "s": [ + 92.606, + 167.707 + ] + } + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": -45, + "ix": 6 + }, + "o": { + "a": 0, + "k": 100, + "ix": 7 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 4 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 5 + }, + "nm": "Transform" + } + ], + "nm": "Rectangle 1", + "np": 3, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 25, + "op": 132, + "st": -18, + "bm": 0 + }, + { + "ddd": 0, + "ind": 3, + "ty": 4, + "nm": "Shape Layer 9", + "parent": 9, + "sr": 1, + "ks": { + "o": { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": { + "a": 0, + "k": 0.113, + "ix": 10 + }, + "p": { + "a": 0, + "k": [ + -16.64, + -10.313, + 0 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + -9.678, + -12.102, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 14.905, + 14.905, + 100 + ], + "ix": 6 + } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "ty": "rc", + "d": 1, + "s": { + "a": 0, + "k": [ + 14.893, + 21.297 + ], + "ix": 2 + }, + "p": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": 2, + "ix": 4 + }, + "nm": "Rectangle Path 1", + "mn": "ADBE Vector Shape - Rect", + "hd": false + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [ + 1, + 1, + 1, + 1 + ], + "ix": 4 + }, + "o": { + "a": 0, + "k": 100, + "ix": 5 + }, + "r": 1, + "bm": 0, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill", + "hd": false + }, + { + "ty": "tr", + "p": { + "a": 0, + "k": [ + -9.559, + -11.867 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + -0.091, + -10.492 + ], + "ix": 1 + }, + "s": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.26, + 0.26 + ], + "y": [ + 1, + 1 + ] + }, + "o": { + "x": [ + 0.708, + 0.708 + ], + "y": [ + 0, + 0 + ] + }, + "t": 17, + "s": [ + 92.606, + 3.438 + ] + }, + { + "t": 25, + "s": [ + 92.606, + 117.488 + ] + } + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": -45, + "ix": 6 + }, + "o": { + "a": 0, + "k": 100, + "ix": 7 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 4 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 5 + }, + "nm": "Transform" + } + ], + "nm": "Rectangle 1", + "np": 3, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 17, + "op": 128, + "st": -22, + "bm": 0 + }, + { + "ddd": 0, + "ind": 9, + "ty": 4, + "nm": "big cir", + "sr": 1, + "ks": { + "o": { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 13, + "s": [ + -72 + ] + }, + { + "t": 29, + "s": [ + 0 + ] + } + ], + "ix": 10, + "x": "var $bm_rt;\nvar n, n, t, t, v, amp, freq, decay;\n$bm_rt = n = 0;\nif (numKeys > 0) {\n $bm_rt = n = nearestKey(time).index;\n if (key(n).time > time) {\n n--;\n }\n}\nif (n == 0) {\n $bm_rt = t = 0;\n} else {\n $bm_rt = t = $bm_sub(time, key(n).time);\n}\nif (n > 0) {\n v = velocityAtTime($bm_sub(key(n).time, $bm_div(thisComp.frameDuration, 10)));\n amp = 0.2;\n freq = 2;\n decay = 4;\n $bm_rt = $bm_sum(value, $bm_div($bm_mul($bm_mul(v, amp), Math.sin($bm_mul($bm_mul($bm_mul(freq, t), 2), Math.PI))), Math.exp($bm_mul(decay, t))));\n} else {\n $bm_rt = value;\n}" + }, + "p": { + "s": true, + "x": { + "a": 0, + "k": 55.969, + "ix": 3 + }, + "y": { + "a": 0, + "k": 56, + "ix": 4, + "x": "var $bm_rt;\nvar n, n, t, t, v, amp, freq, decay;\n$bm_rt = n = 0;\nif (numKeys > 0) {\n $bm_rt = n = nearestKey(time).index;\n if (key(n).time > time) {\n n--;\n }\n}\nif (n == 0) {\n $bm_rt = t = 0;\n} else {\n $bm_rt = t = $bm_sub(time, key(n).time);\n}\nif (n > 0) {\n v = velocityAtTime($bm_sub(key(n).time, $bm_div(thisComp.frameDuration, 10)));\n amp = 0.5;\n freq = 2;\n decay = 4;\n $bm_rt = $bm_sum(value, $bm_div($bm_mul($bm_mul(v, amp), Math.sin($bm_mul($bm_mul($bm_mul(freq, t), 2), Math.PI))), Math.exp($bm_mul(decay, t))));\n} else {\n $bm_rt = value;\n}" + } + }, + "a": { + "a": 0, + "k": [ + -14.253, + -9.753, + 0 + ], + "ix": 1 + }, + "s": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.833, + 0.833, + 0.833 + ], + "y": [ + 0.833, + 0.833, + 0.833 + ] + }, + "o": { + "x": [ + 0.167, + 0.167, + 0.167 + ], + "y": [ + 0.167, + 0.167, + 0.167 + ] + }, + "t": 10, + "s": [ + 100, + 100, + 100 + ] + }, + { + "t": 17, + "s": [ + 670.93, + 670.93, + 100 + ] + } + ], + "ix": 6, + "x": "var $bm_rt;\nvar n, n, t, t, v, amp, freq, decay;\n$bm_rt = n = 0;\nif (numKeys > 0) {\n $bm_rt = n = nearestKey(time).index;\n if (key(n).time > time) {\n n--;\n }\n}\nif (n == 0) {\n $bm_rt = t = 0;\n} else {\n $bm_rt = t = $bm_sub(time, key(n).time);\n}\nif (n > 0) {\n v = velocityAtTime($bm_sub(key(n).time, $bm_div(thisComp.frameDuration, 10)));\n amp = 0.1;\n freq = 2;\n decay = 4;\n $bm_rt = $bm_sum(value, $bm_div($bm_mul($bm_mul(v, amp), Math.sin($bm_mul($bm_mul($bm_mul(freq, t), 2), Math.PI))), Math.exp($bm_mul(decay, t))));\n} else {\n $bm_rt = value;\n}" + } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "d": 1, + "ty": "el", + "s": { + "a": 0, + "k": [ + 4.994, + 4.994 + ], + "ix": 2 + }, + "p": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 3 + }, + "nm": "Ellipse Path 1", + "mn": "ADBE Vector Shape - Ellipse", + "hd": false + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [ + 0, + 0.752941236309, + 0.482352971096, + 1 + ], + "ix": 4 + }, + "o": { + "a": 0, + "k": 100, + "ix": 5 + }, + "r": 1, + "bm": 0, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill", + "hd": false + }, + { + "ty": "tr", + "p": { + "a": 0, + "k": [ + -14.253, + -9.753 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 192.894, + 192.894 + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": 0, + "ix": 6 + }, + "o": { + "a": 0, + "k": 100, + "ix": 7 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 4 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 5 + }, + "nm": "Transform" + } + ], + "nm": "Ellipse 1", + "np": 3, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 10, + "op": 151, + "st": 0, + "bm": 0 + }, + { + "ddd": 0, + "ind": 10, + "ty": 4, + "nm": "Shape Layer 2", + "sr": 1, + "ks": { + "o": { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": { + "s": true, + "x": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.168 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.333 + ], + "y": [ + 0 + ] + }, + "t": 0, + "s": [ + 39.469 + ] + }, + { + "t": 10, + "s": [ + 55.969 + ] + } + ], + "ix": 3 + }, + "y": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 0, + "s": [ + 56 + ] + }, + { + "t": 10, + "s": [ + 56 + ] + } + ], + "ix": 4 + } + }, + "a": { + "a": 0, + "k": [ + -14.253, + -9.753, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100, + 100 + ], + "ix": 6 + } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "d": 1, + "ty": "el", + "s": { + "a": 0, + "k": [ + 4.994, + 4.994 + ], + "ix": 2 + }, + "p": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 3 + }, + "nm": "Ellipse Path 1", + "mn": "ADBE Vector Shape - Ellipse", + "hd": false + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [ + 1, + 0.866666726505, + 0.188235309077, + 1 + ], + "ix": 4 + }, + "o": { + "a": 0, + "k": 100, + "ix": 5 + }, + "r": 1, + "bm": 0, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill", + "hd": false + }, + { + "ty": "tr", + "p": { + "a": 0, + "k": [ + -14.253, + -9.753 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 192.894, + 192.894 + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": 0, + "ix": 6 + }, + "o": { + "a": 0, + "k": 100, + "ix": 7 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 4 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 5 + }, + "nm": "Transform" + } + ], + "nm": "Ellipse 1", + "np": 3, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 0, + "op": 10, + "st": 0, + "bm": 0 + }, + { + "ddd": 0, + "ind": 11, + "ty": 4, + "nm": "Shape Layer 1", + "sr": 1, + "ks": { + "o": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 0, + "s": [ + 100 + ] + }, + { + "t": 10, + "s": [ + 0 + ] + } + ], + "ix": 11 + }, + "r": { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": { + "a": 0, + "k": [ + 55.969, + 56, + 0 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + -14.253, + -9.753, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100, + 100 + ], + "ix": 6 + } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "d": 1, + "ty": "el", + "s": { + "a": 0, + "k": [ + 4.994, + 4.994 + ], + "ix": 2 + }, + "p": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 3 + }, + "nm": "Ellipse Path 1", + "mn": "ADBE Vector Shape - Ellipse", + "hd": false + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [ + 0, + 0.752941236309, + 0.482352971096, + 1 + ], + "ix": 4 + }, + "o": { + "a": 0, + "k": 100, + "ix": 5 + }, + "r": 1, + "bm": 0, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill", + "hd": false + }, + { + "ty": "tr", + "p": { + "a": 0, + "k": [ + -14.253, + -9.753 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 192.894, + 192.894 + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": 0, + "ix": 6 + }, + "o": { + "a": 0, + "k": 100, + "ix": 7 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 4 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 5 + }, + "nm": "Transform" + } + ], + "nm": "Ellipse 1", + "np": 3, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 0, + "op": 11, + "st": 4, + "bm": 0 + }, + { + "ddd": 0, + "ind": 12, + "ty": 4, + "nm": "Shape Layer 3", + "sr": 1, + "ks": { + "o": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 5, + "s": [ + 100 + ] + }, + { + "t": 16, + "s": [ + 0 + ] + } + ], + "ix": 11 + }, + "r": { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": { + "s": true, + "x": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.261 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.41 + ], + "y": [ + 0 + ] + }, + "t": 5, + "s": [ + 72.969 + ] + }, + { + "t": 16, + "s": [ + 55.969 + ] + } + ], + "ix": 3 + }, + "y": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 5, + "s": [ + 56 + ] + }, + { + "t": 16, + "s": [ + 56 + ] + } + ], + "ix": 4 + } + }, + "a": { + "a": 0, + "k": [ + -14.253, + -9.753, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100, + 100 + ], + "ix": 6 + } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "d": 1, + "ty": "el", + "s": { + "a": 0, + "k": [ + 4.994, + 4.994 + ], + "ix": 2 + }, + "p": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 3 + }, + "nm": "Ellipse Path 1", + "mn": "ADBE Vector Shape - Ellipse", + "hd": false + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [ + 0.937254901961, + 0.356862745098, + 0.494117647059, + 1 + ], + "ix": 4 + }, + "o": { + "a": 0, + "k": 100, + "ix": 5 + }, + "r": 1, + "bm": 0, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill", + "hd": false + }, + { + "ty": "tr", + "p": { + "a": 0, + "k": [ + -14.253, + -9.753 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 192.894, + 192.894 + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": 0, + "ix": 6 + }, + "o": { + "a": 0, + "k": 100, + "ix": 7 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 4 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 5 + }, + "nm": "Transform" + } + ], + "nm": "Ellipse 1", + "np": 3, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 0, + "op": 17, + "st": 8, + "bm": 0 + } + ], + "markers": [] +} \ No newline at end of file diff --git a/app/src/main/res/raw/epub_importing_lottie.json b/app/src/main/res/raw/epub_importing_lottie.json new file mode 100644 index 00000000..3073fa42 --- /dev/null +++ b/app/src/main/res/raw/epub_importing_lottie.json @@ -0,0 +1,5002 @@ +{ + "v": "5.5.5", + "fr": 30, + "ip": 0, + "op": 60, + "w": 112, + "h": 112, + "nm": "loading", + "ddd": 0, + "assets": [ + { + "id": "comp_0", + "layers": [ + { + "ddd": 0, + "ind": 1, + "ty": 0, + "nm": "flick", + "refId": "comp_1", + "sr": 1, + "ks": { + "o": { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": { + "a": 0, + "k": 45, + "ix": 10 + }, + "p": { + "a": 0, + "k": [ + 84, + 23, + 0 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 56, + 56, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 27.272, + 27.272, + 100 + ], + "ix": 6 + } + }, + "ao": 0, + "w": 112, + "h": 112, + "ip": 53, + "op": 203, + "st": 53, + "bm": 0 + }, + { + "ddd": 0, + "ind": 2, + "ty": 4, + "nm": "Shape Layer 10", + "parent": 9, + "sr": 1, + "ks": { + "o": { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": { + "a": 0, + "k": -89.887, + "ix": 10 + }, + "p": { + "a": 0, + "k": [ + -15.397, + -7.665, + 0 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + -9.678, + -12.102, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 14.905, + 14.905, + 100 + ], + "ix": 6 + } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "ty": "rc", + "d": 1, + "s": { + "a": 0, + "k": [ + 14.893, + 21.297 + ], + "ix": 2 + }, + "p": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": 2, + "ix": 4 + }, + "nm": "Rectangle Path 1", + "mn": "ADBE Vector Shape - Rect", + "hd": false + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [ + 0, + 0.752941236309, + 0.482352971096, + 1 + ], + "ix": 4 + }, + "o": { + "a": 0, + "k": 100, + "ix": 5 + }, + "r": 1, + "bm": 0, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill", + "hd": false + }, + { + "ty": "tr", + "p": { + "a": 0, + "k": [ + -9.832, + -12.103 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + -0.082, + -10.641 + ], + "ix": 1 + }, + "s": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.057, + 0.057 + ], + "y": [ + 1, + 1 + ] + }, + "o": { + "x": [ + 0.333, + 0.333 + ], + "y": [ + 0, + 0 + ] + }, + "t": 47, + "s": [ + 92.606, + 0.914 + ] + }, + { + "t": 55, + "s": [ + 92.606, + 167.707 + ] + } + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": -45, + "ix": 6 + }, + "o": { + "a": 0, + "k": 100, + "ix": 7 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 4 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 5 + }, + "nm": "Transform" + } + ], + "nm": "Rectangle 1", + "np": 3, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 47, + "op": 154, + "st": 4, + "bm": 0 + }, + { + "ddd": 0, + "ind": 3, + "ty": 4, + "nm": "Shape Layer 9", + "parent": 9, + "sr": 1, + "ks": { + "o": { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": { + "a": 0, + "k": 0.113, + "ix": 10 + }, + "p": { + "a": 0, + "k": [ + -16.64, + -10.313, + 0 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + -9.678, + -12.102, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 14.905, + 14.905, + 100 + ], + "ix": 6 + } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "ty": "rc", + "d": 1, + "s": { + "a": 0, + "k": [ + 14.893, + 21.297 + ], + "ix": 2 + }, + "p": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": 2, + "ix": 4 + }, + "nm": "Rectangle Path 1", + "mn": "ADBE Vector Shape - Rect", + "hd": false + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [ + 0, + 0.752941236309, + 0.482352971096, + 1 + ], + "ix": 4 + }, + "o": { + "a": 0, + "k": 100, + "ix": 5 + }, + "r": 1, + "bm": 0, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill", + "hd": false + }, + { + "ty": "tr", + "p": { + "a": 0, + "k": [ + -9.559, + -11.867 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + -0.091, + -10.492 + ], + "ix": 1 + }, + "s": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.26, + 0.26 + ], + "y": [ + 1, + 1 + ] + }, + "o": { + "x": [ + 0.708, + 0.708 + ], + "y": [ + 0, + 0 + ] + }, + "t": 39, + "s": [ + 92.606, + 3.438 + ] + }, + { + "t": 47, + "s": [ + 92.606, + 117.488 + ] + } + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": -45, + "ix": 6 + }, + "o": { + "a": 0, + "k": 100, + "ix": 7 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 4 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 5 + }, + "nm": "Transform" + } + ], + "nm": "Rectangle 1", + "np": 3, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 39, + "op": 150, + "st": 0, + "bm": 0 + }, + { + "ddd": 0, + "ind": 5, + "ty": 4, + "nm": "Shape Layer 5", + "parent": 9, + "sr": 1, + "ks": { + "o": { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": { + "s": true, + "x": { + "a": 0, + "k": -14.253, + "ix": 3 + }, + "y": { + "a": 0, + "k": -9.768, + "ix": 4 + } + }, + "a": { + "a": 0, + "k": [ + -14.253, + -9.753, + 0 + ], + "ix": 1 + }, + "s": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.833, + 0.833, + 0.833 + ], + "y": [ + 0.833, + 0.833, + 0.833 + ] + }, + "o": { + "x": [ + 0.167, + 0.167, + 0.167 + ], + "y": [ + 0.167, + 0.167, + 0.167 + ] + }, + "t": 23, + "s": [ + 0, + 0, + 100 + ] + }, + { + "t": 33, + "s": [ + 86.075, + 86.075, + 100 + ] + } + ], + "ix": 6 + } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "d": 1, + "ty": "el", + "s": { + "a": 0, + "k": [ + 4.994, + 4.994 + ], + "ix": 2 + }, + "p": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 3 + }, + "nm": "Ellipse Path 1", + "mn": "ADBE Vector Shape - Ellipse", + "hd": false + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [ + 1, + 0.964705942191, + 0.847058883368, + 1 + ], + "ix": 4 + }, + "o": { + "a": 0, + "k": 100, + "ix": 5 + }, + "r": 1, + "bm": 0, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill", + "hd": false + }, + { + "ty": "tr", + "p": { + "a": 0, + "k": [ + -14.253, + -9.753 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 192.894, + 192.894 + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": 0, + "ix": 6 + }, + "o": { + "a": 0, + "k": 100, + "ix": 7 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 4 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 5 + }, + "nm": "Transform" + } + ], + "nm": "Ellipse 1", + "np": 3, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 23, + "op": 151, + "st": 13, + "bm": 0 + }, + { + "ddd": 0, + "ind": 6, + "ty": 4, + "nm": "Shape Layer 6", + "parent": 9, + "sr": 1, + "ks": { + "o": { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": { + "s": true, + "x": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 23, + "s": [ + -14.253 + ] + }, + { + "t": 39, + "s": [ + -14.253 + ] + } + ], + "ix": 3 + }, + "y": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.658 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.791 + ], + "y": [ + 0 + ] + }, + "t": 23, + "s": [ + -6.936 + ] + }, + { + "t": 39, + "s": [ + -2.912 + ] + } + ], + "ix": 4 + } + }, + "a": { + "a": 0, + "k": [ + -14.253, + -9.753, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 24.961, + 24.961, + 100 + ], + "ix": 6 + } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "d": 1, + "ty": "el", + "s": { + "a": 0, + "k": [ + 4.994, + 4.994 + ], + "ix": 2 + }, + "p": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 3 + }, + "nm": "Ellipse Path 1", + "mn": "ADBE Vector Shape - Ellipse", + "hd": false + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [ + 1, + 0.866666726505, + 0.188235309077, + 1 + ], + "ix": 4 + }, + "o": { + "a": 0, + "k": 100, + "ix": 5 + }, + "r": 1, + "bm": 0, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill", + "hd": false + }, + { + "ty": "tr", + "p": { + "a": 0, + "k": [ + -14.253, + -9.753 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 192.894, + 192.894 + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": 0, + "ix": 6 + }, + "o": { + "a": 0, + "k": 100, + "ix": 7 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 4 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 5 + }, + "nm": "Transform" + } + ], + "nm": "Ellipse 1", + "np": 3, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 23, + "op": 142, + "st": 13, + "bm": 0 + }, + { + "ddd": 0, + "ind": 7, + "ty": 4, + "nm": "Shape Layer 8", + "parent": 9, + "sr": 1, + "ks": { + "o": { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": { + "a": 0, + "k": [ + -14.248, + -8.047, + 0 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + -14.905, + 14.905, + 100 + ], + "ix": 6 + } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "ind": 0, + "ty": "sh", + "ix": 1, + "ks": { + "a": 1, + "k": [ + { + "i": { + "x": 0.833, + "y": 0.833 + }, + "o": { + "x": 0.167, + "y": 0.167 + }, + "t": 31, + "s": [ + { + "i": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -4.372, + 0.955 + ], + [ + -2.75, + 4.25 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "o": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 14.875, + -3.25 + ], + [ + 2.75, + -4.25 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "v": [ + [ + -0.125, + 13.625 + ], + [ + 0.25, + 20.75 + ], + [ + 7.5, + 20.25 + ], + [ + 30.125, + 0.25 + ], + [ + 19.625, + -15.75 + ], + [ + 0, + 3.875 + ] + ], + "c": false + } + ] + }, + { + "i": { + "x": 0.833, + "y": 0.833 + }, + "o": { + "x": 0.167, + "y": 0.167 + }, + "t": 33, + "s": [ + { + "i": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -3.936, + 1.623 + ], + [ + -2.75, + 4.25 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "o": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 11.25, + -4.594 + ], + [ + 2.75, + -4.25 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "v": [ + [ + -0.125, + 13.625 + ], + [ + 0.125, + 26.156 + ], + [ + 7.375, + 24.594 + ], + [ + 30.125, + 0.25 + ], + [ + 19.625, + -15.75 + ], + [ + 0, + 3.875 + ] + ], + "c": false + } + ] + }, + { + "i": { + "x": 0.833, + "y": 0.833 + }, + "o": { + "x": 0.167, + "y": 0.167 + }, + "t": 35, + "s": [ + { + "i": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -3.499, + 2.29 + ], + [ + -2.75, + 4.25 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "o": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 7.625, + -5.938 + ], + [ + 2.75, + -4.25 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "v": [ + [ + -0.125, + 13.625 + ], + [ + 0, + 33.812 + ], + [ + 7.25, + 31.188 + ], + [ + 30.125, + 0.25 + ], + [ + 19.625, + -15.75 + ], + [ + 0, + 3.875 + ] + ], + "c": false + } + ] + }, + { + "i": { + "x": 0.833, + "y": 0.833 + }, + "o": { + "x": 0.167, + "y": 0.167 + }, + "t": 37, + "s": [ + { + "i": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -3.062, + 2.958 + ], + [ + -2.75, + 4.25 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "o": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 5.125, + -4.781 + ], + [ + 2.75, + -4.25 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "v": [ + [ + -0.125, + 13.625 + ], + [ + -0.125, + 38.219 + ], + [ + 7.125, + 36.656 + ], + [ + 30.125, + 0.25 + ], + [ + 19.625, + -15.75 + ], + [ + 0, + 3.875 + ] + ], + "c": false + } + ] + }, + { + "t": 39, + "s": [ + { + "i": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -2.625, + 3.625 + ], + [ + -2.75, + 4.25 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "o": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 2.625, + -3.625 + ], + [ + 2.75, + -4.25 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "v": [ + [ + -0.125, + 13.625 + ], + [ + -0.125, + 38.875 + ], + [ + 7.125, + 38.375 + ], + [ + 30.125, + 0.25 + ], + [ + 19.625, + -15.75 + ], + [ + 0, + 3.875 + ] + ], + "c": false + } + ] + } + ], + "ix": 2 + }, + "nm": "Path 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [ + 1, + 0.866666726505, + 0.188235309077, + 1 + ], + "ix": 4 + }, + "o": { + "a": 0, + "k": 100, + "ix": 5 + }, + "r": 1, + "bm": 0, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill", + "hd": false + }, + { + "ty": "tr", + "p": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100 + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": 0, + "ix": 6 + }, + "o": { + "a": 0, + "k": 100, + "ix": 7 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 4 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 5 + }, + "nm": "Transform" + } + ], + "nm": "Shape 1", + "np": 3, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 23, + "op": 194, + "st": -36, + "bm": 0 + }, + { + "ddd": 0, + "ind": 8, + "ty": 4, + "nm": "Shape Layer 7", + "parent": 9, + "sr": 1, + "ks": { + "o": { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": { + "a": 0, + "k": [ + -14.248, + -8.047, + 0 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 14.905, + 14.905, + 100 + ], + "ix": 6 + } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "ind": 0, + "ty": "sh", + "ix": 1, + "ks": { + "a": 1, + "k": [ + { + "i": { + "x": 0.833, + "y": 0.833 + }, + "o": { + "x": 0.167, + "y": 0.167 + }, + "t": 31, + "s": [ + { + "i": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -4.372, + 0.955 + ], + [ + -2.75, + 4.25 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "o": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 14.875, + -3.25 + ], + [ + 2.75, + -4.25 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "v": [ + [ + -0.125, + 13.625 + ], + [ + 0.25, + 20.75 + ], + [ + 7.5, + 20.25 + ], + [ + 30.125, + 0.25 + ], + [ + 19.625, + -15.75 + ], + [ + 0, + 3.875 + ] + ], + "c": false + } + ] + }, + { + "i": { + "x": 0.833, + "y": 0.833 + }, + "o": { + "x": 0.167, + "y": 0.167 + }, + "t": 33, + "s": [ + { + "i": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -3.936, + 1.623 + ], + [ + -2.75, + 4.25 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "o": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 11.25, + -4.594 + ], + [ + 2.75, + -4.25 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "v": [ + [ + -0.125, + 13.625 + ], + [ + 0.125, + 26.156 + ], + [ + 7.375, + 24.594 + ], + [ + 30.125, + 0.25 + ], + [ + 19.625, + -15.75 + ], + [ + 0, + 3.875 + ] + ], + "c": false + } + ] + }, + { + "i": { + "x": 0.833, + "y": 0.833 + }, + "o": { + "x": 0.167, + "y": 0.167 + }, + "t": 35, + "s": [ + { + "i": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -3.499, + 2.29 + ], + [ + -2.75, + 4.25 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "o": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 7.625, + -5.938 + ], + [ + 2.75, + -4.25 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "v": [ + [ + -0.125, + 13.625 + ], + [ + 0, + 33.812 + ], + [ + 7.25, + 31.188 + ], + [ + 30.125, + 0.25 + ], + [ + 19.625, + -15.75 + ], + [ + 0, + 3.875 + ] + ], + "c": false + } + ] + }, + { + "i": { + "x": 0.833, + "y": 0.833 + }, + "o": { + "x": 0.167, + "y": 0.167 + }, + "t": 37, + "s": [ + { + "i": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -3.062, + 2.958 + ], + [ + -2.75, + 4.25 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "o": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 5.125, + -4.781 + ], + [ + 2.75, + -4.25 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "v": [ + [ + -0.125, + 13.625 + ], + [ + -0.125, + 38.219 + ], + [ + 7.125, + 36.656 + ], + [ + 30.125, + 0.25 + ], + [ + 19.625, + -15.75 + ], + [ + 0, + 3.875 + ] + ], + "c": false + } + ] + }, + { + "t": 39, + "s": [ + { + "i": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + -2.625, + 3.625 + ], + [ + -2.75, + 4.25 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "o": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ], + [ + 2.625, + -3.625 + ], + [ + 2.75, + -4.25 + ], + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "v": [ + [ + -0.125, + 13.625 + ], + [ + -0.125, + 38.875 + ], + [ + 7.125, + 38.375 + ], + [ + 30.125, + 0.25 + ], + [ + 19.625, + -15.75 + ], + [ + 0, + 3.875 + ] + ], + "c": false + } + ] + } + ], + "ix": 2 + }, + "nm": "Path 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [ + 1, + 0.866666726505, + 0.188235309077, + 1 + ], + "ix": 4 + }, + "o": { + "a": 0, + "k": 100, + "ix": 5 + }, + "r": 1, + "bm": 0, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill", + "hd": false + }, + { + "ty": "tr", + "p": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100 + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": 0, + "ix": 6 + }, + "o": { + "a": 0, + "k": 100, + "ix": 7 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 4 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 5 + }, + "nm": "Transform" + } + ], + "nm": "Shape 1", + "np": 3, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 23, + "op": 194, + "st": -36, + "bm": 0 + }, + { + "ddd": 0, + "ind": 9, + "ty": 4, + "nm": "big cir", + "sr": 1, + "ks": { + "o": { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 23, + "s": [ + -72 + ] + }, + { + "t": 39, + "s": [ + 0 + ] + } + ], + "ix": 10, + "x": "var $bm_rt;\nvar n, n, t, t, v, amp, freq, decay;\n$bm_rt = n = 0;\nif (numKeys > 0) {\n $bm_rt = n = nearestKey(time).index;\n if (key(n).time > time) {\n n--;\n }\n}\nif (n == 0) {\n $bm_rt = t = 0;\n} else {\n $bm_rt = t = $bm_sub(time, key(n).time);\n}\nif (n > 0) {\n v = velocityAtTime($bm_sub(key(n).time, $bm_div(thisComp.frameDuration, 10)));\n amp = 0.2;\n freq = 2;\n decay = 4;\n $bm_rt = $bm_sum(value, $bm_div($bm_mul($bm_mul(v, amp), Math.sin($bm_mul($bm_mul($bm_mul(freq, t), 2), Math.PI))), Math.exp($bm_mul(decay, t))));\n} else {\n $bm_rt = value;\n}" + }, + "p": { + "s": true, + "x": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 10, + "s": [ + 55.969 + ] + }, + { + "t": 20, + "s": [ + 55.969 + ] + } + ], + "ix": 3 + }, + "y": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 10, + "s": [ + 56 + ] + }, + { + "t": 20, + "s": [ + 44.5 + ] + } + ], + "ix": 4, + "x": "var $bm_rt;\nvar n, n, t, t, v, amp, freq, decay;\n$bm_rt = n = 0;\nif (numKeys > 0) {\n $bm_rt = n = nearestKey(time).index;\n if (key(n).time > time) {\n n--;\n }\n}\nif (n == 0) {\n $bm_rt = t = 0;\n} else {\n $bm_rt = t = $bm_sub(time, key(n).time);\n}\nif (n > 0) {\n v = velocityAtTime($bm_sub(key(n).time, $bm_div(thisComp.frameDuration, 10)));\n amp = 0.5;\n freq = 2;\n decay = 4;\n $bm_rt = $bm_sum(value, $bm_div($bm_mul($bm_mul(v, amp), Math.sin($bm_mul($bm_mul($bm_mul(freq, t), 2), Math.PI))), Math.exp($bm_mul(decay, t))));\n} else {\n $bm_rt = value;\n}" + } + }, + "a": { + "a": 0, + "k": [ + -14.253, + -9.753, + 0 + ], + "ix": 1 + }, + "s": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.833, + 0.833, + 0.833 + ], + "y": [ + 0.833, + 0.833, + 0.833 + ] + }, + "o": { + "x": [ + 0.167, + 0.167, + 0.167 + ], + "y": [ + 0.167, + 0.167, + 0.167 + ] + }, + "t": 20, + "s": [ + 100, + 100, + 100 + ] + }, + { + "t": 29, + "s": [ + 670.93, + 670.93, + 100 + ] + } + ], + "ix": 6 + } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "d": 1, + "ty": "el", + "s": { + "a": 0, + "k": [ + 4.994, + 4.994 + ], + "ix": 2 + }, + "p": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 3 + }, + "nm": "Ellipse Path 1", + "mn": "ADBE Vector Shape - Ellipse", + "hd": false + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [ + 1, + 0.866666726505, + 0.188235309077, + 1 + ], + "ix": 4 + }, + "o": { + "a": 0, + "k": 100, + "ix": 5 + }, + "r": 1, + "bm": 0, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill", + "hd": false + }, + { + "ty": "tr", + "p": { + "a": 0, + "k": [ + -14.253, + -9.753 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 192.894, + 192.894 + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": 0, + "ix": 6 + }, + "o": { + "a": 0, + "k": 100, + "ix": 7 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 4 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 5 + }, + "nm": "Transform" + } + ], + "nm": "Ellipse 1", + "np": 3, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 10, + "op": 151, + "st": 0, + "bm": 0 + }, + { + "ddd": 0, + "ind": 10, + "ty": 4, + "nm": "Shape Layer 2", + "sr": 1, + "ks": { + "o": { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": { + "s": true, + "x": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.168 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.333 + ], + "y": [ + 0 + ] + }, + "t": 0, + "s": [ + 39.469 + ] + }, + { + "t": 10, + "s": [ + 55.969 + ] + } + ], + "ix": 3 + }, + "y": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 0, + "s": [ + 56 + ] + }, + { + "t": 10, + "s": [ + 56 + ] + } + ], + "ix": 4 + } + }, + "a": { + "a": 0, + "k": [ + -14.253, + -9.753, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100, + 100 + ], + "ix": 6 + } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "d": 1, + "ty": "el", + "s": { + "a": 0, + "k": [ + 4.994, + 4.994 + ], + "ix": 2 + }, + "p": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 3 + }, + "nm": "Ellipse Path 1", + "mn": "ADBE Vector Shape - Ellipse", + "hd": false + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [ + 1, + 0.866666726505, + 0.188235309077, + 1 + ], + "ix": 4 + }, + "o": { + "a": 0, + "k": 100, + "ix": 5 + }, + "r": 1, + "bm": 0, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill", + "hd": false + }, + { + "ty": "tr", + "p": { + "a": 0, + "k": [ + -14.253, + -9.753 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 192.894, + 192.894 + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": 0, + "ix": 6 + }, + "o": { + "a": 0, + "k": 100, + "ix": 7 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 4 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 5 + }, + "nm": "Transform" + } + ], + "nm": "Ellipse 1", + "np": 3, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 0, + "op": 10, + "st": 0, + "bm": 0 + }, + { + "ddd": 0, + "ind": 11, + "ty": 4, + "nm": "Shape Layer 1", + "sr": 1, + "ks": { + "o": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 0, + "s": [ + 100 + ] + }, + { + "t": 10, + "s": [ + 0 + ] + } + ], + "ix": 11 + }, + "r": { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": { + "a": 0, + "k": [ + 55.969, + 56, + 0 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + -14.253, + -9.753, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100, + 100 + ], + "ix": 6 + } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "d": 1, + "ty": "el", + "s": { + "a": 0, + "k": [ + 4.994, + 4.994 + ], + "ix": 2 + }, + "p": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 3 + }, + "nm": "Ellipse Path 1", + "mn": "ADBE Vector Shape - Ellipse", + "hd": false + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [ + 0, + 0.752941236309, + 0.482352971096, + 1 + ], + "ix": 4 + }, + "o": { + "a": 0, + "k": 100, + "ix": 5 + }, + "r": 1, + "bm": 0, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill", + "hd": false + }, + { + "ty": "tr", + "p": { + "a": 0, + "k": [ + -14.253, + -9.753 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 192.894, + 192.894 + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": 0, + "ix": 6 + }, + "o": { + "a": 0, + "k": 100, + "ix": 7 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 4 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 5 + }, + "nm": "Transform" + } + ], + "nm": "Ellipse 1", + "np": 3, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 0, + "op": 11, + "st": 4, + "bm": 0 + }, + { + "ddd": 0, + "ind": 12, + "ty": 4, + "nm": "Shape Layer 3", + "sr": 1, + "ks": { + "o": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 5, + "s": [ + 100 + ] + }, + { + "t": 16, + "s": [ + 0 + ] + } + ], + "ix": 11 + }, + "r": { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": { + "s": true, + "x": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.261 + ], + "y": [ + 1 + ] + }, + "o": { + "x": [ + 0.41 + ], + "y": [ + 0 + ] + }, + "t": 5, + "s": [ + 72.969 + ] + }, + { + "t": 16, + "s": [ + 55.969 + ] + } + ], + "ix": 3 + }, + "y": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 5, + "s": [ + 56 + ] + }, + { + "t": 16, + "s": [ + 56 + ] + } + ], + "ix": 4 + } + }, + "a": { + "a": 0, + "k": [ + -14.253, + -9.753, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100, + 100 + ], + "ix": 6 + } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "d": 1, + "ty": "el", + "s": { + "a": 0, + "k": [ + 4.994, + 4.994 + ], + "ix": 2 + }, + "p": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 3 + }, + "nm": "Ellipse Path 1", + "mn": "ADBE Vector Shape - Ellipse", + "hd": false + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [ + 0.937254901961, + 0.356862745098, + 0.494117647059, + 1 + ], + "ix": 4 + }, + "o": { + "a": 0, + "k": 100, + "ix": 5 + }, + "r": 1, + "bm": 0, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill", + "hd": false + }, + { + "ty": "tr", + "p": { + "a": 0, + "k": [ + -14.253, + -9.753 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 192.894, + 192.894 + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": 0, + "ix": 6 + }, + "o": { + "a": 0, + "k": 100, + "ix": 7 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 4 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 5 + }, + "nm": "Transform" + } + ], + "nm": "Ellipse 1", + "np": 3, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 0, + "op": 17, + "st": 8, + "bm": 0 + }, + { + "ddd": 0, + "ind": 14, + "ty": 1, + "nm": "White Solid 1", + "sr": 1, + "ks": { + "o": { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": { + "a": 0, + "k": [ + 56, + 56, + 0 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 56, + 56, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100, + 100 + ], + "ix": 6 + } + }, + "ao": 0, + "sw": 112, + "sh": 112, + "sc": "#ffffff", + "ip": 0, + "op": 150, + "st": 0, + "bm": 0 + } + ] + }, + { + "id": "comp_1", + "layers": [ + { + "ddd": 0, + "ind": 1, + "ty": 4, + "nm": "Shape Layer 13", + "sr": 1, + "ks": { + "o": { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": { + "a": 0, + "k": -45, + "ix": 10 + }, + "p": { + "a": 0, + "k": [ + 46, + 56, + 0 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100, + 100 + ], + "ix": 6 + } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "ind": 0, + "ty": "sh", + "ix": 1, + "ks": { + "a": 0, + "k": { + "i": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "o": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "v": [ + [ + 0, + -41.5 + ], + [ + 0, + -20.625 + ] + ], + "c": false + }, + "ix": 2 + }, + "nm": "Path 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "st", + "c": { + "a": 0, + "k": [ + 0, + 0.752941176471, + 0.482352941176, + 1 + ], + "ix": 3 + }, + "o": { + "a": 0, + "k": 100, + "ix": 4 + }, + "w": { + "a": 0, + "k": 10, + "ix": 5 + }, + "lc": 2, + "lj": 1, + "ml": 4, + "bm": 0, + "nm": "Stroke 1", + "mn": "ADBE Vector Graphic - Stroke", + "hd": false + }, + { + "ty": "tr", + "p": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100 + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": 0, + "ix": 6 + }, + "o": { + "a": 0, + "k": 100, + "ix": 7 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 4 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 5 + }, + "nm": "Transform" + } + ], + "nm": "Shape 1", + "np": 3, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + }, + { + "ty": "tm", + "s": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 0, + "s": [ + 100 + ] + }, + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 7.5, + "s": [ + 0 + ] + }, + { + "t": 15, + "s": [ + 0 + ] + } + ], + "ix": 1 + }, + "e": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 0, + "s": [ + 100 + ] + }, + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 7.5, + "s": [ + 100 + ] + }, + { + "t": 15, + "s": [ + 0 + ] + } + ], + "ix": 2 + }, + "o": { + "a": 0, + "k": 0, + "ix": 3 + }, + "m": 1, + "ix": 2, + "nm": "Trim Paths 1", + "mn": "ADBE Vector Filter - Trim", + "hd": false + } + ], + "ip": 0, + "op": 150, + "st": 0, + "bm": 0 + }, + { + "ddd": 0, + "ind": 2, + "ty": 4, + "nm": "Shape Layer 12", + "sr": 1, + "ks": { + "o": { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": { + "a": 0, + "k": 45, + "ix": 10 + }, + "p": { + "a": 0, + "k": [ + 63.5, + 56, + 0 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100, + 100 + ], + "ix": 6 + } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "ind": 0, + "ty": "sh", + "ix": 1, + "ks": { + "a": 0, + "k": { + "i": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "o": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "v": [ + [ + 0, + -41.5 + ], + [ + 0, + -20.625 + ] + ], + "c": false + }, + "ix": 2 + }, + "nm": "Path 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "st", + "c": { + "a": 0, + "k": [ + 0, + 0.752941176471, + 0.482352941176, + 1 + ], + "ix": 3 + }, + "o": { + "a": 0, + "k": 100, + "ix": 4 + }, + "w": { + "a": 0, + "k": 10, + "ix": 5 + }, + "lc": 2, + "lj": 1, + "ml": 4, + "bm": 0, + "nm": "Stroke 1", + "mn": "ADBE Vector Graphic - Stroke", + "hd": false + }, + { + "ty": "tr", + "p": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100 + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": 0, + "ix": 6 + }, + "o": { + "a": 0, + "k": 100, + "ix": 7 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 4 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 5 + }, + "nm": "Transform" + } + ], + "nm": "Shape 1", + "np": 3, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + }, + { + "ty": "tm", + "s": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 0, + "s": [ + 100 + ] + }, + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 7.5, + "s": [ + 0 + ] + }, + { + "t": 15, + "s": [ + 0 + ] + } + ], + "ix": 1 + }, + "e": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 0, + "s": [ + 100 + ] + }, + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 7.5, + "s": [ + 100 + ] + }, + { + "t": 15, + "s": [ + 0 + ] + } + ], + "ix": 2 + }, + "o": { + "a": 0, + "k": 0, + "ix": 3 + }, + "m": 1, + "ix": 2, + "nm": "Trim Paths 1", + "mn": "ADBE Vector Filter - Trim", + "hd": false + } + ], + "ip": 0, + "op": 150, + "st": 0, + "bm": 0 + }, + { + "ddd": 0, + "ind": 3, + "ty": 4, + "nm": "Shape Layer 11", + "sr": 1, + "ks": { + "o": { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": { + "a": 0, + "k": [ + 56, + 56, + 0 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100, + 100 + ], + "ix": 6 + } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "ind": 0, + "ty": "sh", + "ix": 1, + "ks": { + "a": 0, + "k": { + "i": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "o": [ + [ + 0, + 0 + ], + [ + 0, + 0 + ] + ], + "v": [ + [ + 0, + -41.5 + ], + [ + 0, + -20.625 + ] + ], + "c": false + }, + "ix": 2 + }, + "nm": "Path 1", + "mn": "ADBE Vector Shape - Group", + "hd": false + }, + { + "ty": "st", + "c": { + "a": 0, + "k": [ + 0, + 0.752941176471, + 0.482352941176, + 1 + ], + "ix": 3 + }, + "o": { + "a": 0, + "k": 100, + "ix": 4 + }, + "w": { + "a": 0, + "k": 10, + "ix": 5 + }, + "lc": 2, + "lj": 1, + "ml": 4, + "bm": 0, + "nm": "Stroke 1", + "mn": "ADBE Vector Graphic - Stroke", + "hd": false + }, + { + "ty": "tr", + "p": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100 + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": 0, + "ix": 6 + }, + "o": { + "a": 0, + "k": 100, + "ix": 7 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 4 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 5 + }, + "nm": "Transform" + } + ], + "nm": "Shape 1", + "np": 3, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + }, + { + "ty": "tm", + "s": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 0, + "s": [ + 100 + ] + }, + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 7.5, + "s": [ + 0 + ] + }, + { + "t": 15, + "s": [ + 0 + ] + } + ], + "ix": 1 + }, + "e": { + "a": 1, + "k": [ + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 0, + "s": [ + 100 + ] + }, + { + "i": { + "x": [ + 0.833 + ], + "y": [ + 0.833 + ] + }, + "o": { + "x": [ + 0.167 + ], + "y": [ + 0.167 + ] + }, + "t": 7.5, + "s": [ + 100 + ] + }, + { + "t": 15, + "s": [ + 0 + ] + } + ], + "ix": 2 + }, + "o": { + "a": 0, + "k": 0, + "ix": 3 + }, + "m": 1, + "ix": 2, + "nm": "Trim Paths 1", + "mn": "ADBE Vector Filter - Trim", + "hd": false + } + ], + "ip": 0, + "op": 150, + "st": 0, + "bm": 0 + } + ] + } + ], + "layers": [ + { + "ddd": 0, + "ind": 1, + "ty": 4, + "nm": "Shape Layer 2", + "sr": 1, + "ks": { + "o": { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": { + "a": 1, + "k": [ + { + "i": { + "x": 0.667, + "y": 1 + }, + "o": { + "x": 0.333, + "y": 0 + }, + "t": 0, + "s": [ + 39.438, + 56, + 0 + ], + "to": [ + 0, + 0, + 0 + ], + "ti": [ + 0, + 0, + 0 + ] + }, + { + "i": { + "x": 0.667, + "y": 1 + }, + "o": { + "x": 0.333, + "y": 0 + }, + "t": 4.565, + "s": [ + 39.438, + 45.75, + 0 + ], + "to": [ + 0, + 0, + 0 + ], + "ti": [ + 0, + 0, + 0 + ] + }, + { + "i": { + "x": 0.667, + "y": 0.667 + }, + "o": { + "x": 0.167, + "y": 0.167 + }, + "t": 9.13, + "s": [ + 39.438, + 56, + 0 + ], + "to": [ + 0, + 0, + 0 + ], + "ti": [ + 0, + 0, + 0 + ] + }, + { + "i": { + "x": 0.667, + "y": 1 + }, + "o": { + "x": 0.333, + "y": 0 + }, + "t": 25.87, + "s": [ + 39.438, + 56, + 0 + ], + "to": [ + 0, + 0, + 0 + ], + "ti": [ + 0, + 0, + 0 + ] + }, + { + "i": { + "x": 0.667, + "y": 1 + }, + "o": { + "x": 0.333, + "y": 0 + }, + "t": 30.435, + "s": [ + 39.438, + 45.75, + 0 + ], + "to": [ + 0, + 0, + 0 + ], + "ti": [ + 0, + 0, + 0 + ] + }, + { + "t": 35, + "s": [ + 39.438, + 56, + 0 + ] + } + ], + "ix": 2, + "x": "var $bm_rt;\nvar n, n, t, t, v, amp, freq, decay;\n$bm_rt = n = 0;\nif (numKeys > 0) {\n $bm_rt = n = nearestKey(time).index;\n if (key(n).time > time) {\n n--;\n }\n}\nif (n == 0) {\n $bm_rt = t = 0;\n} else {\n $bm_rt = t = $bm_sub(time, key(n).time);\n}\nif (n > 0) {\n v = velocityAtTime($bm_sub(key(n).time, $bm_div(thisComp.frameDuration, 10)));\n amp = 1;\n freq = 2;\n decay = 4;\n $bm_rt = $bm_sum(value, $bm_div($bm_mul($bm_mul(v, amp), Math.sin($bm_mul($bm_mul($bm_mul(freq, t), 2), Math.PI))), Math.exp($bm_mul(decay, t))));\n} else {\n $bm_rt = value;\n}" + }, + "a": { + "a": 0, + "k": [ + -14.253, + -9.753, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100, + 100 + ], + "ix": 6 + } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "d": 1, + "ty": "el", + "s": { + "a": 0, + "k": [ + 4.994, + 4.994 + ], + "ix": 2 + }, + "p": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 3 + }, + "nm": "Ellipse Path 1", + "mn": "ADBE Vector Shape - Ellipse", + "hd": false + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [ + 1, + 0.866666726505, + 0.188235309077, + 1 + ], + "ix": 4 + }, + "o": { + "a": 0, + "k": 100, + "ix": 5 + }, + "r": 1, + "bm": 0, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill", + "hd": false + }, + { + "ty": "tr", + "p": { + "a": 0, + "k": [ + -14.253, + -9.753 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 192.894, + 192.894 + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": 0, + "ix": 6 + }, + "o": { + "a": 0, + "k": 100, + "ix": 7 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 4 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 5 + }, + "nm": "Transform" + } + ], + "nm": "Ellipse 1", + "np": 3, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 0, + "op": 60, + "st": 0, + "bm": 0 + }, + { + "ddd": 0, + "ind": 2, + "ty": 4, + "nm": "Shape Layer 1", + "sr": 1, + "ks": { + "o": { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": { + "a": 1, + "k": [ + { + "i": { + "x": 0.667, + "y": 1 + }, + "o": { + "x": 0.333, + "y": 0 + }, + "t": 3.5, + "s": [ + 55.938, + 56, + 0 + ], + "to": [ + 0, + 0, + 0 + ], + "ti": [ + 0, + 0, + 0 + ] + }, + { + "i": { + "x": 0.667, + "y": 1 + }, + "o": { + "x": 0.333, + "y": 0 + }, + "t": 8.065, + "s": [ + 55.938, + 45.75, + 0 + ], + "to": [ + 0, + 0, + 0 + ], + "ti": [ + 0, + 0, + 0 + ] + }, + { + "i": { + "x": 0.667, + "y": 0.667 + }, + "o": { + "x": 0.167, + "y": 0.167 + }, + "t": 12.63, + "s": [ + 55.938, + 56, + 0 + ], + "to": [ + 0, + 0, + 0 + ], + "ti": [ + 0, + 0, + 0 + ] + }, + { + "i": { + "x": 0.667, + "y": 1 + }, + "o": { + "x": 0.333, + "y": 0 + }, + "t": 29.37, + "s": [ + 55.938, + 56, + 0 + ], + "to": [ + 0, + 0, + 0 + ], + "ti": [ + 0, + 0, + 0 + ] + }, + { + "i": { + "x": 0.667, + "y": 1 + }, + "o": { + "x": 0.333, + "y": 0 + }, + "t": 33.935, + "s": [ + 55.938, + 45.75, + 0 + ], + "to": [ + 0, + 0, + 0 + ], + "ti": [ + 0, + 0, + 0 + ] + }, + { + "t": 38.5, + "s": [ + 55.938, + 56, + 0 + ] + } + ], + "ix": 2, + "x": "var $bm_rt;\nvar n, n, t, t, v, amp, freq, decay;\n$bm_rt = n = 0;\nif (numKeys > 0) {\n $bm_rt = n = nearestKey(time).index;\n if (key(n).time > time) {\n n--;\n }\n}\nif (n == 0) {\n $bm_rt = t = 0;\n} else {\n $bm_rt = t = $bm_sub(time, key(n).time);\n}\nif (n > 0) {\n v = velocityAtTime($bm_sub(key(n).time, $bm_div(thisComp.frameDuration, 10)));\n amp = 1;\n freq = 2;\n decay = 4;\n $bm_rt = $bm_sum(value, $bm_div($bm_mul($bm_mul(v, amp), Math.sin($bm_mul($bm_mul($bm_mul(freq, t), 2), Math.PI))), Math.exp($bm_mul(decay, t))));\n} else {\n $bm_rt = value;\n}" + }, + "a": { + "a": 0, + "k": [ + -14.253, + -9.753, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100, + 100 + ], + "ix": 6 + } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "d": 1, + "ty": "el", + "s": { + "a": 0, + "k": [ + 4.994, + 4.994 + ], + "ix": 2 + }, + "p": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 3 + }, + "nm": "Ellipse Path 1", + "mn": "ADBE Vector Shape - Ellipse", + "hd": false + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [ + 0, + 0.752941236309, + 0.482352971096, + 1 + ], + "ix": 4 + }, + "o": { + "a": 0, + "k": 100, + "ix": 5 + }, + "r": 1, + "bm": 0, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill", + "hd": false + }, + { + "ty": "tr", + "p": { + "a": 0, + "k": [ + -14.253, + -9.753 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 192.894, + 192.894 + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": 0, + "ix": 6 + }, + "o": { + "a": 0, + "k": 100, + "ix": 7 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 4 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 5 + }, + "nm": "Transform" + } + ], + "nm": "Ellipse 1", + "np": 3, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": 0, + "op": 60, + "st": 4, + "bm": 0 + }, + { + "ddd": 0, + "ind": 3, + "ty": 4, + "nm": "Shape Layer 3", + "sr": 1, + "ks": { + "o": { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": { + "a": 1, + "k": [ + { + "i": { + "x": 0.667, + "y": 1 + }, + "o": { + "x": 0.333, + "y": 0 + }, + "t": 7, + "s": [ + 72.938, + 56, + 0 + ], + "to": [ + 0, + 0, + 0 + ], + "ti": [ + 0, + 0, + 0 + ] + }, + { + "i": { + "x": 0.667, + "y": 1 + }, + "o": { + "x": 0.333, + "y": 0 + }, + "t": 11.565, + "s": [ + 72.938, + 45.75, + 0 + ], + "to": [ + 0, + 0, + 0 + ], + "ti": [ + 0, + 0, + 0 + ] + }, + { + "i": { + "x": 0.667, + "y": 0.667 + }, + "o": { + "x": 0.167, + "y": 0.167 + }, + "t": 16.13, + "s": [ + 72.938, + 56, + 0 + ], + "to": [ + 0, + 0, + 0 + ], + "ti": [ + 0, + 0, + 0 + ] + }, + { + "i": { + "x": 0.667, + "y": 1 + }, + "o": { + "x": 0.333, + "y": 0 + }, + "t": 32.87, + "s": [ + 72.938, + 56, + 0 + ], + "to": [ + 0, + 0, + 0 + ], + "ti": [ + 0, + 0, + 0 + ] + }, + { + "i": { + "x": 0.667, + "y": 1 + }, + "o": { + "x": 0.333, + "y": 0 + }, + "t": 37.435, + "s": [ + 72.938, + 45.75, + 0 + ], + "to": [ + 0, + 0, + 0 + ], + "ti": [ + 0, + 0, + 0 + ] + }, + { + "t": 42, + "s": [ + 72.938, + 56, + 0 + ] + } + ], + "ix": 2, + "x": "var $bm_rt;\nvar n, n, t, t, v, amp, freq, decay;\n$bm_rt = n = 0;\nif (numKeys > 0) {\n $bm_rt = n = nearestKey(time).index;\n if (key(n).time > time) {\n n--;\n }\n}\nif (n == 0) {\n $bm_rt = t = 0;\n} else {\n $bm_rt = t = $bm_sub(time, key(n).time);\n}\nif (n > 0) {\n v = velocityAtTime($bm_sub(key(n).time, $bm_div(thisComp.frameDuration, 10)));\n amp = 1;\n freq = 2;\n decay = 4;\n $bm_rt = $bm_sum(value, $bm_div($bm_mul($bm_mul(v, amp), Math.sin($bm_mul($bm_mul($bm_mul(freq, t), 2), Math.PI))), Math.exp($bm_mul(decay, t))));\n} else {\n $bm_rt = value;\n}" + }, + "a": { + "a": 0, + "k": [ + -14.253, + -9.753, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100, + 100 + ], + "ix": 6 + } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "d": 1, + "ty": "el", + "s": { + "a": 0, + "k": [ + 4.994, + 4.994 + ], + "ix": 2 + }, + "p": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 3 + }, + "nm": "Ellipse Path 1", + "mn": "ADBE Vector Shape - Ellipse", + "hd": false + }, + { + "ty": "fl", + "c": { + "a": 0, + "k": [ + 0.937254901961, + 0.356862745098, + 0.494117647059, + 1 + ], + "ix": 4 + }, + "o": { + "a": 0, + "k": 100, + "ix": 5 + }, + "r": 1, + "bm": 0, + "nm": "Fill 1", + "mn": "ADBE Vector Graphic - Fill", + "hd": false + }, + { + "ty": "tr", + "p": { + "a": 0, + "k": [ + -14.253, + -9.753 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 0, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 192.894, + 192.894 + ], + "ix": 3 + }, + "r": { + "a": 0, + "k": 0, + "ix": 6 + }, + "o": { + "a": 0, + "k": 100, + "ix": 7 + }, + "sk": { + "a": 0, + "k": 0, + "ix": 4 + }, + "sa": { + "a": 0, + "k": 0, + "ix": 5 + }, + "nm": "Transform" + } + ], + "nm": "Ellipse 1", + "np": 3, + "cix": 2, + "bm": 0, + "ix": 1, + "mn": "ADBE Vector Group", + "hd": false + } + ], + "ip": -4, + "op": 60, + "st": 8, + "bm": 0 + }, + { + "ddd": 0, + "ind": 5, + "ty": 0, + "nm": "success", + "refId": "comp_0", + "sr": 1, + "ks": { + "o": { + "a": 0, + "k": 100, + "ix": 11 + }, + "r": { + "a": 0, + "k": 0, + "ix": 10 + }, + "p": { + "a": 0, + "k": [ + 56, + 56, + 0 + ], + "ix": 2 + }, + "a": { + "a": 0, + "k": [ + 56, + 56, + 0 + ], + "ix": 1 + }, + "s": { + "a": 0, + "k": [ + 100, + 100, + 100 + ], + "ix": 6 + } + }, + "ao": 0, + "w": 112, + "h": 112, + "ip": 60, + "op": 210, + "st": 60, + "bm": 0 + } + ], + "markers": [] +} \ No newline at end of file diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index fb80589e..03364be1 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -39,6 +39,12 @@ المكتبة لا يوجد شئ هنا + استيراد + استيراد كتاب EPUB + متاح فقط للكتب الداخلية. + جاري الاستيراد… يرجى الانتظار. + تم الاستيراد بنجاح! + فشل الاستيراد! افتح بواسطة مشاركة من خلال اقرأ @@ -54,7 +60,7 @@ آسف، لا يمكن أن تذهب إلى أعلى من ذلك! آسف، لا يمكن أن تذهب إلى أقل من ذلك! - تغيير نمط الخط + تغيير نمط الخط الفصول diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index c8947cfc..5535dcb9 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -38,6 +38,12 @@ Knihovna Nic tu není! + Importovat + Importovat knihu EPUB + Dostupné pouze pro interní knihy. + Probíhá importování… Prosím čekejte. + Úspěšně importováno! + Selhalo importování! Otevřít pomocí… Sdílet s… Číst @@ -53,7 +59,7 @@ Je nám líto, ale výš už to nejde! Je nám líto, ale níž už to nejde! - Change FontStyle + Change FontStyle Kapitoly diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 690e0dc4..3184545b 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -39,6 +39,12 @@ Bibliothek Nichts vorhanden! + Importieren + EPUB-Buch importieren + Nur für interne Bücher verfügbar. + Importiere… Bitte warten. + Erfolgreich importiert! + Import fehlgeschlagen! Öffnen mit … Teilen mit … Lesen @@ -54,7 +60,7 @@ Entschuldige bitte, größer kann es nicht dargestellt werden! Entschuldige bitte, kleiner kann es nicht dargestellt werden! - Schriftart ändern + Schriftart ändern Kapitel @@ -88,7 +94,7 @@ Buchdetails - Zurück gehen. + Zurückgehen. Dieses Buch teilen. Teilen mit … Lesen beginnen diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index e1ff2498..85f58294 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -39,6 +39,12 @@ Biblioteca ¡Nada por aquí! + Importar + Importar libro EPUB + Solo disponible para libros internos. + Importando… Por favor, espere. + ¡Importado con éxito! + ¡Error al importar! Abrir con… Compartir con… Leer @@ -54,7 +60,7 @@ Lo sentimos, no puede subir más Lo sentimos, no puede bajar más - Change FontStyle + Change FontStyle Capítulos diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index f9a9754a..a888c9d8 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -39,6 +39,12 @@ Libreria Non c\'è nulla qui! + Importa + Importa libro EPUB + Disponibile solo per libri interni. + Importazione in corso… Attendere prego. + Importato con successo! + Importazione fallita! Apri con… Condividi con… Leggi @@ -54,7 +60,7 @@ Spiacenti, non si può andare più in alto! Spiacenti, non si può andare più in basso! - Cambia Font + Cambia Font Capitoli diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 4fbec3a6..e4b87ca4 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -39,6 +39,12 @@ Biblioteca Nada por aqui! + Importar + Importar livro EPUB + Disponível apenas para livros internos. + Importando… Por favor, aguarde. + Importado com sucesso! + Falha ao importar! Abrir com… Compartilhar com… Ler @@ -54,7 +60,7 @@ Foi mal, não vai mais alto que isso! Foi mal, não vai mais baixo que isso! - Mudar estilo da fonte + Mudar estilo da fonte Capítulos diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index 980ac083..0fc0a643 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -39,6 +39,12 @@ Librărie Nimic aici! + Importă + Importează carte EPUB + Disponibil doar pentru cărți interne. + Se importă… Vă rugăm să așteptați. + Importat cu succes! + Import eșuat! Deschide cu… Împărtășiți cu… Citește @@ -54,7 +60,7 @@ Îmi pare rău, nu pot merge mai sus de atât! Îmi pare rău, nu pot merge mai jos de atât! - Schimbați FontStyle + Schimbați FontStyle Capitole diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 30378026..1673f387 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -39,6 +39,12 @@ Библиотека Здесь пусто! + Импорт + Импорт книги EPUB + Доступно только для внутренних книг. + Импорт… Пожалуйста, подождите. + Успешно импортировано! + Ошибка импорта! Читать в… Поделиться через… Читать @@ -54,7 +60,7 @@ Извините, достигнут максимальный размер шрифта! Извините, достигнут минимальный размер шрифта! - Стиль шрифта + Стиль шрифта Главы @@ -62,7 +68,7 @@ Установщик электронных книг "Сделано Shivam, с любовью ❤" Общие - Читать в... + Читать в… Открыть с помощью… Встроенная читалка Внешняя читалка diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 8c476057..969273a8 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -40,6 +40,12 @@ 书库 这里是空的 + 导入 + 导入 EPUB 书籍 + 仅适用于内部书籍。 + 正在导入… 请稍候。 + 成功导入! + 导入失败! 打开方式 分享 阅读 @@ -55,7 +61,7 @@ 抱歉,不能再大了 抱歉,不能再小了 - 改变字体风格 + 改变字体风格 章节 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d5a699ca..f5bd7288 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -39,6 +39,12 @@ Library Nothing Here! + Import + Import epub book + Only available for internal books. + Importing… Please wait. + Imported successfully! + Failed to import! Open with… Share with… Read @@ -54,7 +60,7 @@ Sorry, cannot go any higher than that! Sorry, cannot go any lower than that! - Change FontStyle + Change FontStyle Chapters @@ -99,10 +105,10 @@ About - A Free & Open Source Android application to download ebooks from the Project GutenBerg. + A Free & Open Source Android application to download ebooks from the Project Gutenberg. Developed By Starry Shivam - \@starry-shivam + starry@krsh.dev Project Contributors Meet our wonderful contributors. Useful Links