├── .gitmodules ├── app ├── .gitignore ├── src │ └── main │ │ ├── ic_launcher-web.png │ │ ├── res │ │ ├── mipmap-hdpi │ │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ │ └── ic_launcher.png │ │ ├── drawable-hdpi │ │ │ ├── ic_notification.png │ │ │ ├── ic_clear_white_24dp.png │ │ │ ├── ic_star_black_24dp.png │ │ │ ├── ic_star_white_24dp.png │ │ │ ├── ic_history_black_18dp.png │ │ │ ├── ic_history_white_24dp.png │ │ │ ├── ic_search_white_24dp.png │ │ │ ├── ic_settings_white_24dp.png │ │ │ ├── ic_visibility_black_18dp.png │ │ │ ├── ic_visibility_white_24dp.png │ │ │ └── ic_star_border_black_24dp.png │ │ ├── drawable-mdpi │ │ │ ├── ic_notification.png │ │ │ ├── ic_clear_white_24dp.png │ │ │ ├── ic_star_black_24dp.png │ │ │ ├── ic_star_white_24dp.png │ │ │ ├── ic_history_black_18dp.png │ │ │ ├── ic_history_white_24dp.png │ │ │ ├── ic_search_white_24dp.png │ │ │ ├── ic_settings_white_24dp.png │ │ │ ├── ic_visibility_black_18dp.png │ │ │ ├── ic_visibility_white_24dp.png │ │ │ └── ic_star_border_black_24dp.png │ │ ├── drawable-xhdpi │ │ │ ├── ic_notification.png │ │ │ ├── ic_star_black_24dp.png │ │ │ ├── ic_star_white_24dp.png │ │ │ ├── ic_clear_white_24dp.png │ │ │ ├── ic_search_white_24dp.png │ │ │ ├── ic_history_black_18dp.png │ │ │ ├── ic_history_white_24dp.png │ │ │ ├── ic_settings_white_24dp.png │ │ │ ├── ic_star_border_black_24dp.png │ │ │ ├── ic_visibility_black_18dp.png │ │ │ └── ic_visibility_white_24dp.png │ │ ├── drawable-xxhdpi │ │ │ ├── ic_notification.png │ │ │ ├── ic_clear_white_24dp.png │ │ │ ├── ic_star_black_24dp.png │ │ │ ├── ic_star_white_24dp.png │ │ │ ├── ic_history_black_18dp.png │ │ │ ├── ic_history_white_24dp.png │ │ │ ├── ic_search_white_24dp.png │ │ │ ├── ic_settings_white_24dp.png │ │ │ ├── ic_visibility_black_18dp.png │ │ │ ├── ic_visibility_white_24dp.png │ │ │ └── ic_star_border_black_24dp.png │ │ ├── drawable-xxxhdpi │ │ │ ├── ic_star_black_24dp.png │ │ │ ├── ic_star_white_24dp.png │ │ │ ├── ic_clear_white_24dp.png │ │ │ ├── ic_history_black_18dp.png │ │ │ ├── ic_history_white_24dp.png │ │ │ ├── ic_search_white_24dp.png │ │ │ ├── ic_settings_white_24dp.png │ │ │ ├── ic_star_border_black_24dp.png │ │ │ ├── ic_visibility_black_18dp.png │ │ │ └── ic_visibility_white_24dp.png │ │ ├── values │ │ │ ├── strings.xml │ │ │ ├── dimens.xml │ │ │ └── styles.xml │ │ ├── drawable │ │ │ ├── ic_history_black_18dp_tint.xml │ │ │ ├── ic_visibility_black_18dp_tint.xml │ │ │ ├── book_starred_selector.xml │ │ │ ├── ic_baseline_save_alt_24.xml │ │ │ ├── baseline_favorite_18.xml │ │ │ ├── baseline_favorite_24.xml │ │ │ ├── ic_baseline_settings_backup_restore_24.xml │ │ │ └── ic_baseline_duration_18.xml │ │ ├── values-w820dp │ │ │ └── dimens.xml │ │ ├── drawable-anydpi-v24 │ │ │ └── ic_notification.xml │ │ ├── layout │ │ │ ├── activity_play_audiobook.xml │ │ │ ├── database_management_fragment.xml │ │ │ ├── activity_books.xml │ │ │ ├── item_book.xml │ │ │ ├── activity_indexing.xml │ │ │ └── exo_styled_player_control_view.xml │ │ ├── menu │ │ │ └── list_menu.xml │ │ └── layout-sw600dp │ │ │ └── item_book.xml │ │ ├── java │ │ └── net │ │ │ └── bloople │ │ │ └── audiobooks │ │ │ ├── IndexingMetrics.kt │ │ │ ├── PlayerState.kt │ │ │ ├── Indexable.kt │ │ │ ├── AudiobooksApplication.kt │ │ │ ├── Util.kt │ │ │ ├── FileParser.kt │ │ │ ├── IndexViewModel.kt │ │ │ ├── DescriptionAdapter.kt │ │ │ ├── BooksSearcher.kt │ │ │ ├── DatabaseHelper.kt │ │ │ ├── IndexingTask.kt │ │ │ ├── DatabaseManagementFragment.kt │ │ │ ├── PlayAudiobookActivity.kt │ │ │ ├── BooksAdapter.kt │ │ │ ├── Book.kt │ │ │ ├── IndexingActivity.kt │ │ │ ├── CursorRecyclerAdapter.kt │ │ │ ├── BooksActivity.kt │ │ │ └── PlayerService.kt │ │ └── AndroidManifest.xml ├── proguard-rules.pro └── build.gradle ├── settings.gradle ├── gradlew ├── .gitignore ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties ├── LICENSE ├── gradlew.bat └── LICENSE-APACHE-2.0 /.gitmodules: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloopletech/audiobooks/master/gradlew -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea 5 | .DS_Store 6 | /build 7 | /captures 8 | /app/release/ 9 | -------------------------------------------------------------------------------- /app/src/main/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/ic_launcher-web.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloopletech/audiobooks/master/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-hdpi/ic_notification.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-mdpi/ic_notification.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-xhdpi/ic_notification.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_clear_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-hdpi/ic_clear_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_star_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-hdpi/ic_star_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_star_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-hdpi/ic_star_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_clear_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-mdpi/ic_clear_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_star_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-mdpi/ic_star_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_star_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-mdpi/ic_star_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_star_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-xhdpi/ic_star_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_star_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-xhdpi/ic_star_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-xxhdpi/ic_notification.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_history_black_18dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-hdpi/ic_history_black_18dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_history_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-hdpi/ic_history_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_search_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-hdpi/ic_search_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_history_black_18dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-mdpi/ic_history_black_18dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_history_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-mdpi/ic_history_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_search_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-mdpi/ic_search_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_clear_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-xhdpi/ic_clear_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_search_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-xhdpi/ic_search_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_clear_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-xxhdpi/ic_clear_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_star_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-xxhdpi/ic_star_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_star_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-xxhdpi/ic_star_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_star_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-xxxhdpi/ic_star_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_star_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-xxxhdpi/ic_star_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_settings_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-hdpi/ic_settings_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_visibility_black_18dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-hdpi/ic_visibility_black_18dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_visibility_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-hdpi/ic_visibility_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_settings_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-mdpi/ic_settings_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_visibility_black_18dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-mdpi/ic_visibility_black_18dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_visibility_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-mdpi/ic_visibility_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_history_black_18dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-xhdpi/ic_history_black_18dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_history_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-xhdpi/ic_history_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_settings_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-xhdpi/ic_settings_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_history_black_18dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-xxhdpi/ic_history_black_18dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_history_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-xxhdpi/ic_history_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_search_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-xxhdpi/ic_search_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_settings_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-xxhdpi/ic_settings_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_clear_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-xxxhdpi/ic_clear_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_history_black_18dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-xxxhdpi/ic_history_black_18dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_history_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-xxxhdpi/ic_history_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_search_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-xxxhdpi/ic_search_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_star_border_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-hdpi/ic_star_border_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_star_border_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-mdpi/ic_star_border_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_star_border_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-xhdpi/ic_star_border_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_visibility_black_18dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-xhdpi/ic_visibility_black_18dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_visibility_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-xhdpi/ic_visibility_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_visibility_black_18dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-xxhdpi/ic_visibility_black_18dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_visibility_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-xxhdpi/ic_visibility_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_settings_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-xxxhdpi/ic_settings_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_star_border_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-xxhdpi/ic_star_border_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_star_border_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-xxxhdpi/ic_star_border_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_visibility_black_18dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-xxxhdpi/ic_visibility_black_18dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_visibility_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bloopletech/audiobooks/master/app/src/main/res/drawable-xxxhdpi/ic_visibility_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Audiobooks 3 | Radio Streaming 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/java/net/bloople/audiobooks/IndexingMetrics.kt: -------------------------------------------------------------------------------- 1 | package net.bloople.audiobooks 2 | 3 | data class IndexingMetrics(var created: Int = 0, var updated: Int = 0, var skipped: Int = 0, var deleted: Int = 0) 4 | -------------------------------------------------------------------------------- /app/src/main/java/net/bloople/audiobooks/PlayerState.kt: -------------------------------------------------------------------------------- 1 | package net.bloople.audiobooks 2 | 3 | data class PlayerState( 4 | var window: Int = 0, 5 | var position: Long = 0, 6 | var whenReady: Boolean = true 7 | ) -------------------------------------------------------------------------------- /app/src/main/java/net/bloople/audiobooks/Indexable.kt: -------------------------------------------------------------------------------- 1 | package net.bloople.audiobooks 2 | 3 | interface Indexable { 4 | fun onIndexingProgress(progress: Int, max: Int) 5 | fun onIndexingComplete(metrics: IndexingMetrics) 6 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_history_black_18dp_tint.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_visibility_black_18dp_tint.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/book_starred_selector.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_save_alt_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_favorite_18.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_favorite_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_settings_backup_restore_24.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_duration_18.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/java/net/bloople/audiobooks/AudiobooksApplication.kt: -------------------------------------------------------------------------------- 1 | package net.bloople.audiobooks 2 | 3 | import android.annotation.SuppressLint 4 | import android.app.Application 5 | import android.content.Context 6 | import android.os.StrictMode 7 | 8 | class AudiobooksApplication: Application() { 9 | init { 10 | if(BuildConfig.DEBUG) StrictMode.enableDefaults() 11 | } 12 | 13 | override fun onCreate() { 14 | super.onCreate() 15 | context = applicationContext 16 | } 17 | 18 | companion object { 19 | @SuppressLint("StaticFieldLeak") 20 | lateinit var context: Context 21 | private set 22 | } 23 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-anydpi-v24/ic_notification.xml: -------------------------------------------------------------------------------- 1 | 7 | 11 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in C:\Android\sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_play_audiobook.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 16 | -------------------------------------------------------------------------------- /app/src/main/java/net/bloople/audiobooks/Util.kt: -------------------------------------------------------------------------------- 1 | package net.bloople.audiobooks 2 | 3 | import android.database.Cursor 4 | 5 | inline operator fun Cursor.get(columnName: String): T { 6 | val columnIndex = getColumnIndexOrThrow(columnName) 7 | return when (T::class) { 8 | ByteArray::class -> getBlob(columnIndex) as T 9 | Double::class -> getDouble(columnIndex) as T 10 | Float::class -> getFloat(columnIndex) as T 11 | Int::class -> getInt(columnIndex) as T 12 | Long::class -> getLong(columnIndex) as T 13 | Short::class -> getShort(columnIndex) as T 14 | String::class -> getString(columnIndex) as T 15 | Boolean::class -> (getInt(columnIndex) == 1) as T 16 | else -> throw IllegalArgumentException("Unsupported type") 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/res/layout/database_management_fragment.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 14 | 15 | 21 | 22 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m 13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 14 | 15 | # When configured, Gradle will run in incubating parallel mode. 16 | # This option should only be used with decoupled projects. More details, visit 17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 18 | # org.gradle.parallel=true 19 | android.enableJetifier=true 20 | android.useAndroidX=true 21 | org.gradle.unsafe.configuration-cache=true 22 | org.gradle.jvmargs=-XX:+UseParallelGC 23 | android.defaults.buildfeatures.buildconfig=true 24 | android.nonTransitiveRClass=false 25 | android.nonFinalResIds=false -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Brenton Fletcher (http://bloople.net i@bloople.net) 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | Some files in this software have an Apache 2.0 license header; 25 | those files are licensed under the Apache 2.0 license, instead of this license. -------------------------------------------------------------------------------- /app/src/main/java/net/bloople/audiobooks/FileParser.kt: -------------------------------------------------------------------------------- 1 | package net.bloople.audiobooks 2 | 3 | import android.media.MediaMetadataRetriever 4 | import android.util.Log 5 | import java.io.Closeable 6 | import java.io.File 7 | 8 | data class ParsedFile(val path: String, val title: String, val mtime: Long, val size: Long) 9 | 10 | class FileParser: Closeable { 11 | private val retriever = MediaMetadataRetriever() 12 | 13 | override fun close() { 14 | retriever.close() 15 | } 16 | 17 | fun parse(file: File): ParsedFile { 18 | return ParsedFile( 19 | file.canonicalPath, 20 | file.nameWithoutExtension, 21 | file.lastModified(), 22 | getDuration(file.canonicalPath).toLong() 23 | ) 24 | } 25 | 26 | private fun getDuration(path: String): Int { 27 | return try { 28 | retriever.setDataSource(path) 29 | val duration = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION) 30 | duration?.toInt() ?: 0 31 | } 32 | catch (e: RuntimeException) { 33 | Log.e(TAG, "Error while trying to extract duration for $path", e) 34 | 0 35 | } 36 | } 37 | 38 | companion object { 39 | private const val TAG = "FileParser" 40 | } 41 | } 42 | 43 | -------------------------------------------------------------------------------- /app/src/main/java/net/bloople/audiobooks/IndexViewModel.kt: -------------------------------------------------------------------------------- 1 | package net.bloople.audiobooks 2 | 3 | import android.app.Application 4 | import android.database.Cursor 5 | import androidx.lifecycle.AndroidViewModel 6 | import androidx.lifecycle.MutableLiveData 7 | import java.util.concurrent.Executors 8 | 9 | class IndexViewModel(application: Application) : AndroidViewModel(application) { 10 | private val searcher = BooksSearcher() 11 | 12 | val searchResults: MutableLiveData by lazy { 13 | MutableLiveData().also { resolve() } 14 | } 15 | 16 | val sorterDescription: MutableLiveData by lazy { 17 | MutableLiveData(searcher.description()) 18 | } 19 | 20 | val sortMethod: Int 21 | get() = searcher.sortMethod 22 | val sortDirectionAsc: Boolean 23 | get() = searcher.sortDirectionAsc 24 | 25 | fun setSearchText(searchText: String) { 26 | searcher.setSearchText(searchText) 27 | resolve() 28 | } 29 | 30 | fun setSort(sortMethod: Int, sortDirectionAsc: Boolean) { 31 | searcher.sortMethod = sortMethod 32 | searcher.sortDirectionAsc = sortDirectionAsc 33 | sorterDescription.value = searcher.description() 34 | resolve() 35 | } 36 | 37 | fun refresh() { 38 | resolve() 39 | } 40 | 41 | private fun resolve() { 42 | val service = Executors.newSingleThreadExecutor() 43 | service.submit { searchResults.postValue(searcher.search()) } 44 | } 45 | } -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 21 | 22 | 26 | 27 | 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /app/src/main/res/menu/list_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 9 | 13 | 18 | 23 | 28 | 33 | 34 | 35 | 36 | 41 | 42 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_books.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 21 | 31 | 32 | 43 | 48 | 49 | 50 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | 4 | android { 5 | compileSdk 33 6 | 7 | defaultConfig { 8 | applicationId "net.bloople.audiobooks" 9 | minSdk 33 10 | targetSdk 33 11 | final def version = 15 12 | versionCode version 13 | versionName version.toString() 14 | } 15 | compileOptions { 16 | sourceCompatibility JavaVersion.VERSION_17 17 | targetCompatibility JavaVersion.VERSION_17 18 | } 19 | buildTypes { 20 | release { 21 | minifyEnabled false 22 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 23 | } 24 | debug { 25 | applicationIdSuffix ".debug" 26 | } 27 | } 28 | lint { 29 | abortOnError false 30 | } 31 | namespace 'net.bloople.audiobooks' 32 | } 33 | 34 | dependencies { 35 | implementation fileTree(dir: 'libs', include: ['*.jar']) 36 | 37 | implementation "androidx.core:core-ktx:1.10.1" 38 | 39 | def appcompat_version = "1.6.1" 40 | def lifecycle_version = "2.6.1" 41 | def arch_version = "2.1.0" 42 | 43 | implementation "androidx.appcompat:appcompat:$appcompat_version" 44 | // For loading and tinting drawables on older versions of the platform 45 | implementation "androidx.appcompat:appcompat-resources:$appcompat_version" 46 | 47 | // ViewModel 48 | implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" 49 | // LiveData 50 | //implementation "androidx.lifecycle:lifecycle-livedata:$lifecycle_version" 51 | 52 | // Saved state module for ViewModel 53 | implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:$lifecycle_version" 54 | 55 | // Annotation processor 56 | implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version" 57 | 58 | implementation 'androidx.recyclerview:recyclerview:1.3.1' 59 | implementation 'com.google.android.material:material:1.9.0' 60 | 61 | implementation 'com.google.android.exoplayer:exoplayer-core:2.19.1' 62 | implementation 'com.google.android.exoplayer:exoplayer-dash:2.19.1' 63 | implementation 'com.google.android.exoplayer:exoplayer-ui:2.19.1' 64 | } 65 | repositories { 66 | mavenCentral() 67 | } 68 | -------------------------------------------------------------------------------- /app/src/main/java/net/bloople/audiobooks/DescriptionAdapter.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Filippo Beraldo. All rights reserved. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | //Changed by Brenton Fletcher 17 | 18 | @file:Suppress("DEPRECATION") 19 | 20 | package net.bloople.audiobooks 21 | 22 | import android.app.PendingIntent 23 | import android.content.Intent 24 | import android.graphics.Bitmap 25 | import com.google.android.exoplayer2.Player 26 | import com.google.android.exoplayer2.ui.PlayerNotificationManager 27 | 28 | /** 29 | * Created by Filippo Beraldo on 15/11/2018. 30 | * http://github.com/beraldofilippo 31 | */ 32 | class DescriptionAdapter(private val context: PlayerService) : 33 | PlayerNotificationManager.MediaDescriptionAdapter { 34 | 35 | override fun getCurrentContentTitle(player: Player): String = 36 | player.mediaMetadata.title.toString() 37 | 38 | override fun getCurrentContentText(player: Player): String? = null 39 | 40 | override fun getCurrentLargeIcon( 41 | player: Player, 42 | callback: PlayerNotificationManager.BitmapCallback 43 | ): Bitmap? = null 44 | 45 | /** 46 | * Specify the PendingIntent which is fired whenever there's a click on the notification. 47 | * The intent will start the player activity. 48 | * */ 49 | override fun createCurrentContentIntent(player: Player): PendingIntent? { 50 | val explicit = Book.idTo(Intent(context, PlayAudiobookActivity::class.java), context.bookId) 51 | explicit.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP 52 | 53 | return PendingIntent.getActivity( 54 | context, 55 | 1, 56 | explicit, 57 | PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE 58 | ) 59 | } 60 | } -------------------------------------------------------------------------------- /app/src/main/java/net/bloople/audiobooks/BooksSearcher.kt: -------------------------------------------------------------------------------- 1 | package net.bloople.audiobooks 2 | 3 | import android.database.Cursor 4 | import net.bloople.audiobooks.DatabaseHelper.instance 5 | import java.lang.IllegalStateException 6 | 7 | class BooksSearcher { 8 | private var searchText = "" 9 | var sortMethod = SORT_AGE 10 | var sortDirectionAsc = false 11 | 12 | fun setSearchText(inSearchText: String) { 13 | searchText = inSearchText 14 | } 15 | 16 | fun flipSortDirection() { 17 | sortDirectionAsc = !sortDirectionAsc 18 | } 19 | 20 | fun description(): String { 21 | return "Sorted by ${sortMethodDescription().lowercase()} ${sortDirectionDescription().lowercase()}" 22 | } 23 | 24 | private fun sortMethodDescription(): String { 25 | return when (sortMethod) { 26 | SORT_ALPHABETIC -> "Title" 27 | SORT_AGE -> "Published Date" 28 | SORT_SIZE -> "Size" 29 | SORT_LAST_OPENED -> "Last Opened At" 30 | SORT_OPENED_COUNT -> "Opened Count" 31 | SORT_STARRED -> "Starred" 32 | else -> throw IllegalStateException("sort_method not in valid range") 33 | } 34 | } 35 | 36 | private fun sortDirectionDescription(): String { 37 | return if (sortDirectionAsc) "Ascending" else "Descending" 38 | } 39 | 40 | private fun orderBy(): String { 41 | var orderBy = when (sortMethod) { 42 | SORT_ALPHABETIC -> "title" 43 | SORT_AGE -> "mtime" 44 | SORT_SIZE -> "size" 45 | SORT_LAST_OPENED -> "last_opened_at" 46 | SORT_STARRED -> "starred" 47 | SORT_OPENED_COUNT -> "opened_count" 48 | else -> throw IllegalArgumentException("Invalid sort method") 49 | } 50 | orderBy += if (sortDirectionAsc) " ASC" else " DESC" 51 | orderBy += ", title ASC" 52 | return orderBy 53 | } 54 | 55 | fun search(): Cursor { 56 | val db = instance() 57 | val cursor: Cursor = if (searchText != "") { 58 | db.query("books", null, "title LIKE ?", arrayOf("%$searchText%"), null, null, orderBy()) 59 | } 60 | else { 61 | db.query("books", null, null, null, null, null, orderBy()) 62 | } 63 | cursor.moveToFirst() 64 | return cursor 65 | } 66 | 67 | companion object { 68 | const val SORT_ALPHABETIC = 0 69 | const val SORT_AGE = 1 70 | const val SORT_SIZE = 2 71 | const val SORT_LAST_OPENED = 3 72 | const val SORT_STARRED = 4 73 | const val SORT_OPENED_COUNT = 5 74 | } 75 | } -------------------------------------------------------------------------------- /app/src/main/java/net/bloople/audiobooks/DatabaseHelper.kt: -------------------------------------------------------------------------------- 1 | package net.bloople.audiobooks 2 | 3 | import android.content.Context 4 | import android.database.sqlite.SQLiteDatabase 5 | import java.io.FileInputStream 6 | import java.io.FileOutputStream 7 | import java.io.InputStream 8 | import java.io.OutputStream 9 | 10 | internal object DatabaseHelper { 11 | private const val DB_NAME = "books" 12 | private lateinit var database: SQLiteDatabase 13 | 14 | private fun obtainDatabase(): SQLiteDatabase { 15 | val db = AudiobooksApplication.context.openOrCreateDatabase(DB_NAME, Context.MODE_PRIVATE, null) 16 | loadSchema(db) 17 | return db 18 | } 19 | 20 | private fun loadSchema(db: SQLiteDatabase) { 21 | db.execSQL("CREATE TABLE IF NOT EXISTS books ( " + 22 | "_id INTEGER PRIMARY KEY AUTOINCREMENT, " + 23 | "path TEXT, " + 24 | "title TEXT, " + 25 | "mtime INT DEFAULT 0, " + 26 | "size INTEGER DEFAULT 0, " + 27 | "last_opened_at INTEGER DEFAULT 0, " + 28 | "last_read_position INTEGER DEFAULT 0, " + 29 | "starred INTEGER DEFAULT 0" + 30 | ")") 31 | 32 | if (!hasColumn(db, "books", "opened_count")) { 33 | db.execSQL("ALTER TABLE books ADD COLUMN opened_count INTEGER") 34 | db.execSQL("UPDATE books SET opened_count=0") 35 | } 36 | } 37 | 38 | @JvmStatic 39 | @Synchronized 40 | fun instance(): SQLiteDatabase { 41 | if (!::database.isInitialized) { 42 | database = obtainDatabase() 43 | } 44 | return database 45 | } 46 | 47 | @JvmStatic 48 | @Synchronized 49 | fun deleteDatabase() { 50 | AudiobooksApplication.context.deleteDatabase(DB_NAME) 51 | database = obtainDatabase() 52 | } 53 | 54 | @JvmStatic 55 | @Synchronized 56 | fun exportDatabase(outputStream: OutputStream) { 57 | val path = instance().use { it.path } 58 | outputStream.use { FileInputStream(path).use { inputStream -> inputStream.copyTo(it) } } 59 | } 60 | 61 | @JvmStatic 62 | @Synchronized 63 | fun importDatabase(inputStream: InputStream) { 64 | val path = instance().use { it.path } 65 | inputStream.use { FileOutputStream(path).use { outputStream -> inputStream.copyTo(outputStream) } } 66 | database = obtainDatabase() 67 | } 68 | 69 | private fun hasColumn(db: SQLiteDatabase, tableName: String, columnName: String): Boolean { 70 | db.rawQuery("PRAGMA table_info($tableName)", null).use { 71 | while (it.moveToNext()) { 72 | if (it.getString(it.getColumnIndexOrThrow("name")).equals(columnName)) { 73 | return true 74 | } 75 | } 76 | } 77 | return false 78 | } 79 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/item_book.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 16 | 17 | 26 | 27 | 31 | 32 | 45 | 46 | 60 | 61 | 62 | 63 | 69 | -------------------------------------------------------------------------------- /app/src/main/java/net/bloople/audiobooks/IndexingTask.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("DEPRECATION") 2 | 3 | package net.bloople.audiobooks 4 | 5 | import android.os.AsyncTask 6 | import java.io.File 7 | import java.util.* 8 | 9 | @Suppress("OVERRIDE_DEPRECATION") 10 | internal class IndexingTask(private val indexable: Indexable) : 11 | AsyncTask() { 12 | private var progress = 0 13 | private var max = 0 14 | private val metrics = IndexingMetrics() 15 | private val fileParser = FileParser() 16 | 17 | override fun doInBackground(vararg params: String) { 18 | destroyDeleted() 19 | fileParser.use { indexDirectory(File(params[0])) } 20 | publishProgress(progress, max) 21 | } 22 | 23 | override fun onProgressUpdate(vararg args: Int?) { 24 | indexable.onIndexingProgress(args[0]!!, args[1]!!) 25 | } 26 | 27 | override fun onPostExecute(result: Unit?) { 28 | indexable.onIndexingComplete(metrics) 29 | } 30 | 31 | private fun destroyDeleted() { 32 | Book.findAll { 33 | max += it.count 34 | while (it.moveToNext()) { 35 | val book = Book(it) 36 | if (!book.file.exists()) { 37 | book.destroy() 38 | metrics.deleted++ 39 | } 40 | 41 | progress++ 42 | publishProgress(progress, max) 43 | } 44 | } 45 | } 46 | 47 | private fun indexDirectory(directory: File) { 48 | val (directories, files) = directory.listFiles()?.partition { it.isDirectory } ?: return 49 | for(d in directories) { indexDirectory(d) } 50 | 51 | files.filter { AUDIOBOOK_EXTENSIONS.contains(it.extension.lowercase()) }.also { 52 | max += it.size 53 | publishProgress(progress, max) 54 | }.forEach(::indexFile) 55 | } 56 | 57 | private fun indexFile(file: File) { 58 | val parsedFile by lazy { fileParser.parse(file) } 59 | val book = Book.findByPathOrNull(file.canonicalPath) 60 | if(book != null) updateBook(parsedFile, book) 61 | else createBook(parsedFile) 62 | 63 | progress++ 64 | publishProgress(progress, max) 65 | } 66 | 67 | private fun createBook(parsedFile: ParsedFile) { 68 | NewBook(parsedFile.path, parsedFile.title, parsedFile.mtime, parsedFile.size).save() 69 | metrics.created++ 70 | } 71 | 72 | private fun updateBook(parsedFile: ParsedFile, book: Book) { 73 | if(parsedFile.mtime == book.mtime) { 74 | metrics.skipped++ 75 | return 76 | } 77 | 78 | book.edit { 79 | title = parsedFile.title 80 | mtime = parsedFile.mtime 81 | size = parsedFile.size 82 | } 83 | metrics.updated++ 84 | } 85 | 86 | companion object { 87 | val AUDIOBOOK_EXTENSIONS = listOf("m4a", "mkv", "mp3", "mp4", "wav", "webm") 88 | } 89 | } -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_indexing.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 19 | 26 | 27 | 28 | 33 | 40 | 41 | 45 | 46 |