├── .gitignore ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── coroutines.pro ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── na │ │ └── komi │ │ └── kodesh │ │ └── ExampleInstrumentedTest.kt │ ├── main │ ├── AndroidManifest.xml │ ├── assets │ │ ├── databases │ │ │ ├── KJV1611.txt │ │ │ ├── ParagraphList.txt │ │ │ ├── RedLetters.txt │ │ │ └── kjv-pce-v2.db │ │ └── fonts │ │ │ ├── GentiumPlus-I.otf │ │ │ ├── GentiumPlus-R.otf │ │ │ └── Merriweather-Black.otf │ ├── ic_launcher-web.png │ ├── kotlin │ │ ├── androidx │ │ │ └── sqlite │ │ │ │ └── db │ │ │ │ └── framework │ │ │ │ ├── AssetSQLiteOpenHelper.kt │ │ │ │ └── AssetSQLiteOpenHelperFactory.kt │ │ ├── com │ │ │ └── readystatesoftware │ │ │ │ └── sqliteasset │ │ │ │ ├── SQLiteAssetHelper.kt │ │ │ │ ├── Utils.kt │ │ │ │ └── VersionComparator.kt │ │ └── na │ │ │ └── komi │ │ │ └── kodesh │ │ │ ├── Application.kt │ │ │ ├── model │ │ │ ├── ApplicationDatabase.kt │ │ │ ├── Bible.kt │ │ │ ├── MainDao.kt │ │ │ ├── MainRepository.kt │ │ │ └── Preferences.kt │ │ │ ├── ui │ │ │ ├── about │ │ │ │ └── AboutFragment.kt │ │ │ ├── find │ │ │ │ └── FindInPageFragment.kt │ │ │ ├── font │ │ │ │ └── FontDialogFragment.kt │ │ │ ├── internal │ │ │ │ ├── BaseActivity.kt │ │ │ │ ├── BaseAdapter.kt │ │ │ │ ├── BaseFragment.kt │ │ │ │ ├── BaseFragment2.kt │ │ │ │ ├── BaseKatanaFragment.kt │ │ │ │ ├── BasePreferenceFragment.kt │ │ │ │ ├── BottomSheetBehavior2.kt │ │ │ │ ├── ExtendedBottomSheetDialogFragment.kt │ │ │ │ ├── FragmentToolbar.kt │ │ │ │ ├── HideBehavior.kt │ │ │ │ ├── ItemDecorator.kt │ │ │ │ ├── LinearLayoutManager2.kt │ │ │ │ └── MultilineText.kt │ │ │ ├── main │ │ │ │ ├── MainActivity.kt │ │ │ │ ├── MainChildAdapter.kt │ │ │ │ ├── MainComponent.kt │ │ │ │ ├── MainFragment.kt │ │ │ │ ├── MainModule.kt │ │ │ │ ├── MainPageAdapter.kt │ │ │ │ ├── MainViewModel.kt │ │ │ │ └── MainViewModelFactory.kt │ │ │ ├── navigate │ │ │ │ └── NavigateDialogFragment.kt │ │ │ ├── preface │ │ │ │ ├── PrefaceFragment.kt │ │ │ │ └── PrefaceImageFragment.kt │ │ │ ├── search │ │ │ │ ├── SearchAdapter.kt │ │ │ │ └── SearchFragment.kt │ │ │ ├── setting │ │ │ │ └── SettingsFragment.kt │ │ │ ├── styling │ │ │ │ └── StylingDialogFragment.kt │ │ │ └── widget │ │ │ │ ├── BaselineGridTextView.kt │ │ │ │ ├── LayoutedTextView.kt │ │ │ │ ├── NestedRecyclerView.kt │ │ │ │ ├── NumberPicker2.kt │ │ │ │ ├── PullDismissLayout.kt │ │ │ │ ├── ViewPager3.kt │ │ │ │ └── ZoomNestedScollView.kt │ │ │ └── util │ │ │ ├── CoroutineUtils.kt │ │ │ ├── Executors.kt │ │ │ ├── InjectorUtils.kt │ │ │ ├── KatanaExt.kt │ │ │ ├── SystemConfig.kt │ │ │ ├── ThemeUtils.kt │ │ │ ├── TranslucentBarManager.kt │ │ │ ├── Utils.kt │ │ │ ├── ViewExt.kt │ │ │ ├── livedata │ │ │ ├── LiveDataExt.kt │ │ │ └── NonNullMediatorLiveData.kt │ │ │ ├── page │ │ │ ├── Fonts.kt │ │ │ ├── Formatting.kt │ │ │ ├── LongClickLinkMovementMethod.kt │ │ │ ├── LongClickableSpanClass.kt │ │ │ ├── MySpannableFactory.kt │ │ │ └── diff_match_patch.kt │ │ │ └── text │ │ │ ├── CustomMovementMethod.kt │ │ │ ├── DropCapSpan.kt │ │ │ ├── LettrineLeadingMarginSpan2.kt │ │ │ ├── LineOverlapSpan.kt │ │ │ └── StringExt.kt │ └── res │ │ ├── animator │ │ ├── lift_on_touch.xml │ │ └── shr_next_button_state_list_anim.xml │ │ ├── color │ │ ├── chip_color.xml │ │ ├── nav_item_text.xml │ │ └── nav_item_text_dark.xml │ │ ├── drawable │ │ ├── dedicatory.jpg │ │ ├── ic_android.xml │ │ ├── ic_crown.xml │ │ ├── ic_down.xml │ │ ├── ic_email.xml │ │ ├── ic_filter.xml │ │ ├── ic_find_in_page.xml │ │ ├── ic_font_outline.xml │ │ ├── ic_font_size.xml │ │ ├── ic_font_style.png │ │ ├── ic_format_text.xml │ │ ├── ic_github.xml │ │ ├── ic_go_to.xml │ │ ├── ic_info.xml │ │ ├── ic_inject.png │ │ ├── ic_paragraph.png │ │ ├── ic_script.xml │ │ ├── ic_seek_bar_tick.xml │ │ ├── ic_settings.xml │ │ ├── ic_share.xml │ │ ├── ic_skateboard.xml │ │ ├── ic_star.xml │ │ ├── ic_study.png │ │ ├── ic_text_fields.xml │ │ ├── ic_text_format.xml │ │ ├── ic_time.xml │ │ ├── ic_up.xml │ │ ├── kod_branded_menu.xml │ │ ├── kod_close_menu.xml │ │ ├── kod_filter.xml │ │ ├── kod_logo.xml │ │ ├── kod_menu.xml │ │ ├── kod_search.xml │ │ ├── nav_item_background.xml │ │ ├── nav_item_background_black.xml │ │ ├── nav_item_background_light.xml │ │ ├── ripple.xml │ │ ├── scrollbar_color.xml │ │ ├── shape_divider_flexbox.xml │ │ └── style_circular_button.xml │ │ ├── font │ │ ├── rubik.xml │ │ └── rubik_regular.ttf │ │ ├── layout │ │ ├── activity_main.xml │ │ ├── card_item.xml │ │ ├── card_item_single.xml │ │ ├── fragment_about.xml │ │ ├── fragment_dialog_container.xml │ │ ├── fragment_dialog_font.xml │ │ ├── fragment_dialog_menu.xml │ │ ├── fragment_dialog_navigate.xml │ │ ├── fragment_dialog_preface_image.xml │ │ ├── fragment_find_in_page.xml │ │ ├── fragment_main.xml │ │ ├── fragment_navigate.xml │ │ ├── fragment_preface.xml │ │ ├── fragment_search.xml │ │ ├── layout_toolbar.xml │ │ ├── preferences_category.xml │ │ ├── recyclerview_child.xml │ │ ├── recyclerview_child_content_main.xml │ │ ├── recyclerview_content_main.xml │ │ ├── recyclerview_content_search.xml │ │ ├── recyclerview_standard.xml │ │ └── view_upgrade.xml │ │ ├── menu │ │ ├── menu_drawer.xml │ │ ├── shr_toolbar_menu.xml │ │ ├── toolbar_barebones.xml │ │ └── toolbar_menu_main.xml │ │ ├── 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 │ │ ├── values-large │ │ └── bools.xml │ │ ├── values-sw600dp │ │ ├── dimens.xml │ │ └── internal.xml │ │ ├── values-v19 │ │ └── dimens.xml │ │ ├── values │ │ ├── arrays.xml │ │ ├── attrs.xml │ │ ├── attrs_baseline_grid_text_view.xml │ │ ├── bools.xml │ │ ├── bottom_sheet_width.xml │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── internal.xml │ │ ├── strings.xml │ │ └── styles.xml │ │ └── xml │ │ ├── my_backup_rules.xml │ │ ├── preferences.xml │ │ ├── preferences_default.xml │ │ └── styling_preferences.xml │ └── test │ └── java │ └── na │ └── komi │ └── kodesh │ └── ExampleUnitTest.kt ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/* 5 | /.idea 6 | /.idea/caches 7 | /.idea/libraries 8 | /.idea/modules.xml 9 | /.idea/workspace.xml 10 | /.idea/navEditor.xml 11 | /.idea/assetWizardSettings.xml 12 | .DS_Store 13 | /build 14 | /captures 15 | .externalNativeBuild 16 | .cxx 17 | 18 | *iml 19 | */release 20 | */build 21 | /build 22 | /app/*.iml 23 | /app/release 24 | 25 | # Built application files 26 | *.apk 27 | *.ap_ 28 | 29 | # Files for the ART/Dalvik VM 30 | *.dex 31 | 32 | # Java class files 33 | *.class 34 | 35 | # Generated files 36 | bin/ 37 | gen/ 38 | out/ 39 | 40 | # Gradle files 41 | .gradle/ 42 | build/ 43 | 44 | # Local configuration file (sdk path, etc) 45 | local.properties 46 | 47 | # Proguard folder generated by Eclipse 48 | proguard/ 49 | 50 | # Log Files 51 | *.log 52 | 53 | # Android Studio Navigation editor temp files 54 | .navigation/ 55 | 56 | # Android Studio captures folder 57 | captures/ 58 | 59 | # Intellij 60 | *.iml 61 | .idea/workspace.xml 62 | .idea/tasks.xml 63 | .idea/gradle.xml 64 | .idea/dictionaries 65 | .idea/libraries 66 | 67 | # Keystore files 68 | # Uncomment the following line if you do not want to check your keystore files in. 69 | #*.jks 70 | 71 | # External native build folder generated in Android Studio 2.2 and later 72 | .externalNativeBuild 73 | 74 | # Google Services (e.g. APIs or Firebase) 75 | google-services.json 76 | 77 | # Freeline 78 | freeline.py 79 | freeline/ 80 | freeline_project_description.json -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

Kodesh

An app for reading the scriptures.
2 | 3 | Get it on Google Play 6 | 7 |


8 | 9 | # Features 10 | 11 | - Verse stylings 12 | - KJV mode 13 | - Drop cap 14 | - Red letter 15 | - Verse number 16 | - Search verses 17 | - Find in page 18 | - Night mode 19 | - Pinch to zoom 20 | - Lightweight and fast 21 | - Absolutely no permissions n eeded 22 | - Completely offline 23 | - Clean and elegant Material design 24 | - Translations from the [PCE](http://www.bibleprotector.com/) 25 | 26 | # 27 | See the [wiki](https://github.com/inshiro/Kodesh/wiki) for some developer insights! 28 | 29 | License 30 | ------- 31 | 32 | Copyright 2019 inshiro 33 | 34 | Licensed under the Apache License, Version 2.0 (the "License"); 35 | you may not use this file except in compliance with the License. 36 | You may obtain a copy of the License at 37 | 38 | http://www.apache.org/licenses/LICENSE-2.0 39 | 40 | Unless required by applicable law or agreed to in writing, software 41 | distributed under the License is distributed on an "AS IS" BASIS, 42 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 43 | See the License for the specific language governing permissions and 44 | limitations under the License. -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/coroutines.pro: -------------------------------------------------------------------------------- 1 | 2 | # ServiceLoader support 3 | -keepnames class kotlinx.coroutines.internal.MainDispatcherFactory {} 4 | -keepnames class kotlinx.coroutines.CoroutineExceptionHandler {} 5 | -keepnames class kotlinx.coroutines.android.AndroidExceptionPreHandler {} 6 | -keepnames class kotlinx.coroutines.android.AndroidDispatcherFactory {} 7 | 8 | # Most of volatile fields are updated with AFU and should not be mangled 9 | -keepclassmembernames class kotlinx.** { 10 | volatile ; 11 | } 12 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | 23 | #-assumenosideeffects class kotlin.jvm.internal.Intrinsics { 24 | # static void checkParameterIsNotNull(java.lang.Object, java.lang.String); 25 | #} 26 | 27 | #-keepclasseswithmembers class **.R$* { 28 | # public static final int define_*; 29 | #} 30 | #### No obfuscation 31 | #-dontobfuscate 32 | # -keep public class na.komi.kodesh.ui.** { *; } 33 | # -keep public class na.komi.kodesh.util.skate.** { *; } 34 | # -keepclassmembers class na.komi.kodesh.util.skate.** { *; } 35 | -keep public class na.komi.kodesh.ui.main.** { *; } 36 | -keepclassmembers class na.komi.kodesh.ui.main.** { *; } 37 | -keep public class na.komi.kodesh.ui.find.** { *; } 38 | -keepclassmembers class na.komi.kodesh.ui.find.** { *; } 39 | -------------------------------------------------------------------------------- /app/src/androidTest/java/na/komi/kodesh/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package na.komi.kodesh 2 | 3 | import androidx.test.ext.junit.runners.AndroidJUnit4 4 | import androidx.test.platform.app.InstrumentationRegistry 5 | import org.junit.Assert.assertEquals 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | /** 10 | * Instrumented test, which will execute on an Android device. 11 | * 12 | * See [testing documentation](http://d.android.com/tools/testing). 13 | */ 14 | @RunWith(AndroidJUnit4::class) 15 | class ExampleInstrumentedTest { 16 | @Test 17 | fun useAppContext() { 18 | // Context of the app under test. 19 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext 20 | assertEquals("na.komi.kodesh", appContext.packageName) 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 15 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 30 | -------------------------------------------------------------------------------- /app/src/main/assets/databases/kjv-pce-v2.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inshiro/kodesh/311fe579feb9948086e0523226c11a69dac26133/app/src/main/assets/databases/kjv-pce-v2.db -------------------------------------------------------------------------------- /app/src/main/assets/fonts/GentiumPlus-I.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inshiro/kodesh/311fe579feb9948086e0523226c11a69dac26133/app/src/main/assets/fonts/GentiumPlus-I.otf -------------------------------------------------------------------------------- /app/src/main/assets/fonts/GentiumPlus-R.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inshiro/kodesh/311fe579feb9948086e0523226c11a69dac26133/app/src/main/assets/fonts/GentiumPlus-R.otf -------------------------------------------------------------------------------- /app/src/main/assets/fonts/Merriweather-Black.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inshiro/kodesh/311fe579feb9948086e0523226c11a69dac26133/app/src/main/assets/fonts/Merriweather-Black.otf -------------------------------------------------------------------------------- /app/src/main/ic_launcher-web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inshiro/kodesh/311fe579feb9948086e0523226c11a69dac26133/app/src/main/ic_launcher-web.png -------------------------------------------------------------------------------- /app/src/main/kotlin/androidx/sqlite/db/framework/AssetSQLiteOpenHelper.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2016 The Android Open Source Project 3 | * Modifications (c) 2017 CommonsWare, LLC 4 | * 5 | * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * you may not use this file except in compliance with the License. 7 | * You may obtain a copy of the License at 8 | * 9 | * http://www.apache.org/licenses/LICENSE-2.0 10 | * 11 | * Unless required by applicable law or agreed to in writing, software 12 | * distributed under the License is distributed on an "AS IS" BASIS, 13 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * See the License for the specific language governing permissions and 15 | * limitations under the License. 16 | */ 17 | 18 | package androidx.sqlite.db.framework 19 | 20 | import androidx.sqlite.db.SupportSQLiteDatabase 21 | import androidx.sqlite.db.SupportSQLiteOpenHelper 22 | import android.content.Context 23 | import android.database.sqlite.SQLiteDatabase 24 | import android.os.Build 25 | import androidx.annotation.RequiresApi 26 | import com.readystatesoftware.sqliteasset.SQLiteAssetHelper 27 | 28 | internal class AssetSQLiteOpenHelper(context: Context, name: String, version: Int, 29 | callback: SupportSQLiteOpenHelper.Callback) : SupportSQLiteOpenHelper { 30 | private val delegate: AssetHelper 31 | 32 | init { 33 | delegate = createDelegate(context, name, version, callback) 34 | } 35 | 36 | private fun createDelegate(context: Context, name: String, 37 | version: Int, callback: SupportSQLiteOpenHelper.Callback): AssetHelper { 38 | return object : AssetHelper(context, name, version) { 39 | override fun onCreate(db: SQLiteDatabase) { 40 | wrappedDb = FrameworkSQLiteDatabase(db) 41 | callback.onCreate(wrappedDb) 42 | } 43 | 44 | override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, 45 | newVersion: Int) { 46 | callback.onUpgrade(getWrappedDb(db), oldVersion, 47 | newVersion) 48 | } 49 | 50 | override fun onConfigure(db: SQLiteDatabase) { 51 | callback.onConfigure(getWrappedDb(db)) 52 | } 53 | 54 | override fun onDowngrade(db: SQLiteDatabase, oldVersion: Int, 55 | newVersion: Int) { 56 | callback.onDowngrade(getWrappedDb(db), oldVersion, newVersion) 57 | } 58 | 59 | override fun onOpen(db: SQLiteDatabase) { 60 | callback.onOpen(getWrappedDb(db)) 61 | } 62 | } 63 | } 64 | 65 | override fun getDatabaseName(): String { 66 | return delegate.databaseName 67 | } 68 | 69 | @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN) 70 | override fun setWriteAheadLoggingEnabled(enabled: Boolean) { 71 | delegate.setWriteAheadLoggingEnabled(enabled) 72 | } 73 | 74 | override fun getWritableDatabase(): SupportSQLiteDatabase { 75 | return delegate.writableSupportDatabase 76 | } 77 | 78 | override fun getReadableDatabase(): SupportSQLiteDatabase { 79 | return delegate.readableSupportDatabase 80 | } 81 | 82 | override fun close() { 83 | delegate.close() 84 | } 85 | 86 | internal abstract class AssetHelper(context: Context, name: String, version: Int) : SQLiteAssetHelper(context, name, null, null, version, null) { 87 | var wrappedDb: FrameworkSQLiteDatabase? = null 88 | 89 | val writableSupportDatabase: SupportSQLiteDatabase 90 | get() { 91 | val db = super.getWritableDatabase() 92 | return getWrappedDb(db) 93 | } 94 | 95 | val readableSupportDatabase: SupportSQLiteDatabase 96 | get() { 97 | val db = super.getReadableDatabase() 98 | return getWrappedDb(db) 99 | } 100 | 101 | fun getWrappedDb(sqLiteDatabase: SQLiteDatabase): FrameworkSQLiteDatabase { 102 | if (wrappedDb == null) { 103 | wrappedDb = FrameworkSQLiteDatabase(sqLiteDatabase) 104 | } 105 | return wrappedDb as FrameworkSQLiteDatabase 106 | } 107 | 108 | @Synchronized 109 | override fun close() { 110 | super.close() 111 | wrappedDb = null 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /app/src/main/kotlin/androidx/sqlite/db/framework/AssetSQLiteOpenHelperFactory.kt: -------------------------------------------------------------------------------- 1 | /*** 2 | * Copyright (c) 2017 CommonsWare, LLC 3 | * Licensed under the Apache License, Version 2.0 (the "License"); you may not 4 | * use this file except in compliance with the License. You may obtain a copy 5 | * of the License at http://www.apache.org/licenses/LICENSE-2.0. Unless required 6 | * by applicable law or agreed to in writing, software distributed under the 7 | * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS 8 | * OF ANY KIND, either express or implied. See the License for the specific 9 | * language governing permissions and limitations under the License. 10 | * 11 | * Covered in detail in the book _Android's Architecture Components_ 12 | * https://commonsware.com/AndroidArch 13 | */ 14 | 15 | package androidx.sqlite.db.framework 16 | 17 | import androidx.sqlite.db.SupportSQLiteOpenHelper 18 | 19 | class AssetSQLiteOpenHelperFactory : SupportSQLiteOpenHelper.Factory { 20 | override fun create(configuration: SupportSQLiteOpenHelper.Configuration): SupportSQLiteOpenHelper { 21 | return AssetSQLiteOpenHelper(configuration.context, configuration.name!!, 22 | configuration.callback.version, configuration.callback) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/readystatesoftware/sqliteasset/Utils.kt: -------------------------------------------------------------------------------- 1 | package com.readystatesoftware.sqliteasset 2 | 3 | import android.util.Log 4 | 5 | import java.io.IOException 6 | import java.io.InputStream 7 | import java.io.OutputStream 8 | import java.util.ArrayList 9 | import java.util.Scanner 10 | import java.util.zip.ZipEntry 11 | import java.util.zip.ZipInputStream 12 | 13 | internal object Utils { 14 | 15 | private val TAG = SQLiteAssetHelper::class.java.simpleName 16 | 17 | fun splitSqlScript(script: String, delim: Char): List { 18 | val statements = ArrayList() 19 | var sb = StringBuilder() 20 | var inLiteral = false 21 | val content = script.toCharArray() 22 | for (i in 0 until script.length) { 23 | if (content[i] == '"') { 24 | inLiteral = !inLiteral 25 | } 26 | if (content[i] == delim && !inLiteral) { 27 | if (sb.length > 0) { 28 | statements.add(sb.toString().trim { it <= ' ' }) 29 | sb = StringBuilder() 30 | } 31 | } else { 32 | sb.append(content[i]) 33 | } 34 | } 35 | if (sb.length > 0) { 36 | statements.add(sb.toString().trim { it <= ' ' }) 37 | } 38 | return statements 39 | } 40 | 41 | @Throws(IOException::class) 42 | fun writeExtractedFileToDisk(`in`: InputStream, outs: OutputStream) { 43 | val buffer = ByteArray(1024) 44 | var length: Int = 0 45 | //while ((length = `in`.read(buffer)) > 0) { 46 | while ({ length = `in`.read(buffer); length }() > 0) { 47 | outs.write(buffer, 0, length) 48 | } 49 | outs.flush() 50 | outs.close() 51 | `in`.close() 52 | } 53 | 54 | @Throws(IOException::class) 55 | fun getFileFromZip(zipFileStream: InputStream): ZipInputStream? { 56 | val zis = ZipInputStream(zipFileStream) 57 | var ze: ZipEntry? = null 58 | //while ((ze = zis.nextEntry() != null) { 59 | while ({ ze = zis.nextEntry; ze }() != null) { 60 | Log.w(TAG, "extracting file: '" + ze?.name + "'...") 61 | return zis 62 | } 63 | return null 64 | } 65 | 66 | fun convertStreamToString(`is`: InputStream): String { 67 | return Scanner(`is`).useDelimiter("\\A").next() 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/readystatesoftware/sqliteasset/VersionComparator.kt: -------------------------------------------------------------------------------- 1 | package com.readystatesoftware.sqliteasset 2 | 3 | import android.util.Log 4 | 5 | import java.util.Comparator 6 | import java.util.regex.Matcher 7 | import java.util.regex.Pattern 8 | 9 | import com.readystatesoftware.sqliteasset.SQLiteAssetHelper.SQLiteAssetException 10 | 11 | /** 12 | * Compare paths by their upgrade version numbers, instead of using 13 | * alphanumeric comparison on plain file names. This prevents the upgrade 14 | * scripts from being applied out of order when they first move to double-, 15 | * triple-, etc. digits. 16 | * 17 | * 18 | * For example, this fixes an upgrade that would apply 2 different upgrade 19 | * files from version 9 to 11 (`..._updated_9_10` and 20 | * `..._updated_10_11`) from using the *incorrect* 21 | * alphanumeric order of `10_11` before `9_10`. 22 | * 23 | */ 24 | internal class VersionComparator : Comparator { 25 | 26 | private val pattern = Pattern 27 | .compile(".*_upgrade_([0-9]+)-([0-9]+).*") 28 | 29 | /** 30 | * Compares the two specified upgrade script strings to determine their 31 | * relative ordering considering their two version numbers. Assumes all 32 | * database names used are the same, as this function only compares the 33 | * two version numbers. 34 | * 35 | * @param file0 36 | * an upgrade script file name 37 | * @param file1 38 | * a second upgrade script file name to compare with file0 39 | * @return an integer < 0 if file0 should be applied before file1, 0 if 40 | * they are equal (though that shouldn't happen), and > 0 if 41 | * file0 should be applied after file1. 42 | * 43 | * @exception SQLiteAssetException 44 | * thrown if the strings are not in the correct upgrade 45 | * script format of: 46 | * `databasename_fromVersionInteger_toVersionInteger` 47 | */ 48 | override fun compare(file0: String, file1: String): Int { 49 | val m0 = pattern.matcher(file0) 50 | val m1 = pattern.matcher(file1) 51 | 52 | if (!m0.matches()) { 53 | Log.w(TAG, "could not parse upgrade script file: $file0") 54 | throw SQLiteAssetException("Invalid upgrade script file") 55 | } 56 | 57 | if (!m1.matches()) { 58 | Log.w(TAG, "could not parse upgrade script file: $file1") 59 | throw SQLiteAssetException("Invalid upgrade script file") 60 | } 61 | 62 | val v0_from = Integer.valueOf(m0.group(1)) 63 | val v1_from = Integer.valueOf(m1.group(1)) 64 | val v0_to = Integer.valueOf(m0.group(2)) 65 | val v1_to = Integer.valueOf(m1.group(2)) 66 | 67 | if (v0_from == v1_from) { 68 | // 'from' versions match for both; check 'to' version next 69 | 70 | if (v0_to == v1_to) { 71 | return 0 72 | } 73 | 74 | return if (v0_to < v1_to) -1 else 1 75 | } 76 | 77 | return if (v0_from < v1_from) -1 else 1 78 | } 79 | 80 | companion object { 81 | 82 | private val TAG = SQLiteAssetHelper::class.java.simpleName 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /app/src/main/kotlin/na/komi/kodesh/Application.kt: -------------------------------------------------------------------------------- 1 | package na.komi.kodesh 2 | 3 | import android.app.Application 4 | import android.content.Context 5 | import android.content.res.Resources 6 | import com.squareup.leakcanary.LeakCanary 7 | import na.komi.kodesh.model.Preferences 8 | import na.komi.skate.core.Skate 9 | import na.komi.skate.core.log.SkateLogger 10 | import org.rewedigital.katana.Katana 11 | import org.rewedigital.katana.android.AndroidKatanaLogger 12 | import org.rewedigital.katana.android.environment.AndroidEnvironmentContext 13 | import org.rewedigital.katana.android.modules.APPLICATION_CONTEXT 14 | import org.rewedigital.katana.createModule 15 | import org.rewedigital.katana.dsl.compact.factory 16 | import org.rewedigital.katana.dsl.get 17 | 18 | val Prefs by lazy { na.komi.kodesh.Application.preferences } 19 | 20 | /** 21 | * This module may provide Android specific classes like [Resources], 22 | * [android.content.SharedPreferences], system services etc. 23 | * 24 | * @see org.rewedigital.katana.android.modules.createApplicationModule 25 | */ 26 | val androidModule by lazy { 27 | createModule { 28 | factory { get(APPLICATION_CONTEXT).resources } 29 | } 30 | } 31 | 32 | class Application : Application() { 33 | companion object { 34 | lateinit var instance: Application 35 | lateinit var preferences: Preferences 36 | var init: Boolean = false 37 | //lateinit var applicationComponent: Component 38 | } 39 | 40 | override fun onCreate() { 41 | super.onCreate() 42 | instance = this 43 | preferences = Preferences(this) 44 | 45 | setupKatana() 46 | 47 | if (!BuildConfig.DEBUG) return 48 | Skate.logger = SkateLogger 49 | if (LeakCanary.isInAnalyzerProcess(this)) { 50 | // This process is dedicated to LeakCanary for heap analysis. 51 | // You should not init your app in this process. 52 | return 53 | } 54 | LeakCanary.install(this) 55 | } 56 | 57 | 58 | private fun setupKatana() { 59 | // Installing logger for Katana 60 | if (BuildConfig.DEBUG) 61 | Katana.logger = AndroidKatanaLogger 62 | 63 | // Specify Android environment for optimized usage on Android 64 | Katana.environmentContext = AndroidEnvironmentContext() 65 | //applicationComponent = createComponent(createApplicationModule(this),androidModule) 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /app/src/main/kotlin/na/komi/kodesh/model/ApplicationDatabase.kt: -------------------------------------------------------------------------------- 1 | package na.komi.kodesh.model 2 | 3 | import android.content.Context 4 | import androidx.room.Database 5 | import androidx.room.Room 6 | import androidx.room.RoomDatabase 7 | import androidx.sqlite.db.framework.AssetSQLiteOpenHelperFactory 8 | 9 | @Database(entities = [Bible::class], version = 2, exportSchema = false) 10 | abstract class ApplicationDatabase : RoomDatabase() { 11 | abstract fun mainDao(): MainDao 12 | 13 | companion object { 14 | @Volatile 15 | private var instance: ApplicationDatabase? = null 16 | 17 | @Synchronized 18 | fun getInstance(ctx: Context): ApplicationDatabase = instance 19 | ?: Room.databaseBuilder(ctx.applicationContext, ApplicationDatabase::class.java, "kjv-pce-v2.db") 20 | .openHelperFactory(AssetSQLiteOpenHelperFactory()) 21 | .fallbackToDestructiveMigration() 22 | .build().also { instance = it } 23 | 24 | fun destroyInstance() { 25 | instance = null 26 | } 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/na/komi/kodesh/model/Bible.kt: -------------------------------------------------------------------------------- 1 | package na.komi.kodesh.model 2 | 3 | import androidx.room.ColumnInfo 4 | import androidx.room.Entity 5 | import androidx.room.PrimaryKey 6 | 7 | @Entity(tableName = "Bible") 8 | data class Bible( 9 | 10 | @ColumnInfo(name = "book_id") var bookId: Int? = 1, 11 | @ColumnInfo(name = "book_abbr") var bookAbbr: String? = "", 12 | @ColumnInfo(name = "book_name") var bookName: String? = "", 13 | @ColumnInfo(name = "chapter_id") var chapterId: Int? = 1, 14 | @ColumnInfo(name = "verse_id") var verseId: Int? = 1, 15 | @ColumnInfo(name = "verse_text") var verseText: String? = "", 16 | @PrimaryKey(autoGenerate = false) @ColumnInfo(name = "id") var id: Int, 17 | @ColumnInfo(name = "section") var section: String? = "" 18 | 19 | ) -------------------------------------------------------------------------------- /app/src/main/kotlin/na/komi/kodesh/model/MainDao.kt: -------------------------------------------------------------------------------- 1 | package na.komi.kodesh.model 2 | 3 | import androidx.paging.DataSource 4 | import androidx.room.Dao 5 | import androidx.room.Query 6 | import androidx.room.RawQuery 7 | import androidx.sqlite.db.SupportSQLiteQuery 8 | 9 | /** 10 | * The Data Access Object for the Verse class. 11 | */ 12 | @Suppress("ReplaceArrayOfWithLiteral") 13 | @Dao 14 | interface MainDao { 15 | @Query("SELECT * from Bible WHERE book_id=1 ORDER BY chapter_id") 16 | fun getAll(): DataSource.Factory 17 | 18 | @Query("SELECT * from Bible GROUP BY book_id, chapter_id") 19 | fun getPages(): List 20 | 21 | @Query("SELECT * from Bible GROUP BY book_id, chapter_id") 22 | fun getPagesSource(): DataSource.Factory 23 | 24 | @Query("SELECT * from Bible GROUP BY book_id, chapter_id LIMIT 1 OFFSET :position") 25 | fun getRow(position: Int): Bible 26 | 27 | @Query("SELECT count(*) from (SELECT * from Bible GROUP BY book_id, chapter_id) as g where g.book_id<=(:book) and id>0;") 28 | fun getPageForBook(book: Int): Int 29 | 30 | @Query("SELECT DISTINCT book_name from Bible") 31 | fun getBooks(): Array 32 | 33 | @Query("SELECT DISTINCT chapter_id from Bible WHERE book_id=(:bookID) ORDER BY chapter_id DESC LIMIT 1") 34 | fun getChapterAmount(bookID: Int): Int 35 | 36 | @Query("SELECT DISTINCT verse_id from Bible WHERE book_id=(:bookID) AND chapter_id=(:chapterID) ORDER BY verse_id DESC LIMIT 1") 37 | fun getVerseAmount(bookID: Int, chapterID: Int): Int 38 | 39 | @RawQuery(observedEntities = arrayOf(Bible::class)) 40 | fun getRow(query: SupportSQLiteQuery): Bible 41 | 42 | @RawQuery(observedEntities = arrayOf(Bible::class)) 43 | fun getVersesRaw(query: SupportSQLiteQuery): List 44 | 45 | @Query("SELECT * FROM(SELECT * from Bible GROUP BY book_id, chapter_id limit (:position) ) ORDER BY id DESC LIMIT 1") 46 | fun getRowAtPagePositon(position:Int): Bible 47 | 48 | @Query("SELECT * FROM Bible WHERE REPLACE(REPLACE(verse_text, '[', ''), ']','') LIKE '%' || :query || '%'") 49 | fun getVerses(query: String): DataSource.Factory 50 | } 51 | -------------------------------------------------------------------------------- /app/src/main/kotlin/na/komi/kodesh/model/MainRepository.kt: -------------------------------------------------------------------------------- 1 | package na.komi.kodesh.model 2 | 3 | import androidx.annotation.UiThread 4 | import androidx.annotation.WorkerThread 5 | import androidx.sqlite.db.SimpleSQLiteQuery 6 | 7 | /** 8 | * Repository module for handling data operations. 9 | * Java singleton 10 | */ 11 | //class MainRepository private constructor(private val mainDao: MainDao) { 12 | 13 | class MainRepository constructor(private val mainDao: MainDao) { 14 | 15 | fun getAll() = mainDao.getAll() 16 | 17 | fun getRow(position: Int) = mainDao.getRow(position) 18 | 19 | @WorkerThread 20 | fun getPageForBook(book: Int) = mainDao.getPageForBook(book) 21 | 22 | @WorkerThread 23 | fun getPages() = mainDao.getPages() 24 | 25 | fun getPagesSource() = mainDao.getPagesSource() 26 | 27 | fun getBooks() = mainDao.getBooks() 28 | 29 | fun getChapterAmount(bookID: Int) = mainDao.getChapterAmount(bookID) 30 | 31 | fun getVerseAmount(bookID: Int, chapterID: Int) = mainDao.getVerseAmount(bookID, chapterID) 32 | 33 | fun getVersesRaw(book: Int, chapter: Int) = mainDao.getVersesRaw(SimpleSQLiteQuery("SELECT * FROM Bible WHERE book_id=? AND chapter_id=?", arrayOf(book, chapter))) 34 | 35 | @UiThread 36 | fun getRowAtPagePositon(position:Int) = mainDao.getRowAtPagePositon(position) 37 | 38 | fun getVerses(query: String) = mainDao.getVerses(query) 39 | 40 | companion object { 41 | 42 | // For Singleton instantiation 43 | @Volatile 44 | private var instance: MainRepository? = null 45 | 46 | fun getInstance(mainDao: MainDao) = 47 | instance ?: synchronized(this) { 48 | instance ?: MainRepository(mainDao).also { instance = it } 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /app/src/main/kotlin/na/komi/kodesh/model/Preferences.kt: -------------------------------------------------------------------------------- 1 | package na.komi.kodesh.model 2 | 3 | /** 4 | * A helper class to get or set preferences. 5 | * You must first initialize your values with PreferenceManager.setDefaultValues() 6 | */ 7 | import android.content.Context 8 | import android.content.Context.MODE_PRIVATE 9 | import android.content.SharedPreferences 10 | import na.komi.kodesh.BuildConfig 11 | 12 | class Preferences (context: Context) { 13 | private val PREFS_FILENAME = "${BuildConfig.APPLICATION_ID}.Preferences" 14 | private val BOOK = "BOOKID" 15 | private val CHAPTER = "CHAPTER" 16 | private val BOOK_LENGTH = "BOOK_LENGTH" 17 | private val CHAPTER_LENGTH = "CHAPTER_LENGTH" 18 | private val DAY_MODE = "DAY_MODE" 19 | private val VP_POSITION = "VP_POSITION" 20 | private val MAIN_FONT_SIZE = "MAIN_FONT_SIZE" 21 | private val FONT_SIZE = "FONT_SIZE" 22 | private val THEME_ID = "THEME_ID" 23 | private val KJVSTYLE_ID = "KJVSTYLE_ID" 24 | private val DROP_CAP_ID = "DROP_CAP_ID" 25 | private val PBREAK_ID = "PBREAK_ID" 26 | private val RED_LETTER_ID = "RED_LETTER_ID" 27 | private val VERSE_NUMBERS = "VERSE_NUMBERS" 28 | private val NEW_LINE_EACH_VERSE_ID = "NEW_LINE_EACH_VERSE_ID" 29 | private val HEADINGS_ID = "HEADINGS_ID" 30 | private val FOOTINGS_ID = "FOOTINGS_ID" 31 | private val SCROLLY_ID = "SCROLLY_ID" 32 | private val NAVIGATE_TO_POSITION = "NAVIGATE_TO_POSITION" 33 | private val SCROLL_STRING = "SCROLL_STRING" 34 | private val SCALE_FACTOR = "SCALE_FACTOR" 35 | private val TITLE_ID = "TITLE_ID" 36 | private val prefs: SharedPreferences = context.getSharedPreferences(PREFS_FILENAME, MODE_PRIVATE) 37 | 38 | var Book: Int 39 | get() = prefs.getInt(BOOK, 1) 40 | set(value) = prefs.edit().putInt(BOOK, value).apply() 41 | var Chapter: Int 42 | get() = prefs.getInt(CHAPTER, 1) 43 | set(value) = prefs.edit().putInt(CHAPTER, value).apply() 44 | var BookLength: Int 45 | get() = prefs.getInt(BOOK_LENGTH, 50) 46 | set(value) = prefs.edit().putInt(BOOK_LENGTH, value).apply() 47 | var ChapterLength: Int 48 | get() = prefs.getInt(CHAPTER_LENGTH, 31) 49 | set(value) = prefs.edit().putInt(CHAPTER_LENGTH, value).apply() 50 | var dayMode: Boolean 51 | get() = prefs.getBoolean(DAY_MODE, true) 52 | set(value) = prefs.edit().putBoolean(DAY_MODE, value).apply() 53 | var VP_Position: Int 54 | get() = prefs.getInt(VP_POSITION, 0) 55 | set(value) = prefs.edit().putInt(VP_POSITION, value).apply() 56 | var textSizeMultiplier: Float 57 | get() = prefs.getFloat(FONT_SIZE, 1f) 58 | set(value) = prefs.edit().putFloat(FONT_SIZE, value).apply() 59 | var mainFontSize: Float 60 | get() = prefs.getFloat(MAIN_FONT_SIZE, 26f) 61 | set(value) = prefs.edit().putFloat(MAIN_FONT_SIZE, value).apply() 62 | var scaleFactor: Float 63 | get() = prefs.getFloat(SCALE_FACTOR, 5f) 64 | set(value) = prefs.edit().putFloat(SCALE_FACTOR, value).apply() 65 | var themeId: Int 66 | get() = prefs.getInt(THEME_ID, 0) 67 | set(value) = prefs.edit().putInt(THEME_ID, value).apply() 68 | var kjvStylingPref: Boolean 69 | get() = prefs.getBoolean(KJVSTYLE_ID, false) 70 | set(value) = prefs.edit().putBoolean(KJVSTYLE_ID, value).apply() 71 | var dropCapPref: Boolean 72 | get() = prefs.getBoolean(DROP_CAP_ID, true) 73 | set(value) = prefs.edit().putBoolean(DROP_CAP_ID, value).apply() 74 | var pBreakPref: Boolean 75 | get() = prefs.getBoolean(PBREAK_ID, false) 76 | set(value) = prefs.edit().putBoolean(PBREAK_ID, value).apply() 77 | var redLetterPref: Boolean 78 | get() = prefs.getBoolean(RED_LETTER_ID, true) 79 | set(value) = prefs.edit().putBoolean(RED_LETTER_ID, value).apply() 80 | var verseNumberPref: Boolean 81 | get() = prefs.getBoolean(VERSE_NUMBERS, true) 82 | set(value) = prefs.edit().putBoolean(VERSE_NUMBERS, value).apply() 83 | var seperateVersePref: Boolean 84 | get() = prefs.getBoolean(NEW_LINE_EACH_VERSE_ID, true) 85 | set(value) = prefs.edit().putBoolean(NEW_LINE_EACH_VERSE_ID, value).apply() 86 | var currentScroll: Int 87 | get() = prefs.getInt(SCROLLY_ID, 0) 88 | set(value) = prefs.edit().putInt(SCROLLY_ID, value).apply() 89 | var NavigateToPosition: Int 90 | get() = prefs.getInt(NAVIGATE_TO_POSITION, -1) 91 | set(value) = prefs.edit().putInt(NAVIGATE_TO_POSITION, value).apply() 92 | var ScrollString: String? 93 | get() = prefs.getString(SCROLL_STRING, "") 94 | set(value) = prefs.edit().putString(SCROLL_STRING, value).apply() 95 | var title: String 96 | get() = prefs.getString(TITLE_ID, "") ?: "" 97 | set(value) = prefs.edit().putString(TITLE_ID, value).apply() 98 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/na/komi/kodesh/ui/internal/BaseAdapter.kt: -------------------------------------------------------------------------------- 1 | package na.komi.kodesh.ui.internal 2 | 3 | import android.view.View 4 | import android.view.ViewGroup 5 | import androidx.recyclerview.widget.RecyclerView 6 | import kotlinx.android.extensions.LayoutContainer 7 | 8 | abstract class BaseAdapter : RecyclerView.Adapter() { 9 | open val currentList: MutableList by lazy { mutableListOf() } 10 | 11 | open fun submitList(newList: MutableList) { 12 | currentList.clear() 13 | currentList.addAll(newList) 14 | notifyDataSetChanged() 15 | } 16 | 17 | override fun getItemId(position: Int): Long = currentList[position].hashCode().toLong().let { if (it==0L) position.toLong() else it } 18 | override fun getItemCount(): Int = currentList.size 19 | abstract override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder 20 | abstract override fun onBindViewHolder(holder: ViewHolder, position: Int) 21 | abstract class ViewHolder(override val containerView: View) : RecyclerView.ViewHolder(containerView), LayoutContainer 22 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/na/komi/kodesh/ui/internal/BaseFragment.kt: -------------------------------------------------------------------------------- 1 | package na.komi.kodesh.ui.internal 2 | 3 | import android.app.Activity 4 | import android.content.Context 5 | import android.os.Bundle 6 | import android.view.LayoutInflater 7 | import android.view.View 8 | import android.view.ViewGroup 9 | import android.view.inputmethod.InputMethodManager 10 | import androidx.appcompat.app.AppCompatActivity 11 | import androidx.appcompat.widget.AppCompatTextView 12 | import androidx.appcompat.widget.Toolbar 13 | import androidx.constraintlayout.widget.ConstraintLayout 14 | import androidx.core.view.children 15 | import androidx.fragment.app.Fragment 16 | import com.google.android.material.navigation.NavigationView 17 | import kotlinx.coroutines.CoroutineScope 18 | import kotlinx.coroutines.Dispatchers 19 | import kotlinx.coroutines.SupervisorJob 20 | import kotlinx.coroutines.cancelChildren 21 | import na.komi.kodesh.Application 22 | import na.komi.kodesh.R 23 | import na.komi.kodesh.ui.main.MainActivity 24 | import na.komi.kodesh.util.close 25 | import na.komi.kodesh.util.onClick 26 | import na.komi.kodesh.util.toggle 27 | import org.rewedigital.katana.Component 28 | import org.rewedigital.katana.KatanaTrait 29 | import org.rewedigital.katana.android.fragment.KatanaFragmentDelegate 30 | import org.rewedigital.katana.android.fragment.fragmentDelegate 31 | import kotlin.coroutines.CoroutineContext 32 | 33 | 34 | interface TitleListener { 35 | fun onToolbarTitleClick() {} 36 | } 37 | 38 | interface InjectListener { 39 | fun onInject(activity: Activity) {} 40 | } 41 | 42 | abstract class BaseFragment : Fragment(), CoroutineScope, TitleListener, InjectListener { 43 | 44 | open val job by lazy { SupervisorJob() } 45 | 46 | override val coroutineContext: CoroutineContext 47 | get() = Dispatchers.Main + job//ContextHelper.dispatcher + job 48 | 49 | abstract val layout: Int 50 | 51 | init { 52 | /** 53 | * This retains the state on config change and does not call oncreate again. 54 | * https://stackoverflow.com/a/18681837 55 | */ 56 | retainInstance = true 57 | } 58 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { 59 | return inflater.inflate(layout, container, false) 60 | } 61 | 62 | /** 63 | * getBottomSheetBehavior 64 | * getToolbarTitleView 65 | * getNavigationView 66 | */ 67 | fun getBottomSheetContainer(): ConstraintLayout? = act()?.bottomSheetContainer 68 | 69 | fun getBottomSheetBehavior(): BottomSheetBehavior2? = act()?.bottomSheetBehavior 70 | 71 | fun getToolbarTitleView(): AppCompatTextView? = act()?.getToolbarTitleView() 72 | 73 | fun getNavigationView(): NavigationView? = act()?.getNavigationView() 74 | 75 | fun getToolbar(): Toolbar? = act()?.getToolbar() 76 | 77 | private fun act() = (activity as? MainActivity) 78 | 79 | override fun onCreate(savedInstanceState: Bundle?) { 80 | super.onCreate(savedInstanceState) 81 | } 82 | 83 | //protected abstract fun onToolbarTitleClick() {} 84 | 85 | protected abstract fun ToolbarBuilder(): FragmentToolbar 86 | private val builder by lazy { ToolbarBuilder() } 87 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 88 | //super.onViewCreated(view, savedInstanceState) 89 | getToolbar()?.let { 90 | if (builder.title != -1) 91 | it.title = getString(builder.title) 92 | if (builder.menu != -1) { 93 | it.menu.clear() 94 | it.inflateMenu(builder.menu) 95 | } 96 | } 97 | 98 | 99 | if (builder.menuItems != null && builder.menuClicks != null && getToolbar() != null) 100 | if (builder.menuItems!!.isNotEmpty() && builder.menuClicks!!.isNotEmpty()) { 101 | val menu = getToolbar()?.menu 102 | for ((index, menuItemId) in builder.menuItems!!.withIndex()) { 103 | menu?.findItem(menuItemId)?.setOnMenuItemClickListener(builder.menuClicks!![index]) 104 | } 105 | } 106 | 107 | getToolbar()?.let { 108 | for (a in it.menu.children) 109 | a.isVisible = true 110 | } 111 | getNavigationView()?.let { 112 | it.setCheckedItem(R.id.action_read) 113 | for (a in it.menu.children) 114 | a.isEnabled = true 115 | } 116 | } 117 | 118 | override fun onActivityCreated(savedInstanceState: Bundle?) { 119 | super.onActivityCreated(savedInstanceState) 120 | getToolbarTitleView()?.onClick { 121 | onToolbarTitleClick() 122 | } 123 | } 124 | 125 | fun closeBottomSheet() = getBottomSheetBehavior()?.close() 126 | 127 | fun toggleBottomSheet() = getBottomSheetBehavior()?.toggle() 128 | 129 | override fun onDestroy() { 130 | super.onDestroy() 131 | coroutineContext.cancelChildren() 132 | } 133 | 134 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/na/komi/kodesh/ui/internal/BaseFragment2.kt: -------------------------------------------------------------------------------- 1 | package na.komi.kodesh.ui.internal 2 | 3 | import android.app.Activity 4 | import android.os.Bundle 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import androidx.appcompat.widget.AppCompatTextView 9 | import androidx.appcompat.widget.Toolbar 10 | import androidx.constraintlayout.widget.ConstraintLayout 11 | import androidx.core.view.children 12 | import androidx.fragment.app.Fragment 13 | import com.google.android.material.navigation.NavigationView 14 | import kotlinx.coroutines.CoroutineScope 15 | import kotlinx.coroutines.Dispatchers 16 | import kotlinx.coroutines.SupervisorJob 17 | import kotlinx.coroutines.cancelChildren 18 | import na.komi.kodesh.R 19 | import na.komi.kodesh.ui.main.MainActivity 20 | import na.komi.kodesh.util.close 21 | import na.komi.kodesh.util.onClick 22 | import na.komi.kodesh.util.toggle 23 | import kotlin.coroutines.CoroutineContext 24 | 25 | abstract class BaseFragment2 : Fragment(), CoroutineScope, TitleListener, InjectListener { 26 | 27 | open val job by lazy { SupervisorJob() } 28 | 29 | override val coroutineContext: CoroutineContext 30 | get() = Dispatchers.Main + job//ContextHelper.dispatcher + job 31 | 32 | abstract val layout: Int 33 | 34 | init { 35 | /** 36 | * This retains the state on config change and does not call oncreate again. 37 | * https://stackoverflow.com/a/18681837 38 | */ 39 | retainInstance = true 40 | } 41 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { 42 | return inflater.inflate(layout, container, false) 43 | } 44 | 45 | /** 46 | * getBottomSheetBehavior 47 | * getToolbarTitleView 48 | * getNavigationView 49 | */ 50 | fun getBottomSheetContainer(): ConstraintLayout? = act?.bottomSheetContainer 51 | 52 | fun getBottomSheetBehavior(): BottomSheetBehavior2? = act?.bottomSheetBehavior 53 | 54 | fun getToolbarTitleView(): AppCompatTextView? = act?.getToolbarTitleView() 55 | 56 | fun getNavigationView(): NavigationView = act?.getNavigationView()!! 57 | 58 | fun getToolbar(): Toolbar? = act?.getToolbar() 59 | 60 | private val act 61 | get()= (activity as? MainActivity) 62 | 63 | /* 64 | override fun onActivityCreated(savedInstanceState: Bundle?) { 65 | super.onActivityCreated(savedInstanceState) 66 | getToolbar()?.let { 67 | for (a in it.menu.children) 68 | a.isVisible = true 69 | } 70 | getNavigationView().let { 71 | it.setCheckedItem(R.id.action_read) 72 | for (a in it.menu.children) 73 | a.isEnabled = true 74 | } 75 | getToolbarTitleView()?.onClick { 76 | onToolbarTitleClick() 77 | } 78 | }*/ 79 | 80 | fun closeBottomSheet() = getBottomSheetBehavior()?.close() 81 | 82 | fun toggleBottomSheet() = getBottomSheetBehavior()?.toggle() 83 | 84 | override fun onDestroy() { 85 | super.onDestroy() 86 | coroutineContext.cancelChildren() 87 | } 88 | 89 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/na/komi/kodesh/ui/internal/BaseKatanaFragment.kt: -------------------------------------------------------------------------------- 1 | package na.komi.kodesh.ui.internal 2 | 3 | import org.rewedigital.katana.KatanaTrait 4 | 5 | abstract class BaseKatanaFragment : BaseFragment(), KatanaTrait -------------------------------------------------------------------------------- /app/src/main/kotlin/na/komi/kodesh/ui/internal/BasePreferenceFragment.kt: -------------------------------------------------------------------------------- 1 | package na.komi.kodesh.ui.internal 2 | 3 | /** 4 | * Fix indentation in preferences 5 | * https://is.gd/zoKZaZ 6 | */ 7 | 8 | import android.annotation.SuppressLint 9 | import androidx.preference.* 10 | import androidx.recyclerview.widget.RecyclerView 11 | 12 | abstract class BasePreferenceFragment : PreferenceFragmentCompat() { 13 | private fun setAllPreferencesToAvoidHavingExtraSpace(preference: Preference) { 14 | preference.isIconSpaceReserved = false 15 | if (preference is PreferenceGroup) 16 | for (i in 0 until preference.preferenceCount) 17 | setAllPreferencesToAvoidHavingExtraSpace(preference.getPreference(i)) 18 | } 19 | 20 | override fun setPreferenceScreen(preferenceScreen: PreferenceScreen?) { 21 | if (preferenceScreen != null) 22 | setAllPreferencesToAvoidHavingExtraSpace(preferenceScreen) 23 | super.setPreferenceScreen(preferenceScreen) 24 | 25 | } 26 | 27 | override fun onCreateAdapter(preferenceScreen: PreferenceScreen?): RecyclerView.Adapter<*> = 28 | object : PreferenceGroupAdapter(preferenceScreen) { 29 | @SuppressLint("RestrictedApi") 30 | override fun onPreferenceHierarchyChange(preference: Preference?) { 31 | if (preference != null) 32 | setAllPreferencesToAvoidHavingExtraSpace(preference) 33 | super.onPreferenceHierarchyChange(preference) 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/na/komi/kodesh/ui/internal/ExtendedBottomSheetDialogFragment.kt: -------------------------------------------------------------------------------- 1 | package na.komi.kodesh.ui.internal 2 | 3 | import android.app.Dialog 4 | import android.content.res.Configuration 5 | import android.os.Bundle 6 | import android.view.ViewGroup 7 | import android.widget.FrameLayout 8 | import com.google.android.material.bottomsheet.BottomSheetBehavior 9 | import com.google.android.material.bottomsheet.BottomSheetDialogFragment 10 | import na.komi.kodesh.R 11 | 12 | 13 | /** 14 | * Descendant of BottomSheetDialogFragment that adds a few features and conveniences. 15 | */ 16 | abstract class ExtendedBottomSheetDialogFragment : BottomSheetDialogFragment() { 17 | override fun onCreate(savedInstanceState: Bundle?) { 18 | super.onCreate(savedInstanceState) 19 | } 20 | 21 | @BottomSheetBehavior.State 22 | abstract val initialState:Int 23 | 24 | override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { 25 | val view = super.onCreateDialog(savedInstanceState) 26 | view.setOnShowListener { 27 | val bottomSheet = view.findViewById(com.google.android.material.R.id.design_bottom_sheet) 28 | val behavior = BottomSheetBehavior.from(bottomSheet) 29 | //behavior.skipCollapsed = true 30 | behavior.state = initialState// BottomSheetBehavior.STATE_COLLAPSED 31 | } 32 | return view 33 | } 34 | 35 | override fun onStart() { 36 | super.onStart() 37 | setWindowLayout() 38 | } 39 | 40 | override fun onDestroy() { 41 | super.onDestroy() 42 | } 43 | 44 | override fun onConfigurationChanged(newConfig: Configuration) { 45 | super.onConfigurationChanged(newConfig) 46 | setWindowLayout() 47 | } 48 | 49 | protected fun disableBackgroundDim() { 50 | dialog!!.window!!.setDimAmount(0f) 51 | } 52 | 53 | private fun setWindowLayout() { 54 | if (dialog != null) { 55 | val width = dialogWidthPx() 56 | dialog!!.window!!.setLayout( 57 | if (width > 0) width else ViewGroup.LayoutParams.MATCH_PARENT, 58 | ViewGroup.LayoutParams.MATCH_PARENT 59 | ) 60 | } 61 | } 62 | 63 | private fun dialogWidthPx(): Int { 64 | return context!!.resources.getDimensionPixelSize(R.dimen.bottom_sheet_width) 65 | } 66 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/na/komi/kodesh/ui/internal/FragmentToolbar.kt: -------------------------------------------------------------------------------- 1 | package na.komi.kodesh.ui.internal 2 | 3 | import android.view.MenuItem 4 | import androidx.annotation.IdRes 5 | import androidx.annotation.MenuRes 6 | import androidx.annotation.StringRes 7 | 8 | /** 9 | * https://is.gd/Brbjw8 10 | */ 11 | data class FragmentToolbar( 12 | @IdRes val toolbar: Int, 13 | @StringRes val title: Int = -1, 14 | @MenuRes val menu: Int = -1, 15 | @IdRes val bottomSheet: Int, 16 | @IdRes val navigationView: Int, 17 | @MenuRes val navigationViewMenu: Int = -1, 18 | @Suppress("ArrayInDataClass") val menuItems: IntArray? = null, 19 | @Suppress("ArrayInDataClass") val menuClicks: Array? = null 20 | ) { 21 | 22 | companion object { 23 | val NO_TOOLBAR = -1 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/kotlin/na/komi/kodesh/ui/internal/ItemDecorator.kt: -------------------------------------------------------------------------------- 1 | package na.komi.kodesh.ui.internal 2 | 3 | import android.graphics.Rect 4 | import android.view.View 5 | import androidx.recyclerview.widget.RecyclerView 6 | 7 | /** 8 | * https://stackoverflow.com/q/40677024 9 | */ 10 | class ItemDecorator(private val mSpace: Int) : RecyclerView.ItemDecoration() { 11 | 12 | override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) { 13 | //val position = parent.getChildAdapterPosition(view) 14 | val position = parent.getChildViewHolder(view).adapterPosition 15 | //val position = (view.layoutParams as RecyclerView.LayoutParams).viewAdapterPosition 16 | val isLast = position == state.itemCount - 1 17 | 18 | if (isLast) { 19 | // Bottom margin because RecyclerView has a bottomPadding bug 20 | outRect.bottom = 400 21 | } else { 22 | outRect.bottom = 0 23 | } 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/na/komi/kodesh/ui/main/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package na.komi.kodesh.ui.main 2 | 3 | import android.os.Bundle 4 | import androidx.preference.PreferenceManager 5 | import na.komi.kodesh.R 6 | import na.komi.kodesh.model.ApplicationDatabase 7 | import na.komi.kodesh.ui.internal.BaseActivity 8 | 9 | class MainActivity : BaseActivity() { 10 | 11 | override val layout: Int = R.layout.activity_main 12 | 13 | override fun onCreate(savedInstanceState: Bundle?) { 14 | super.onCreate(savedInstanceState) 15 | savedInstanceState ?: PreferenceManager.setDefaultValues(this, R.xml.styling_preferences, false) 16 | } 17 | 18 | override fun onDestroy() { 19 | super.onDestroy() 20 | if (!isFinishing) return 21 | ApplicationDatabase.destroyInstance() 22 | MainComponent.clear() 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/kotlin/na/komi/kodesh/ui/main/MainComponent.kt: -------------------------------------------------------------------------------- 1 | package na.komi.kodesh.ui.main 2 | 3 | import org.rewedigital.katana.Component 4 | import org.rewedigital.katana.createComponent 5 | 6 | 7 | /** 8 | * See [Component] 9 | * As long as the same Component reference is used for injection, the same 10 | * singleton instances are reused. Once the Component is eligible for garbage collection so are the instances hold by 11 | * this component. The developer is responsible for holding a Component reference and releasing it when necessary. This 12 | * design was chosen in contrast to other DI libraries that for instance work with a global, singleton state to prevent 13 | * accidental memory leaks. 14 | */ 15 | object MainComponent { 16 | private var mmainComponent: Component? = null 17 | val mainComponent 18 | get() = mmainComponent ?: createComponent( 19 | modules = listOf(Modules.mainModule)// + Modules.modules, 20 | ).also { mmainComponent = it } 21 | 22 | fun clear() { 23 | mmainComponent = null 24 | Modules.clear() 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/kotlin/na/komi/kodesh/ui/main/MainModule.kt: -------------------------------------------------------------------------------- 1 | package na.komi.kodesh.ui.main 2 | 3 | import na.komi.kodesh.Application 4 | import na.komi.kodesh.model.ApplicationDatabase 5 | import na.komi.kodesh.model.MainRepository 6 | import org.rewedigital.katana.Module 7 | import org.rewedigital.katana.androidx.viewmodel.viewModel 8 | import org.rewedigital.katana.createModule 9 | import org.rewedigital.katana.dsl.compact.singleton 10 | import org.rewedigital.katana.dsl.get 11 | 12 | /** 13 | * Modules do not need to be cached since MainComponent hold 14 | * all the instances. 15 | */ 16 | object Modules { 17 | private var _mainModule: Module? = null 18 | val mainModule: Module 19 | get() = _mainModule ?: createModule { 20 | singleton { ApplicationDatabase.getInstance(Application.instance) } 21 | singleton { get().mainDao() } 22 | singleton { MainRepository.getInstance(get()) } 23 | viewModel { MainViewModel(get()) } 24 | }.also { _mainModule = it } 25 | 26 | fun clear() { 27 | _mainModule = null 28 | } 29 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/na/komi/kodesh/ui/main/MainViewModel.kt: -------------------------------------------------------------------------------- 1 | package na.komi.kodesh.ui.main 2 | 3 | import android.os.Bundle 4 | import android.os.Parcelable 5 | import androidx.lifecycle.LiveData 6 | import androidx.lifecycle.MutableLiveData 7 | import androidx.lifecycle.ViewModel 8 | import androidx.paging.LivePagedListBuilder 9 | import androidx.paging.PagedList 10 | import kotlinx.coroutines.* 11 | import kotlinx.coroutines.Dispatchers.IO 12 | import na.komi.kodesh.Prefs 13 | import na.komi.kodesh.model.Bible 14 | import na.komi.kodesh.model.MainRepository 15 | import na.komi.kodesh.util.Coroutines 16 | import na.komi.kodesh.util.livedata.LiveEvent 17 | import java.util.concurrent.Executors 18 | import kotlin.coroutines.CoroutineContext 19 | 20 | 21 | class MainViewModel internal constructor( 22 | private val mainRepository: MainRepository 23 | ) : ViewModel(), CoroutineScope { 24 | override val coroutineContext: CoroutineContext 25 | get() = Dispatchers.Main 26 | private val bg = Dispatchers.IO 27 | 28 | var kjvStyling = Prefs.kjvStylingPref//Prefs.kjvStylingPref 29 | 30 | 31 | var showDropCap = Prefs.dropCapPref//LiveEvent(Prefs.dropCapPref) 32 | var showParagraphs = Prefs.pBreakPref//LiveEvent(Prefs.pBreakPref) 33 | var showRedLetters = Prefs.redLetterPref//LiveEvent(Prefs.redLetterPref) 34 | var showVerseNumbers = Prefs.verseNumberPref 35 | var seperateVerses = Prefs.seperateVersePref 36 | var fromAdapterNotify = false 37 | val pagePosition by lazy { LiveEvent(Prefs.VP_Position) } 38 | val verseNumberPref by lazy { Prefs.verseNumberPref } 39 | val textSize by lazy { LiveEvent(Prefs.mainFontSize) } 40 | val progressBarStatus by lazy { LiveEvent(false) } 41 | 42 | val currentBook by lazy { LiveEvent(1) } 43 | val currentChapter by lazy { LiveEvent(1) } 44 | val currentChapterAmount by lazy { LiveEvent(1) } 45 | val currentVerse by lazy { LiveEvent(1) } 46 | val currentVerseAmount by lazy { LiveEvent(1) } 47 | val gotoPage by lazy { LiveEvent(0) } 48 | 49 | val buttonState by lazy { LiveEvent(false) } 50 | val currentScroll by lazy { LiveEvent(Prefs.currentScroll) } 51 | var versePicked: Int = 1 52 | 53 | private val _adapterUpdate by lazy { MutableLiveData() } 54 | val adapterUpdate: LiveData 55 | get() = _adapterUpdate 56 | 57 | fun setAdapterUpdate(state: Boolean) { 58 | _adapterUpdate.value = state 59 | } 60 | 61 | val currentLowProfileFlag by lazy { LiveEvent(false) } 62 | private val _lowProfileFlag by lazy { LiveEvent(true) } 63 | val lowProfileFlag 64 | get() = _lowProfileFlag 65 | 66 | fun setLowProfileFlag(flag: Boolean) { 67 | _lowProfileFlag.postValue(flag) 68 | } 69 | 70 | var MainRecyclerViewState: Parcelable? = null 71 | 72 | val pagesList = 73 | LivePagedListBuilder( 74 | mainRepository.getPagesSource(), 75 | PagedList.Config.Builder() 76 | .setPageSize(3) 77 | .setPrefetchDistance(3) 78 | .setEnablePlaceholders(true) 79 | .build() 80 | ).setFetchExecutor(executorDispatcher.executor) 81 | .build() 82 | 83 | private var _executorDispatcher: ExecutorCoroutineDispatcher? = null 84 | val executorDispatcher: ExecutorCoroutineDispatcher 85 | get() = _executorDispatcher ?: Executors.newCachedThreadPool().asCoroutineDispatcher() 86 | 87 | val KEY_RECYCLER_STATE = "recycler_state" 88 | var mBundleRecyclerViewState: Bundle? = null 89 | 90 | fun getBooks() = runBlocking(IO) { mainRepository.getBooks() } 91 | 92 | fun getChapterAmount(bookID: Int) = runBlocking(IO) { mainRepository.getChapterAmount(bookID) } 93 | 94 | fun getVerseAmount(bookID: Int, chapterID: Int) = 95 | runBlocking(IO) { mainRepository.getVerseAmount(bookID, chapterID) } 96 | 97 | fun getPagePosition(bookID: Int) = runBlocking(IO) { mainRepository.getPageForBook(bookID) } 98 | 99 | fun getVersesRaw(book: Int, chapter: Int) = mainRepository.getVersesRaw(book, chapter) 100 | 101 | fun getRowAtPagePositon(i: Int) = runBlocking(IO) { mainRepository.getRowAtPagePositon(i) } 102 | 103 | var longClicked = false //LiveEvent(false) 104 | private val _list by lazy { MutableLiveData>() } 105 | val list: LiveData> get() = _list 106 | 107 | private val _item by lazy { LiveEvent(false) } 108 | val item: LiveData 109 | get() = _item 110 | 111 | fun setItem(flag: Boolean) { 112 | _item.postValue(flag) 113 | } 114 | 115 | // DataSource documentation states that it runs immediately 116 | fun getPages2(): LiveData> { 117 | 118 | if (_list.value == null) 119 | Coroutines.ioThenMain({ 120 | mainRepository.getPages() 121 | }) { 122 | _list.postValue(it) 123 | } 124 | 125 | return list 126 | } 127 | 128 | fun searchVerse(query: String) = runBlocking(Dispatchers.IO) { 129 | // Outputs a new livedata on each call 130 | LivePagedListBuilder( 131 | mainRepository.getVerses(query), 132 | PagedList.Config.Builder() 133 | .setPageSize(PAGE_SIZE) 134 | .setPrefetchDistance(PREFETCH_DISTANCE) 135 | .setEnablePlaceholders(ENABLE_PLACEHOLDERS) 136 | .build() 137 | ) 138 | .build() 139 | } 140 | 141 | companion object { 142 | private const val PAGE_SIZE = 30 143 | private const val PREFETCH_DISTANCE = 30 144 | private const val ENABLE_PLACEHOLDERS = true 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /app/src/main/kotlin/na/komi/kodesh/ui/main/MainViewModelFactory.kt: -------------------------------------------------------------------------------- 1 | package na.komi.kodesh.ui.main 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.ViewModelProvider 5 | import na.komi.kodesh.model.MainRepository 6 | 7 | class MainViewModelFactory(private val repository: MainRepository) : ViewModelProvider.NewInstanceFactory() { 8 | @Suppress("UNCHECKED_CAST") 9 | override fun create(modelClass: Class) = MainViewModel(repository) as T 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/kotlin/na/komi/kodesh/ui/preface/PrefaceImageFragment.kt: -------------------------------------------------------------------------------- 1 | package na.komi.kodesh.ui.preface 2 | 3 | import android.app.Dialog 4 | import android.graphics.Color 5 | import android.graphics.drawable.ColorDrawable 6 | import android.os.Bundle 7 | import android.view.LayoutInflater 8 | import android.view.View 9 | import android.view.ViewGroup 10 | import android.view.Window 11 | import androidx.fragment.app.DialogFragment 12 | import na.komi.kodesh.R 13 | 14 | 15 | class PrefaceImageFragment : DialogFragment() { 16 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { 17 | return inflater.inflate(R.layout.fragment_dialog_preface_image, container) 18 | } 19 | 20 | /** 21 | * https://stackoverflow.com/a/28528209 22 | */ 23 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 24 | super.onViewCreated(view, savedInstanceState) 25 | 26 | setStyle(DialogFragment.STYLE_NO_FRAME, android.R.style.Theme) 27 | } 28 | 29 | override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { 30 | val dialog=super.onCreateDialog(savedInstanceState) 31 | dialog.apply { 32 | 33 | requestWindowFeature(Window.FEATURE_NO_TITLE); 34 | window?.apply { 35 | setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT)) 36 | setDimAmount(1f) 37 | attributes.windowAnimations = android.R.style.Animation_Dialog 38 | } 39 | } 40 | return dialog 41 | } 42 | 43 | override fun onStart() { 44 | super.onStart() 45 | dialog?.window?.setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); 46 | } 47 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/na/komi/kodesh/ui/search/SearchAdapter.kt: -------------------------------------------------------------------------------- 1 | package na.komi.kodesh.ui.search 2 | 3 | import android.app.Activity 4 | import android.content.ClipData 5 | import android.content.ClipboardManager 6 | import android.content.Context 7 | import android.content.ContextWrapper 8 | import android.view.LayoutInflater 9 | import android.view.View 10 | import android.view.ViewGroup 11 | import android.widget.Toast 12 | import androidx.appcompat.widget.AppCompatTextView 13 | import androidx.cardview.widget.CardView 14 | import androidx.coordinatorlayout.widget.CoordinatorLayout 15 | import androidx.paging.PagedListAdapter 16 | import androidx.recyclerview.widget.DiffUtil 17 | import androidx.recyclerview.widget.RecyclerView 18 | import com.google.android.material.snackbar.Snackbar 19 | import kotlinx.android.extensions.LayoutContainer 20 | import kotlinx.android.synthetic.main.recyclerview_content_search.* 21 | import na.komi.kodesh.Application 22 | import na.komi.kodesh.R 23 | import na.komi.kodesh.model.Bible 24 | import na.komi.kodesh.util.page.Fonts 25 | import na.komi.kodesh.util.snackbar 26 | import na.komi.kodesh.util.text.futureSet 27 | 28 | 29 | class SearchAdapter : PagedListAdapter(object : DiffUtil.ItemCallback() { 30 | 31 | override fun areItemsTheSame(oldItem: Bible, newItem: Bible): Boolean = 32 | oldItem.id == newItem.id 33 | 34 | override fun areContentsTheSame(oldItem: Bible, newItem: Bible): Boolean = 35 | oldItem == newItem 36 | }) { 37 | 38 | init { 39 | setHasStableIds(true) 40 | } 41 | 42 | override fun getItemId(position: Int): Long { 43 | return getItem(position)?.id?.toLong() ?: position.toLong() 44 | } 45 | 46 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { 47 | return ViewHolder( 48 | LayoutInflater.from(parent.context).inflate( 49 | R.layout.recyclerview_content_search, 50 | parent, 51 | false 52 | ) 53 | ) 54 | } 55 | 56 | override fun onBindViewHolder(holder: ViewHolder, position: Int) { 57 | //GlobalScope.launch(Dispatchers.IO) { 58 | val bible = getItem(position) 59 | bible?.let { holder.bind(it) } 60 | //} 61 | } 62 | 63 | fun View.getActivity(): Activity? { 64 | var context = this.context 65 | while (context is ContextWrapper) { 66 | if (context is Activity) { 67 | return context 68 | } 69 | @Suppress("USELESS_CAST") 70 | context = (context as ContextWrapper).baseContext 71 | } 72 | return null 73 | } 74 | 75 | private val clipboard by lazy { 76 | Application.instance.applicationContext.getSystemService( 77 | Context.CLIPBOARD_SERVICE 78 | ) as ClipboardManager? 79 | } 80 | inner class ViewHolder(override val containerView: View) : RecyclerView.ViewHolder(containerView), LayoutContainer { 81 | val cardView: CardView = item_card_view 82 | val title: AppCompatTextView = card_search_title 83 | val content: AppCompatTextView = card_search_content 84 | 85 | init { 86 | 87 | title.typeface = Fonts.Merriweather_Black 88 | content.typeface = Fonts.GentiumPlus_R 89 | cardView.setOnLongClickListener { 90 | val title = title.text 91 | it.getActivity()?.findViewById(R.id.container_main)?.let {coordinatorLayout -> 92 | Snackbar.make(coordinatorLayout,"Selected $title",Snackbar.LENGTH_SHORT).setAction("Copy") { 93 | clipboard?.primaryClip = ClipData.newPlainText("Search text", "$title\n${content.text}") 94 | Toast.makeText(coordinatorLayout.context,"Copied verse", Toast.LENGTH_SHORT).show() 95 | }.show() 96 | } 97 | false 98 | } 99 | 100 | } 101 | 102 | fun bind(item: Bible) { 103 | title.futureSet("${item.bookName!!} ${item.chapterId!!}:${item.verseId!!}") 104 | content.futureSet(item.verseText!!.replace("[", "").replace("]", "")) 105 | } 106 | } 107 | 108 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/na/komi/kodesh/ui/setting/SettingsFragment.kt: -------------------------------------------------------------------------------- 1 | package na.komi.kodesh.ui.setting 2 | 3 | import android.os.Bundle 4 | import android.util.TypedValue 5 | import android.view.View 6 | import androidx.core.content.ContextCompat 7 | import androidx.core.view.children 8 | import androidx.preference.ListPreference 9 | import androidx.preference.Preference 10 | import androidx.preference.PreferenceFragmentCompat 11 | import kotlinx.coroutines.CoroutineScope 12 | import kotlinx.coroutines.Dispatchers 13 | import kotlinx.coroutines.SupervisorJob 14 | import kotlinx.coroutines.cancelChildren 15 | import na.komi.kodesh.Prefs 16 | import na.komi.kodesh.R 17 | import na.komi.kodesh.ui.main.MainActivity 18 | import na.komi.kodesh.util.onClick 19 | import kotlin.coroutines.CoroutineContext 20 | 21 | class SettingsFragment : PreferenceFragmentCompat(), CoroutineScope { 22 | override val coroutineContext: CoroutineContext 23 | get() = Dispatchers.Main + job 24 | 25 | private val job = SupervisorJob() 26 | 27 | override fun onDestroy() { 28 | super.onDestroy() 29 | coroutineContext.cancelChildren() 30 | } 31 | 32 | override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { 33 | setPreferencesFromResource(R.xml.preferences, rootKey) 34 | } 35 | 36 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 37 | super.onViewCreated(view, savedInstanceState) 38 | val typedValue = TypedValue() 39 | requireContext().theme.resolveAttribute(android.R.attr.windowBackground, typedValue, true) 40 | val bgColor = typedValue.resourceId 41 | view.setBackgroundColor(ContextCompat.getColor(requireContext(), bgColor)) 42 | } 43 | 44 | override fun onActivityCreated(savedInstanceState: Bundle?) { 45 | super.onActivityCreated(savedInstanceState) 46 | view?.post { 47 | (requireActivity() as MainActivity).let { 48 | it.getToolbar()?.let { toolbar -> 49 | toolbar.title = getString(R.string.settings_title) 50 | for (a in toolbar.menu.children) 51 | a.isVisible = false 52 | } 53 | it.getToolbarTitleView()?.onClick {} 54 | it.getNavigationView().setCheckedItem(R.id.action_settings) 55 | 56 | } 57 | } 58 | val mListPreference = preferenceManager.findPreference("THEME_ID") ?: return 59 | mListPreference.value = Prefs.themeId.toString() 60 | mListPreference.onPreferenceChangeListener = 61 | Preference.OnPreferenceChangeListener { _, newValue -> 62 | if (mListPreference.value != newValue) { 63 | when (newValue) { 64 | "0" -> Prefs.themeId = 0 65 | "1" -> Prefs.themeId = 1 66 | "2" -> Prefs.themeId = 2 67 | } 68 | restartActivity() 69 | true 70 | } else 71 | false 72 | } 73 | } 74 | 75 | private fun restartActivity() { 76 | activity?.let { 77 | /*val intent = Intent(it, MainActivity::class.java) 78 | intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK and Intent.FLAG_ACTIVITY_NO_ANIMATION 79 | startActivity(intent) 80 | it.finish()*/ 81 | it.recreate() 82 | it.overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out) 83 | } 84 | } 85 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/na/komi/kodesh/ui/styling/StylingDialogFragment.kt: -------------------------------------------------------------------------------- 1 | package na.komi.kodesh.ui.styling 2 | 3 | import android.content.DialogInterface 4 | import android.os.Bundle 5 | import android.view.LayoutInflater 6 | import android.view.View 7 | import android.view.ViewGroup 8 | import androidx.preference.Preference 9 | import androidx.preference.SwitchPreferenceCompat 10 | import com.google.android.material.bottomsheet.BottomSheetBehavior 11 | import na.komi.kodesh.Prefs 12 | import na.komi.kodesh.R 13 | import na.komi.kodesh.ui.internal.BasePreferenceFragment 14 | import na.komi.kodesh.ui.internal.ExtendedBottomSheetDialogFragment 15 | import na.komi.kodesh.ui.main.MainActivity 16 | import na.komi.kodesh.ui.main.MainViewModel 17 | import na.komi.kodesh.util.closestKatana 18 | import na.komi.kodesh.util.setLowProfileStatusBar 19 | import org.rewedigital.katana.Component 20 | import org.rewedigital.katana.KatanaTrait 21 | import org.rewedigital.katana.androidx.viewmodel.activityViewModel 22 | 23 | class StylingDialogFragment : ExtendedBottomSheetDialogFragment() { 24 | 25 | override val initialState: Int 26 | get() = BottomSheetBehavior.STATE_COLLAPSED 27 | 28 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { 29 | return inflater.inflate(R.layout.fragment_dialog_container, container, false); 30 | } 31 | 32 | override fun onDismiss(dialog: DialogInterface) { 33 | super.onDismiss(dialog) 34 | (requireActivity() as MainActivity).setLowProfileStatusBar() 35 | } 36 | 37 | override fun onActivityCreated(savedInstanceState: Bundle?) { 38 | super.onActivityCreated(savedInstanceState) 39 | if (savedInstanceState == null) { 40 | childFragmentManager 41 | .beginTransaction() 42 | .replace(R.id.content, PreferenceFragment())//.add(R.id.content, PreferenceFragment()) 43 | .commit() 44 | } 45 | } 46 | 47 | /** https://is.gd/kdWs86 **/ 48 | class PreferenceFragment : BasePreferenceFragment(), KatanaTrait { 49 | override val component: Component by closestKatana() 50 | 51 | private val viewModel:MainViewModel by activityViewModel() 52 | 53 | override fun onCreatePreferences(bundle: Bundle, rootKey: String) { 54 | addPreferencesFromResource(R.xml.styling_preferences) 55 | } 56 | 57 | override fun onPreferenceTreeClick(preference: Preference): Boolean { 58 | var state = true 59 | when ((preference as SwitchPreferenceCompat).key) { 60 | "KJVSTYLE_ID" -> { 61 | Prefs.kjvStylingPref = preference.isChecked 62 | viewModel.kjvStyling = preference.isChecked 63 | } 64 | "DROP_CAP_ID" -> { 65 | Prefs.dropCapPref = preference.isChecked 66 | viewModel.showDropCap = preference.isChecked 67 | } 68 | "RED_LETTER_ID" -> { 69 | Prefs.redLetterPref = preference.isChecked 70 | viewModel.showRedLetters = preference.isChecked 71 | } 72 | "VERSE_NUMBERS" -> { 73 | Prefs.verseNumberPref = preference.isChecked 74 | viewModel.showVerseNumbers = preference.isChecked 75 | } 76 | else -> { 77 | state = super.onPreferenceTreeClick(preference) 78 | } 79 | } 80 | when (preference.key) { 81 | "KJVSTYLE_ID", "DROP_CAP_ID", "RED_LETTER_ID", "VERSE_NUMBERS" -> 82 | viewModel.setAdapterUpdate(viewModel.adapterUpdate.value?.let { !it } ?: true) 83 | } 84 | return state 85 | } 86 | } 87 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/na/komi/kodesh/ui/widget/NestedRecyclerView.kt: -------------------------------------------------------------------------------- 1 | package na.komi.kodesh.ui.widget 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import android.view.GestureDetector 6 | import android.view.MotionEvent 7 | import androidx.core.view.* 8 | import androidx.recyclerview.widget.RecyclerView 9 | 10 | class NestedRecyclerView : RecyclerView, NestedScrollingChild3, GestureDetector.OnGestureListener { 11 | constructor(context: Context) : super(context){ initialize() } 12 | 13 | constructor(context: Context, attrs: AttributeSet?) : super(context, attrs){ initialize() } 14 | 15 | constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { initialize() } 16 | 17 | init { 18 | mNestedScrollingChildHelper = NestedScrollingChildHelper(this) 19 | isNestedScrollingEnabled = true 20 | } 21 | 22 | private fun initialize() { 23 | mDetector = GestureDetectorCompat(context, this) 24 | } 25 | private var mNestedScrollingChildHelper : NestedScrollingChildHelper 26 | private lateinit var mDetector : GestureDetectorCompat 27 | 28 | override fun setNestedScrollingEnabled(enabled: Boolean) { 29 | @Suppress("UNNECESSARY_SAFE_CALL") 30 | mNestedScrollingChildHelper?.isNestedScrollingEnabled = true 31 | } 32 | 33 | override fun isNestedScrollingEnabled(): Boolean { 34 | 35 | return mNestedScrollingChildHelper.isNestedScrollingEnabled; 36 | } 37 | 38 | override fun startNestedScroll(axes: Int): Boolean { 39 | 40 | return mNestedScrollingChildHelper.startNestedScroll(axes) 41 | } 42 | 43 | override fun stopNestedScroll() { 44 | mNestedScrollingChildHelper.stopNestedScroll(); 45 | } 46 | 47 | override fun dispatchNestedScroll( 48 | dxConsumed: Int, 49 | dyConsumed: Int, 50 | dxUnconsumed: Int, 51 | dyUnconsumed: Int, 52 | offsetInWindow: IntArray?, 53 | type: Int 54 | ): Boolean { 55 | return mNestedScrollingChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow,type) 56 | } 57 | 58 | override fun dispatchNestedPreScroll( 59 | dx: Int, 60 | dy: Int, 61 | consumed: IntArray?, 62 | offsetInWindow: IntArray?, 63 | type: Int 64 | ): Boolean { 65 | 66 | return mNestedScrollingChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow,type); 67 | } 68 | 69 | override fun dispatchNestedFling(velocityX: Float, velocityY: Float, consumed: Boolean): Boolean { 70 | 71 | return mNestedScrollingChildHelper.dispatchNestedFling(velocityX, velocityY, consumed); 72 | } 73 | 74 | override fun dispatchNestedPreFling(velocityX: Float, velocityY: Float): Boolean { 75 | return mNestedScrollingChildHelper.dispatchNestedPreFling(velocityX, velocityY); 76 | } 77 | 78 | override fun onDetachedFromWindow() { 79 | super.onDetachedFromWindow() 80 | mNestedScrollingChildHelper.onDetachedFromWindow(); 81 | } 82 | 83 | override fun hasNestedScrollingParent(): Boolean { 84 | return mNestedScrollingChildHelper.hasNestedScrollingParent(); 85 | } 86 | 87 | override fun dispatchTouchEvent(ev: MotionEvent?): Boolean { 88 | super.dispatchTouchEvent(ev) 89 | 90 | val handled = mDetector.onTouchEvent(ev); 91 | if (!handled && ev?.action == MotionEvent.ACTION_UP) 92 | stopNestedScroll() 93 | 94 | return true 95 | } 96 | 97 | override fun onShowPress(e: MotionEvent?) { 98 | } 99 | 100 | override fun onSingleTapUp(e: MotionEvent?): Boolean { 101 | return false 102 | } 103 | 104 | override fun onDown(e: MotionEvent?): Boolean { 105 | startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL) 106 | return true 107 | } 108 | 109 | override fun onFling(e1: MotionEvent?, e2: MotionEvent?, velocityX: Float, velocityY: Float): Boolean { 110 | return true 111 | } 112 | 113 | override fun onScroll(e1: MotionEvent?, e2: MotionEvent?, distanceX: Float, distanceY: Float): Boolean { 114 | dispatchNestedPreScroll(distanceX.toInt(), distanceY.toInt(), null, null); 115 | dispatchNestedScroll(distanceX.toInt(), distanceY.toInt(), 0, 0, null); 116 | return true 117 | } 118 | 119 | override fun onLongPress(e: MotionEvent?) { 120 | } 121 | 122 | 123 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/na/komi/kodesh/ui/widget/NumberPicker2.kt: -------------------------------------------------------------------------------- 1 | package na.komi.kodesh.ui.widget 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import android.widget.NumberPicker 6 | import android.annotation.TargetApi 7 | import android.graphics.PorterDuff 8 | import android.graphics.drawable.Drawable 9 | import android.os.Build 10 | import androidx.annotation.ColorInt 11 | import na.komi.kodesh.R 12 | import android.util.TypedValue 13 | import na.komi.kodesh.util.log 14 | 15 | 16 | /** 17 | * https://stackoverflow.com/a/34449748 18 | */ 19 | class NumberPicker2 : NumberPicker { 20 | 21 | constructor(context: Context) : super(context) 22 | 23 | constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) 24 | 25 | constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) 26 | 27 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 28 | constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) 29 | 30 | init { 31 | /* 32 | 33 | val t = context.getPackageManager().getActivityInfo(ComponentName(context, MainActivity::class.java.name), 0).getThemeResource() 34 | val a = context.getTheme().obtainStyledAttributes( t, intArrayOf(R.attr.editTextColor) ) 35 | 36 | // Get color hex code (eg, #fff) 37 | val intColor = a.getColor(0 /* index */, 0 /* defaultVal */) 38 | val hexColor = Integer.toHexString(intColor) 39 | a.recycle() */ 40 | 41 | val typedValue = TypedValue() 42 | context.theme.resolveAttribute(R.attr.numberPickerDividerColor, typedValue, true) 43 | @ColorInt val color = typedValue.data 44 | setDividerColor(color)//ContextCompat.getColor(context, R.color.etc)) 45 | } 46 | 47 | fun setDividerColor(@ColorInt color: Int) { 48 | try { 49 | val fDividerDrawable = NumberPicker::class.java.getDeclaredField("mSelectionDivider") 50 | fDividerDrawable.isAccessible = true 51 | val d = fDividerDrawable.get(this) as Drawable 52 | d.setColorFilter(color, PorterDuff.Mode.SRC) 53 | d.invalidateSelf() 54 | postInvalidate() // Drawable is dirty 55 | } catch (e: Exception) { 56 | log wtf "$e" 57 | } 58 | 59 | } 60 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/na/komi/kodesh/ui/widget/ZoomNestedScollView.kt: -------------------------------------------------------------------------------- 1 | package na.komi.kodesh.ui.widget 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import android.util.Log 6 | import android.view.MotionEvent 7 | import androidx.core.widget.NestedScrollView 8 | import android.view.ScaleGestureDetector 9 | import android.util.TypedValue 10 | import androidx.appcompat.widget.AppCompatTextView 11 | 12 | 13 | class ZoomNestedScollView : NestedScrollView { 14 | 15 | constructor(context: Context) : super(context) {initialize()} 16 | 17 | constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {initialize()} 18 | 19 | constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {initialize()} 20 | 21 | private val TAG = "ZoomNestedScollView" 22 | private var mScaleDetector: ScaleGestureDetector? = null 23 | private var mScaleFactor = 1f 24 | private var defaultSize: Float = 0.toFloat() 25 | private var zoomLimit = 4.0f 26 | private var zoomEnabled = true 27 | 28 | private fun initialize() { 29 | defaultSize = 5f 30 | mScaleDetector = ScaleGestureDetector(context, ScaleListener()) 31 | } 32 | 33 | /*** 34 | * 35 | * @param zoomLimit 36 | * 37 | * Default value is 3, 3 means text can zoom 3 times the default size 38 | */ 39 | fun setZoomLimit(zoomLimit: Float) { 40 | this.zoomLimit = zoomLimit 41 | } 42 | 43 | override fun onTouchEvent(ev: MotionEvent?): Boolean { 44 | super.onTouchEvent(ev) 45 | 46 | 47 | mScaleDetector?.onTouchEvent(ev) 48 | return true 49 | } 50 | 51 | /*Scale Gesture listener class, 52 | 53 | mScaleFactor is getting the scaling value 54 | 55 | and mScaleFactor is mapped between 1.0 and and zoomLimit 56 | 57 | that is 4.0 by default. You can also change it. 4.0 means text 58 | 59 | can zoom to 4 times the default value.*/ 60 | 61 | 62 | private inner class ScaleListener : ScaleGestureDetector.SimpleOnScaleGestureListener() { 63 | 64 | override fun onScale(detector: ScaleGestureDetector): Boolean { 65 | 66 | mScaleFactor *= detector.scaleFactor 67 | 68 | mScaleFactor = Math.max(1.0f, Math.min(mScaleFactor, zoomLimit)) 69 | 70 | (getChildAt(0) as AppCompatTextView).setTextSize(TypedValue.COMPLEX_UNIT_PT, defaultSize * mScaleFactor) 71 | 72 | //Log.e(TAG, mScaleFactor.toString()) 73 | 74 | return true 75 | 76 | } 77 | 78 | override fun onScaleBegin(detector: ScaleGestureDetector?): Boolean { 79 | Log.e(TAG, "onScaleBegin") 80 | return super.onScaleBegin(detector) 81 | 82 | } 83 | override fun onScaleEnd(detector: ScaleGestureDetector?) { 84 | super.onScaleEnd(detector) 85 | Log.e(TAG, "onScaleEnd") 86 | 87 | 88 | } 89 | 90 | } 91 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/na/komi/kodesh/util/CoroutineUtils.kt: -------------------------------------------------------------------------------- 1 | package na.komi.kodesh.util 2 | 3 | import android.os.Handler 4 | import android.os.Looper 5 | import kotlinx.coroutines.* 6 | import kotlinx.coroutines.android.asCoroutineDispatcher 7 | import java.util.concurrent.Executors 8 | import kotlin.coroutines.CoroutineContext 9 | 10 | object ContextHelper : CoroutineScope { 11 | 12 | val looper = Looper.getMainLooper() 13 | 14 | val handler = Handler(looper) 15 | 16 | /** 17 | * Creating dispatcher from main handler to avoid IO 18 | * See https://github.com/Kotlin/kotlinx.coroutines/issues/878 19 | */ 20 | val dispatcher = handler.asCoroutineDispatcher("kod-main") 21 | 22 | override val coroutineContext: CoroutineContext get() = dispatcher 23 | } 24 | 25 | object Coroutines { 26 | fun io(work: suspend (() -> Unit)): Job = 27 | CoroutineScope(Dispatchers.IO).launch { 28 | work() 29 | } 30 | 31 | fun ioThenMain(work: suspend (() -> T?), callback: ((T?) -> Unit)): Job = 32 | CoroutineScope(Dispatchers.Main).launch { 33 | val data = CoroutineScope(Dispatchers.IO).async rt@{ 34 | return@rt work() 35 | }.await() 36 | callback(data) 37 | } 38 | val cachedExecutor by lazy { Executors.newCachedThreadPool()} 39 | val executorService by lazy { Executors.newFixedThreadPool(100) } 40 | val coroutineDispatcher = executorService.asCoroutineDispatcher() 41 | } 42 | 43 | val UI by lazy { Dispatchers.Main } -------------------------------------------------------------------------------- /app/src/main/kotlin/na/komi/kodesh/util/Executors.kt: -------------------------------------------------------------------------------- 1 | package na.komi.kodesh.util 2 | 3 | import java.util.concurrent.Executors 4 | 5 | val IO_EXECUTOR by lazy { Executors.newSingleThreadExecutor() } -------------------------------------------------------------------------------- /app/src/main/kotlin/na/komi/kodesh/util/InjectorUtils.kt: -------------------------------------------------------------------------------- 1 | package na.komi.kodesh.util 2 | 3 | import android.content.Context 4 | import na.komi.kodesh.model.ApplicationDatabase 5 | import na.komi.kodesh.model.MainRepository 6 | import na.komi.kodesh.ui.main.MainViewModelFactory 7 | 8 | /** 9 | * Static methods used to inject classes needed for various Activities and Fragments. 10 | */ 11 | object InjectorUtils { 12 | 13 | private fun getMainRepository(context: Context): MainRepository { 14 | return MainRepository.getInstance(ApplicationDatabase.getInstance(context).mainDao()) 15 | } 16 | 17 | fun provideMainViewModelFactory(context: Context): MainViewModelFactory { 18 | return MainViewModelFactory(getMainRepository(context)) 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/kotlin/na/komi/kodesh/util/KatanaExt.kt: -------------------------------------------------------------------------------- 1 | package na.komi.kodesh.util 2 | 3 | import androidx.fragment.app.Fragment 4 | import na.komi.kodesh.ui.main.MainComponent 5 | import org.rewedigital.katana.KatanaTrait 6 | 7 | /** 8 | * Gets the component from the activity hosting the Fragment. 9 | */ 10 | fun T.closestKatana() where T : KatanaTrait, T : Fragment = lazy { MainComponent.mainComponent } 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/kotlin/na/komi/kodesh/util/SystemConfig.kt: -------------------------------------------------------------------------------- 1 | package na.komi.kodesh.util 2 | 3 | import android.annotation.TargetApi 4 | import android.app.Activity 5 | import android.content.Context 6 | import android.content.res.Resources 7 | import android.os.Build 8 | import android.util.TypedValue 9 | 10 | /** 11 | * Created by kelin on 2017/5/31. 12 | */ 13 | 14 | class SystemConfig internal constructor(activity: Activity, private val mTranslucentStatusBar: Boolean) { 15 | /** 16 | * Get the height of the system status bar. 17 | * 18 | * @return The height of the status bar (in pixels). 19 | */ 20 | val statusBarHeight: Int 21 | /** 22 | * Get the height of the action bar. 23 | * 24 | * @return The height of the action bar (in pixels). 25 | */ 26 | val actionBarHeight: Int 27 | 28 | init { 29 | val res = activity.resources 30 | statusBarHeight = getInternalDimensionSize(res, STATUS_BAR_HEIGHT_RES_NAME) 31 | actionBarHeight = getActionBarHeight(activity) 32 | } 33 | 34 | @TargetApi(14) 35 | private fun getActionBarHeight(context: Context): Int { 36 | var result = 0 37 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { 38 | val tv = TypedValue() 39 | context.theme.resolveAttribute(android.R.attr.actionBarSize, tv, true) 40 | result = context.resources.getDimensionPixelSize(tv.resourceId) 41 | } 42 | return result 43 | } 44 | 45 | private fun getInternalDimensionSize(res: Resources, key: String): Int { 46 | var result = 0 47 | val resourceId = res.getIdentifier(key, "dimen", "android") 48 | if (resourceId > 0) { 49 | result = res.getDimensionPixelSize(resourceId) 50 | } 51 | return result 52 | } 53 | 54 | /** 55 | * Get the layout inset for any system UI that appears at the top of the screen. 56 | * 57 | * @param withActionBar True to include the height of the action bar, False otherwise. 58 | * @return The layout inset (in pixels). 59 | */ 60 | fun getPixelInsetTop(withActionBar: Boolean): Int { 61 | return (if (mTranslucentStatusBar) statusBarHeight else 0) + if (withActionBar) actionBarHeight else 0 62 | } 63 | 64 | companion object { 65 | 66 | private val STATUS_BAR_HEIGHT_RES_NAME = "status_bar_height" 67 | private val NAV_BAR_HEIGHT_RES_NAME = "navigation_bar_height" 68 | private val NAV_BAR_HEIGHT_LANDSCAPE_RES_NAME = "navigation_bar_height_landscape" 69 | private val NAV_BAR_WIDTH_RES_NAME = "navigation_bar_width" 70 | private val SHOW_NAV_BAR_RES_NAME = "config_showNavigationBar" 71 | } 72 | 73 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/na/komi/kodesh/util/ThemeUtils.kt: -------------------------------------------------------------------------------- 1 | package na.komi.kodesh.util 2 | 3 | import android.app.Activity 4 | import android.os.Build 5 | import android.view.View 6 | import androidx.appcompat.app.AppCompatActivity 7 | import na.komi.kodesh.Application 8 | import na.komi.kodesh.Prefs 9 | import na.komi.kodesh.R 10 | 11 | object ThemeUtils { 12 | private var sTheme: Int = 0 13 | val LIGHT = 0 14 | val DARK = 1 15 | val BLACK = 2 16 | /** 17 | * Set the theme of the Activity, and restart it by creating a new Activity of the same type. 18 | */ 19 | fun changeToTheme(activity: AppCompatActivity, theme: Int) { 20 | Prefs.themeId = theme 21 | activity.recreate() 22 | } 23 | 24 | /** Set the theme of the activity, according to the configuration. */ 25 | fun onActivityCreateSetTheme(activity: Activity) { 26 | when (Prefs.themeId ) { 27 | DARK -> activity.setTheme(R.style.Theme_Dark) 28 | BLACK -> activity.setTheme(R.style.Theme_Black) 29 | else -> activity.setTheme(R.style.Theme_Light) 30 | } 31 | } 32 | 33 | val SYSTEM_UI_FLAG_LOW_PROFILE = View.SYSTEM_UI_FLAG_LOW_PROFILE 34 | val CLEAR_SYSTEM_UI_FLAG_LOW_PROFILE = View.SYSTEM_UI_FLAG_LOW_PROFILE.inv() 35 | val SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR = View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR 36 | val SYSTEM_UI_FLAG_LIGHT_STATUS_BAR = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR 37 | val CLEAR_SYSTEM_UI_FLAG_LIGHT_STATUS_BAR = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.inv() 38 | 39 | fun setBar(TYPE: Int, activity: AppCompatActivity) { 40 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 41 | val view = activity.window.decorView //findViewById(R.id.container_main) 42 | var flags = view.systemUiVisibility 43 | flags = flags or TYPE 44 | view.systemUiVisibility = flags 45 | //activity.window.statusBarColor = Color.WHITE 46 | } 47 | } 48 | 49 | } 50 | 51 | fun AppCompatActivity.setLowProfileStatusBar() = ThemeUtils.setBar(ThemeUtils.SYSTEM_UI_FLAG_LOW_PROFILE, this) 52 | fun AppCompatActivity.clearLowProfileStatusBar() = ThemeUtils.setBar(ThemeUtils.CLEAR_SYSTEM_UI_FLAG_LOW_PROFILE, this) 53 | fun AppCompatActivity.setLightNavBar() = ThemeUtils.setBar(ThemeUtils.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR, this) 54 | fun AppCompatActivity.setLightStatusBar() = ThemeUtils.setBar(ThemeUtils.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR, this) 55 | fun AppCompatActivity.clearLightStatusBar() = ThemeUtils.setBar(ThemeUtils.CLEAR_SYSTEM_UI_FLAG_LIGHT_STATUS_BAR, this) 56 | 57 | 58 | -------------------------------------------------------------------------------- /app/src/main/kotlin/na/komi/kodesh/util/Utils.kt: -------------------------------------------------------------------------------- 1 | package na.komi.kodesh.util 2 | 3 | import android.app.Activity 4 | import android.content.Context 5 | import android.content.res.Resources 6 | import android.util.Log 7 | import android.util.TypedValue 8 | import android.view.View 9 | import android.view.inputmethod.InputMethodManager 10 | import androidx.annotation.DimenRes 11 | import na.komi.kodesh.Application 12 | import na.komi.kodesh.BuildConfig 13 | import java.util.regex.Pattern 14 | 15 | object log { 16 | 17 | infix fun d(message: Any) { 18 | if (BuildConfig.DEBUG) 19 | Log.d(getTag(), message.toString()) 20 | } 21 | 22 | infix fun v(message: String) { 23 | if (BuildConfig.DEBUG) 24 | Log.v(getTag(), message) 25 | } 26 | 27 | infix fun i(message: String) { 28 | if (BuildConfig.DEBUG) 29 | Log.i(getTag(), message) 30 | } 31 | 32 | infix fun w(message: String) { 33 | //if (BuildConfig.DEBUG) 34 | Log.w(getTag(), message) 35 | } 36 | 37 | infix fun e(message: String) { 38 | //if (BuildConfig.DEBUG) 39 | Log.e(getTag(), message) 40 | } 41 | 42 | infix fun wtf(message: String) { 43 | //if (BuildConfig.DEBUG) 44 | Log.wtf(getTag(), message) 45 | } 46 | 47 | 48 | private val CALL_STACK_INDEX = 5 49 | private val ANONYMOUS_CLASS = Pattern.compile("(\\$\\d+)+$") 50 | private val name = BuildConfig.APPLICATION_ID.substring(BuildConfig.APPLICATION_ID.indexOfLast { it == '.' } + 1) 51 | private fun getTag(): String { 52 | /*val stackTrace = Throwable().stackTrace 53 | if (stackTrace.size <= CALL_STACK_INDEX) { 54 | throw IllegalStateException( 55 | "Synthetic stacktrace didn't have enough elements: are you using proguard?" 56 | ) 57 | }*/ 58 | return name//createStackElementTag(stackTrace[CALL_STACK_INDEX]) 59 | } 60 | 61 | private fun createStackElementTag(element: StackTraceElement): String { 62 | var tag = element.className 63 | val m = ANONYMOUS_CLASS.matcher(tag) 64 | if (m.find()) { 65 | tag = m.replaceAll("") 66 | } 67 | return tag.substring(tag.lastIndexOf('.') + 1) 68 | } 69 | } 70 | 71 | inline fun tryy(block: () -> Unit) { 72 | try { 73 | block() 74 | } catch (e: Exception) { 75 | Log.wtf("TAG", e) 76 | //throw RuntimeException(e)//Log.d("TAG", e.message)//e.printStackTrace() 77 | } 78 | } 79 | 80 | 81 | inline fun measureTimeMillis(block: () -> Unit): Long { 82 | val startTime = System.currentTimeMillis() 83 | block.invoke() 84 | return System.currentTimeMillis() - startTime 85 | } 86 | 87 | inline fun benchmark(range: IntRange, block: () -> Unit): Double { 88 | return (range).map { 89 | measureTimeMillis { block() } 90 | }.average() 91 | } 92 | 93 | 94 | fun Context.dimen(@DimenRes id: Int): Float = resources.getDimension(id) 95 | inline val Float.dpToPx: Float 96 | get() = this * Resources.getSystem().displayMetrics.density 97 | 98 | inline val Int.dpToPx: Int 99 | get() = toFloat().dpToPx.toInt() 100 | 101 | inline val Float.pxToDp: Float 102 | get() = this / Resources.getSystem().displayMetrics.density 103 | 104 | inline val Int.pxToDp: Int 105 | get() = toFloat().pxToDp.toInt() 106 | 107 | inline val Float.dpToSp: Float 108 | get() = this * Resources.getSystem().displayMetrics.scaledDensity 109 | 110 | inline val Int.dpToSp: Int 111 | get() = toFloat().dpToSp.toInt() 112 | 113 | inline val Float.spToDp: Float 114 | get() = this / Resources.getSystem().displayMetrics.scaledDensity 115 | 116 | inline val Int.spToDp: Int 117 | get() = toFloat().spToDp.toInt() 118 | 119 | fun View.hideKeyboard() { 120 | val inputMethodManager = Application.instance.getSystemService(Activity.INPUT_METHOD_SERVICE) as? InputMethodManager 121 | inputMethodManager?.hideSoftInputFromWindow(this.windowToken, 0) 122 | } 123 | 124 | fun dp(size: Float) = TypedValue.applyDimension( 125 | TypedValue.COMPLEX_UNIT_DIP, 126 | size, 127 | Application.instance.applicationContext.resources.displayMetrics 128 | ) 129 | 130 | fun MutableList.groupConsecutiveString(): String { 131 | val listMain = this.groupConsecutive() 132 | var str = "" 133 | listMain.forEachIndexed { idx, it -> 134 | if(it.size > 1) str += "${it.min()}-${it.max()}" 135 | else 136 | str += it.get(0) 137 | if(str.isNotEmpty() && idx != listMain.size-1) str += ", " 138 | } 139 | return str 140 | } 141 | fun MutableList.groupConsecutive(): MutableList> { 142 | this.sort() 143 | 144 | val listMain = ArrayList>() 145 | var temp: MutableList = ArrayList() 146 | 147 | for (i in this.indices) { 148 | if (i + 1 < this.size && this[i] + 1 == this[i + 1]) { 149 | temp.add(this[i]) 150 | } else { 151 | temp.add(this[i]) 152 | listMain.add(temp) 153 | temp = ArrayList() 154 | } 155 | 156 | } 157 | return listMain.toMutableList() 158 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/na/komi/kodesh/util/livedata/LiveDataExt.kt: -------------------------------------------------------------------------------- 1 | package na.komi.kodesh.util.livedata 2 | 3 | import android.os.Looper 4 | import androidx.lifecycle.LiveData 5 | import com.hadilq.liveevent.LiveEvent 6 | 7 | // Returns non-null LiveEvent 8 | val LiveData.raw 9 | get() = this.value!! 10 | 11 | // LiveEvent with initializer 12 | // SingleLiveEvent 13 | fun LiveEvent(initialValue: T): LiveEvent = LiveEvent().apply { 14 | if (Looper.myLooper() == Looper.getMainLooper()) 15 | setValue(initialValue) 16 | else 17 | postValue(initialValue) 18 | } 19 | 20 | fun LiveData.toSingleEvent(): LiveData { 21 | val result = LiveEvent() 22 | result.addSource(this) { 23 | result.postValue(it) 24 | } 25 | return result 26 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/na/komi/kodesh/util/livedata/NonNullMediatorLiveData.kt: -------------------------------------------------------------------------------- 1 | package na.komi.kodesh.util.livedata 2 | 3 | import androidx.lifecycle.LifecycleOwner 4 | import androidx.lifecycle.LiveData 5 | import androidx.lifecycle.MediatorLiveData 6 | import androidx.lifecycle.Observer 7 | 8 | class NonNullMediatorLiveData : MediatorLiveData() 9 | 10 | fun LiveData.nonNull(): NonNullMediatorLiveData { 11 | val mediator: NonNullMediatorLiveData = NonNullMediatorLiveData() 12 | mediator.addSource(this, Observer { it?.let { mediator.value = it } }) 13 | return mediator 14 | } 15 | 16 | fun NonNullMediatorLiveData.observe(owner: LifecycleOwner, observer: (t: T) -> Unit) { 17 | this.observe(owner, Observer { 18 | it?.let(observer) 19 | }) 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/kotlin/na/komi/kodesh/util/page/Fonts.kt: -------------------------------------------------------------------------------- 1 | package na.komi.kodesh.util.page 2 | 3 | import android.graphics.Typeface 4 | import na.komi.kodesh.Application 5 | import android.graphics.Paint 6 | import android.text.TextPaint 7 | import android.text.style.TypefaceSpan 8 | 9 | object Fonts { 10 | private val fontAssets by lazy { Application.instance.assets } 11 | 12 | private const val Merriweather_Black_Path = "fonts/Merriweather-Black.otf" 13 | val Merriweather_Black: Typeface by lazy { Typeface.createFromAsset(fontAssets, Merriweather_Black_Path) } 14 | 15 | private const val GentiumPlus_R_Path = "fonts/GentiumPlus-R.otf" 16 | val GentiumPlus_R: Typeface by lazy { Typeface.createFromAsset(fontAssets, GentiumPlus_R_Path) } 17 | 18 | private const val GentiumPlus_I_Path = "fonts/GentiumPlus-I.otf" 19 | val GentiumPlus_I: Typeface by lazy { Typeface.createFromAsset(fontAssets, GentiumPlus_I_Path) } 20 | /* 21 | Typeface nunito = getResources().getFont(R.font.nunito); 22 | TextView text = (TextView)findViewById(R.id.nunito_programmatic); 23 | text.setTypeface(nunito, Typeface.BOLD_ITALIC); 24 | */ 25 | } 26 | 27 | 28 | class CustomTypefaceSpan(private val newType: Typeface, family: String = "") : TypefaceSpan(family) { 29 | 30 | override fun updateDrawState(ds: TextPaint) { 31 | applyCustomTypeFace(ds, newType) 32 | } 33 | 34 | override fun updateMeasureState(paint: TextPaint) { 35 | applyCustomTypeFace(paint, newType) 36 | } 37 | 38 | private fun applyCustomTypeFace(paint: Paint, tf: Typeface) { 39 | val oldStyle: Int 40 | val old = paint.typeface 41 | oldStyle = old?.style ?: 0 42 | 43 | val fake = oldStyle and tf.style.inv() 44 | if (fake and Typeface.BOLD != 0) { 45 | paint.isFakeBoldText = true 46 | } 47 | 48 | if (fake and Typeface.ITALIC != 0) { 49 | paint.textSkewX = -0.25f 50 | } 51 | 52 | paint.typeface = tf 53 | } 54 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/na/komi/kodesh/util/page/LongClickLinkMovementMethod.kt: -------------------------------------------------------------------------------- 1 | package na.komi.kodesh.util.page 2 | 3 | import android.os.Handler 4 | import android.text.Selection 5 | import android.text.Spannable 6 | import android.text.method.LinkMovementMethod 7 | import android.text.method.MovementMethod 8 | import android.view.MotionEvent 9 | import android.widget.TextView 10 | 11 | /** 12 | * https://stackoverflow.com/a/31786969 13 | */ 14 | class LongClickLinkMovementMethod : LinkMovementMethod() { 15 | 16 | private var mLongClickHandler: Handler? = null 17 | private var mIsLongPressed = false 18 | 19 | override fun onTouchEvent( 20 | widget: TextView, buffer: Spannable, 21 | event: MotionEvent 22 | ): Boolean { 23 | val action = event.action 24 | 25 | if (action == MotionEvent.ACTION_CANCEL) { 26 | if (mLongClickHandler != null) { 27 | mLongClickHandler!!.removeCallbacksAndMessages(null) 28 | } 29 | } 30 | 31 | if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_DOWN) { 32 | var x = event.x.toInt() 33 | var y = event.y.toInt() 34 | 35 | x -= widget.totalPaddingLeft 36 | y -= widget.totalPaddingTop 37 | 38 | x += widget.scrollX 39 | y += widget.scrollY 40 | 41 | val layout = widget.layout 42 | val line = layout.getLineForVertical(y) 43 | val off = layout.getOffsetForHorizontal(line, x.toFloat()) 44 | 45 | val link = buffer.getSpans(off, off, LongClickableSpan::class.java) 46 | 47 | if (link.size != 0) { 48 | if (action == MotionEvent.ACTION_UP) { 49 | if (mLongClickHandler != null) { 50 | mLongClickHandler!!.removeCallbacksAndMessages(null) 51 | } 52 | if (!mIsLongPressed) { 53 | link[0].onClick(widget) 54 | } 55 | mIsLongPressed = false 56 | } else { 57 | Selection.setSelection( 58 | buffer, 59 | buffer.getSpanStart(link[0]), 60 | buffer.getSpanEnd(link[0]) 61 | ) 62 | mLongClickHandler!!.postDelayed(Runnable { 63 | link[0].onLongClick(widget) 64 | mIsLongPressed = true 65 | }, LONG_CLICK_TIME.toLong()) 66 | } 67 | return true 68 | } 69 | } 70 | 71 | return super.onTouchEvent(widget, buffer, event) 72 | } 73 | 74 | companion object { 75 | private val LONG_CLICK_TIME = 500 76 | 77 | 78 | val instance: MovementMethod 79 | get() { 80 | if (sInstance == null) { 81 | sInstance = LongClickLinkMovementMethod() 82 | sInstance!!.mLongClickHandler = Handler() 83 | } 84 | 85 | return sInstance!! 86 | } 87 | private var sInstance: LongClickLinkMovementMethod? = null 88 | } 89 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/na/komi/kodesh/util/page/LongClickableSpanClass.kt: -------------------------------------------------------------------------------- 1 | package na.komi.kodesh.util.page 2 | 3 | import android.text.style.ClickableSpan 4 | import android.view.View 5 | 6 | 7 | abstract class LongClickableSpan : ClickableSpan() { 8 | 9 | abstract fun onLongClick(view: View) 10 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/na/komi/kodesh/util/page/MySpannableFactory.kt: -------------------------------------------------------------------------------- 1 | package na.komi.kodesh.util.page 2 | 3 | import android.text.Spannable 4 | 5 | class MySpannableFactory : Spannable.Factory(){ 6 | override fun newSpannable(source: CharSequence?): Spannable { 7 | return source as? Spannable ?: super.newSpannable(source) 8 | } 9 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/na/komi/kodesh/util/text/CustomMovementMethod.kt: -------------------------------------------------------------------------------- 1 | package na.komi.kodesh.util.text 2 | 3 | import android.text.Selection 4 | import android.text.Spannable 5 | import android.text.method.LinkMovementMethod 6 | import android.text.method.Touch 7 | import android.text.style.ClickableSpan 8 | import android.view.MotionEvent 9 | import android.view.View 10 | import android.widget.TextView 11 | 12 | 13 | class CustomMovementMethod : LinkMovementMethod() { 14 | 15 | companion object { 16 | private val LONG_CLICK_TIME = 500 17 | 18 | 19 | val instance: LinkMovementMethod 20 | get() { 21 | if (sInstance == null) { 22 | sInstance = CustomMovementMethod() 23 | } 24 | return sInstance!! 25 | } 26 | private var sInstance: CustomMovementMethod? = null 27 | } 28 | 29 | override fun canSelectArbitrarily(): Boolean { 30 | return true 31 | } 32 | 33 | override fun initialize(widget: TextView, text: Spannable) { 34 | Selection.setSelection(text, text.length) 35 | } 36 | 37 | override fun onTakeFocus(view: TextView, text: Spannable, dir: Int) { 38 | @Suppress("DEPRECATED_IDENTITY_EQUALS") 39 | if (dir and (View.FOCUS_FORWARD or View.FOCUS_DOWN) !== 0) { 40 | if (view.layout == null) { 41 | // This shouldn't be null, but do something sensible if it is. 42 | Selection.setSelection(text, text.length) 43 | } 44 | } else { 45 | Selection.setSelection(text, text.length) 46 | } 47 | } 48 | 49 | override fun onTouchEvent( 50 | widget: TextView, buffer: Spannable, 51 | event: MotionEvent 52 | ): Boolean { 53 | val action = event.action 54 | 55 | if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_DOWN) { 56 | var x = event.x.toInt() 57 | var y = event.y.toInt() 58 | 59 | x -= widget.totalPaddingLeft 60 | y -= widget.totalPaddingTop 61 | 62 | x += widget.scrollX 63 | y += widget.scrollY 64 | 65 | val layout = widget.layout 66 | val line = layout.getLineForVertical(y) 67 | val off = layout.getOffsetForHorizontal(line, x.toFloat()) 68 | 69 | val link = buffer.getSpans(off, off, ClickableSpan::class.java) 70 | 71 | if (link.size != 0) { 72 | if (action == MotionEvent.ACTION_UP) { 73 | link[0].onClick(widget) 74 | } else if (action == MotionEvent.ACTION_DOWN) { 75 | Selection.setSelection( 76 | buffer, 77 | buffer.getSpanStart(link[0]), 78 | buffer.getSpanEnd(link[0]) 79 | ) 80 | } 81 | return true 82 | } 83 | } 84 | 85 | return Touch.onTouchEvent(widget, buffer, event) 86 | } 87 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/na/komi/kodesh/util/text/DropCapSpan.kt: -------------------------------------------------------------------------------- 1 | package na.komi.kodesh.util.text 2 | 3 | import android.graphics.Paint 4 | import android.text.style.LineHeightSpan 5 | import android.graphics.Rect 6 | import android.text.TextPaint 7 | 8 | /** 9 | * Span that applies a margin after the initial string. 10 | * This is better that LeadingMarginSpan because that only 11 | * applies to the "first line of paragraph". 12 | * 13 | * This also preserves the text bounds so that Selection and 14 | * background color are left untouched. This prevents you from 15 | * having to use a DropCap + LineOverLapSpan + RestOfText 16 | */ 17 | 18 | // https://stackoverflow.com/a/12925719 19 | class Height(private var mSize: Int = 0) : LineHeightSpan.WithDensity { 20 | private var sProportion = 0f 21 | 22 | override fun chooseHeight( 23 | text: CharSequence, start: Int, end: Int, 24 | spanstartv: Int, v: Int, 25 | fm: Paint.FontMetricsInt 26 | ) { 27 | // Should not get called, at least not by StaticLayout. 28 | chooseHeight(text, start, end, spanstartv, v, fm, null) 29 | } 30 | 31 | override fun chooseHeight( 32 | text: CharSequence, start: Int, end: Int, 33 | spanstartv: Int, v: Int, 34 | fm: Paint.FontMetricsInt, paint: TextPaint? 35 | ) { 36 | var size = mSize 37 | if (paint != null) { 38 | size *= paint.density.toInt() 39 | } 40 | 41 | if (fm.bottom - fm.top < size) { 42 | fm.top = fm.bottom - size 43 | fm.ascent = fm.ascent - size 44 | } else { 45 | if (sProportion == 0f) { 46 | /* 47 | * Calculate what fraction of the nominal ascent 48 | * the height of a capital letter actually is, 49 | * so that we won't reduce the ascent to less than 50 | * that unless we absolutely have to. 51 | */ 52 | 53 | val p = Paint() 54 | p.textSize = 100f 55 | val r = Rect() 56 | p.getTextBounds("ABCDEFG", 0, 7, r) 57 | 58 | sProportion = r.top / p.ascent() 59 | } 60 | 61 | val need = Math.ceil((-fm.top * sProportion).toDouble()).toInt() 62 | 63 | if (size - fm.descent >= need) { 64 | /* 65 | * It is safe to shrink the ascent this much. 66 | */ 67 | 68 | fm.top = fm.bottom - size 69 | fm.ascent = fm.descent - size 70 | } else if (size >= need) { 71 | /* 72 | * We can't show all the descent, but we can at least 73 | * show all the ascent. 74 | */ 75 | 76 | fm.ascent = -need 77 | fm.top = fm.ascent 78 | fm.descent = fm.top + size 79 | fm.bottom = fm.descent 80 | } else { 81 | /* 82 | * Show as much of the ascent as we can, and no descent. 83 | */ 84 | 85 | fm.ascent = -size 86 | fm.top = fm.ascent 87 | fm.descent = 0 88 | fm.bottom = fm.descent 89 | } 90 | } 91 | } 92 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/na/komi/kodesh/util/text/LettrineLeadingMarginSpan2.kt: -------------------------------------------------------------------------------- 1 | package na.komi.kodesh.util.text 2 | 3 | import android.graphics.Canvas 4 | import android.graphics.Color 5 | import android.graphics.Paint 6 | import android.graphics.Rect 7 | import android.os.Build 8 | import android.text.Layout 9 | import android.text.StaticLayout 10 | import android.text.TextPaint 11 | import android.text.style.LeadingMarginSpan 12 | import na.komi.kodesh.Prefs 13 | import na.komi.kodesh.util.log 14 | import na.komi.kodesh.util.page.Fonts 15 | import na.komi.kodesh.util.sp 16 | import kotlin.math.roundToInt 17 | 18 | 19 | class LettrineLeadingMarginSpan2(private val str: String) : 20 | LeadingMarginSpan.LeadingMarginSpan2 { 21 | 22 | /** 23 | * Change detection from newline to whatever 24 | * So we can control where to start the margin 25 | */ 26 | 27 | private var margin2 = 0 28 | private var PADDING_BOUNDS = 20 29 | 30 | override fun getLeadingMargin(first: Boolean): Int { 31 | return if (first) margin2 else 0 32 | } 33 | 34 | override fun getLeadingMarginLineCount(): Int { 35 | return 2 36 | } 37 | 38 | val bounds = Rect() 39 | var pp: Paint? = null 40 | var init = false 41 | val tp by lazy { 42 | TextPaint().apply { 43 | isAntiAlias = true 44 | color = Color.WHITE 45 | style = Paint.Style.FILL 46 | typeface = Fonts.GentiumPlus_R 47 | textSize = sp(Prefs.mainFontSize * 4) 48 | } 49 | } 50 | 51 | 52 | var sLayout: StaticLayout? = null 53 | fun getStaticLayout(str: CharSequence, textSize: Float, width: Int): StaticLayout { 54 | 55 | if (tp.textSize == textSize && sLayout != null) return sLayout!! 56 | if (tp.textSize != textSize || sLayout == null) { 57 | tp.textSize = textSize 58 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { 59 | log d "Create StaticLayout" 60 | val builder = StaticLayout.Builder.obtain(str, 0, str.length, tp, width) 61 | .setAlignment(Layout.Alignment.ALIGN_NORMAL) 62 | .setLineSpacing(0f, 1f) 63 | .setIncludePad(false) 64 | //.setMaxLines(1) 65 | .setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_NONE) 66 | return builder.build() 67 | } else { 68 | @Suppress("DEPRECATION") 69 | return StaticLayout( 70 | str, 71 | tp, 72 | width, 73 | Layout.Alignment.ALIGN_NORMAL, 74 | 1f, 75 | 0f, 76 | false 77 | ) 78 | } 79 | } 80 | return sLayout!! 81 | } 82 | 83 | override fun drawLeadingMargin( 84 | c: Canvas, p: Paint, x: Int, dir: Int, 85 | top: Int, baseline: Int, bottom: Int, text: CharSequence, 86 | start: Int, end: Int, first: Boolean, layout: Layout 87 | ) { 88 | //if (first && start - 2 >= 0) { 89 | if (!first) return 90 | 91 | tp.textSize = sp(Prefs.mainFontSize * 4) 92 | val tempLayout: StaticLayout = getStaticLayout(text.take(1).toString(), tp.textSize, layout.width) 93 | 94 | val lineCount = tempLayout.lineCount 95 | var textWidth = 0f 96 | for (i in 0 until lineCount) { 97 | textWidth += tempLayout.getLineWidth(i) 98 | } 99 | 100 | margin2 = textWidth.roundToInt() + PADDING_BOUNDS 101 | // if (!init) 102 | // Save coordinates beforehand 103 | c.save() 104 | 105 | c.translate(x.toFloat(), layout.paint.ascent() - PADDING_BOUNDS-10) 106 | tempLayout.draw(c) 107 | 108 | c.restore() 109 | 110 | //log d "Called drawleadingmargin $start ~ $end | DropCap: ${text.subSequence(start - 2, start ).take(1)} | margin2: $margin2" 111 | //} 112 | } 113 | 114 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/na/komi/kodesh/util/text/LineOverlapSpan.kt: -------------------------------------------------------------------------------- 1 | package na.komi.kodesh.util.text 2 | 3 | import android.graphics.Paint 4 | import android.text.style.LineHeightSpan 5 | 6 | /** 7 | * Diagram: is.gd/uhKKGO 8 | * Android span shift 9 | * https://is.gd/coGGJ9 10 | */ 11 | class LineOverlapSpan(private val adjust: Int = 0) : LineHeightSpan { 12 | override fun chooseHeight(text: CharSequence, start: Int, end: Int, spanstartv: Int, v: Int, fm: Paint.FontMetricsInt) { 13 | fm.bottom += fm.top //- (fm.descent + fm.bottom) 14 | fm.descent += fm.top + adjust*3 15 | } 16 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/na/komi/kodesh/util/text/StringExt.kt: -------------------------------------------------------------------------------- 1 | package na.komi.kodesh.util.text 2 | 3 | import android.text.Spannable 4 | import android.text.SpannableStringBuilder 5 | import android.text.Spanned 6 | import android.text.style.CharacterStyle 7 | import androidx.appcompat.widget.AppCompatTextView 8 | import androidx.core.text.PrecomputedTextCompat 9 | import na.komi.kodesh.util.IO_EXECUTOR 10 | import na.komi.kodesh.util.log 11 | 12 | /** 13 | * Count Occurrences of a String in a String 14 | */ 15 | inline fun CharSequence.count(sub: String, ignoreCase: Boolean = false, action: (start: Int, end: Int, countSoFar: Int) -> Unit = { _, _, _ -> }): Int { 16 | var count = 0 17 | var startIdx = 0 18 | while ({ indexOf(sub, startIdx, ignoreCase = ignoreCase).also { startIdx = it + 1 } }() >= 0) { 19 | count++ 20 | action(startIdx - 1, startIdx - 1 + sub.length, count) 21 | } 22 | return count 23 | } 24 | 25 | inline fun CharSequence.count(sub: Char, action: (start: Int, end: Int, countSoFar: Int) -> Unit = { _, _, _ -> }): Int { 26 | var count = 0 27 | var startIdx = 0 28 | while (indexOf(sub, startIdx).also { startIdx = it + 1 } >= 0) { 29 | count++ 30 | action(startIdx - 1, startIdx, count) 31 | } 32 | return count 33 | } 34 | 35 | inline fun SpannableStringBuilder.withSpan(span: Any, action: SpannableStringBuilder.() -> Unit = {}): SpannableStringBuilder { 36 | val from = length 37 | action() 38 | setSpan(span, if (from == length) 0 else from, length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) 39 | return this 40 | } 41 | 42 | fun AppCompatTextView.futureSet(charSequence: CharSequence) { 43 | setTextFuture(PrecomputedTextCompat.getTextFuture(charSequence, this.textMetricsParamsCompat, null)) 44 | //val future = PrecomputedTextCompat.getTextFuture(charSequence, this.textMetricsParamsCompat, IO_EXECUTOR) 45 | //this.setTextFuture(future!!) 46 | } 47 | 48 | /** 49 | * Removes all spans given a class 50 | */ 51 | fun Spannable.removeSpans(start: Int, end: Int, aClass: Class) { 52 | val spans = getSpans(start, end, aClass) 53 | if (spans.isNotEmpty()) { 54 | for (span in spans) { 55 | removeSpan(span) 56 | } 57 | } 58 | } 59 | 60 | fun SpannableStringBuilder.deleteAll(str: String): SpannableStringBuilder{ 61 | var i = 0 62 | while(i >=0 && indexOf(str,i).also { i = it } >= 0 ) 63 | delete(i,i+str.length) 64 | return this 65 | } 66 | fun SpannableStringBuilder.spanBetween(what: Any, begin: String, end: String, removeDelimiters: Boolean): SpannableStringBuilder { 67 | 68 | val e = indexOf(end) 69 | if (e >= 0) { 70 | if (removeDelimiters) 71 | delete(e, e + end.length) 72 | val s = indexOf(begin) 73 | if (s >= 0) { 74 | if (removeDelimiters) 75 | delete(s, s + begin.length) 76 | setSpan(CharacterStyle.wrap(what as CharacterStyle), s, e - begin.length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE) 77 | } 78 | } 79 | return this 80 | 81 | } 82 | 83 | /** https://stackoverflow.com/a/34697667 **/ 84 | fun SpannableStringBuilder.spanBetweenEach(what: Any, begin: String, end: String, removeDelimiters: Boolean): SpannableStringBuilder { 85 | var i = indexOf(begin) 86 | while (i.also {i = indexOf(begin, i+1) } >= 0) { 87 | spanBetween(CharacterStyle.wrap(what as CharacterStyle), begin, end, removeDelimiters) 88 | 89 | } 90 | return this 91 | } 92 | 93 | fun SpannableStringBuilder.capitalizeUntil(search: String): SpannableStringBuilder { 94 | val found = indexOf(search) 95 | if (found >= 0) { 96 | val newStr = substring(0, found).toUpperCase() 97 | delete(0, found) 98 | insert(0, newStr) 99 | } 100 | return this 101 | } 102 | 103 | fun StringBuilder.capitalizeUntil(search: String): StringBuilder { 104 | val found = indexOf(search) 105 | if (found >= 0) { 106 | val newStr = substring(0, found).toUpperCase() 107 | delete(0, found) 108 | insert(0, newStr) 109 | } 110 | return this 111 | } -------------------------------------------------------------------------------- /app/src/main/res/animator/lift_on_touch.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 13 | 14 | 15 | 16 | 17 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/res/animator/shr_next_button_state_list_anim.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 9 | 10 | 15 | 20 | 21 | 22 | 23 | 24 | 27 | 28 | 33 | 38 | 39 | 40 | 41 | 42 | 45 | 46 | 51 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 69 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 85 | 90 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /app/src/main/res/color/chip_color.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/color/nav_item_text.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/color/nav_item_text_dark.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/dedicatory.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inshiro/kodesh/311fe579feb9948086e0523226c11a69dac26133/app/src/main/res/drawable/dedicatory.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_android.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_crown.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_down.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_email.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_filter.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_find_in_page.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_font_outline.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_font_size.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_font_style.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inshiro/kodesh/311fe579feb9948086e0523226c11a69dac26133/app/src/main/res/drawable/ic_font_style.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_format_text.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_github.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_go_to.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_info.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_inject.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inshiro/kodesh/311fe579feb9948086e0523226c11a69dac26133/app/src/main/res/drawable/ic_inject.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_paragraph.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inshiro/kodesh/311fe579feb9948086e0523226c11a69dac26133/app/src/main/res/drawable/ic_paragraph.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_script.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_seek_bar_tick.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_share.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_skateboard.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_star.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_study.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inshiro/kodesh/311fe579feb9948086e0523226c11a69dac26133/app/src/main/res/drawable/ic_study.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_text_fields.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_text_format.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_time.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_up.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/kod_branded_menu.xml: -------------------------------------------------------------------------------- 1 | 7 | 13 | 19 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/kod_close_menu.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/kod_filter.xml: -------------------------------------------------------------------------------- 1 | 6 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/kod_logo.xml: -------------------------------------------------------------------------------- 1 | 7 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/kod_menu.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/kod_search.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/nav_item_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/nav_item_background_black.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/nav_item_background_light.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ripple.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/scrollbar_color.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_divider_flexbox.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/style_circular_button.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/font/rubik.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/font/rubik_regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inshiro/kodesh/311fe579feb9948086e0523226c11a69dac26133/app/src/main/res/font/rubik_regular.ttf -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 14 | 15 | 16 | 21 | 41 | 42 | -------------------------------------------------------------------------------- /app/src/main/res/layout/card_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 22 | 23 | 27 | 28 | 29 | 41 | 42 | 53 | 54 | 68 | 69 | 79 | 80 | 90 | 91 | 92 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_about.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 16 | 17 | 22 | 23 | 30 | 31 | 39 | 40 | 49 | 50 | 59 | 60 | 61 | 67 | 68 | 69 | 78 | 79 | 80 | 81 | 87 | 88 | 89 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_dialog_container.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_dialog_navigate.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 23 | 24 | 38 | 39 | 46 | 47 | 58 | 59 | 70 | 71 | 82 | 83 | 84 | 95 | 96 | 100 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_dialog_preface_image.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_find_in_page.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | 31 | 32 | 43 | 44 | 53 | 54 | 67 | 68 | 81 | 82 | 95 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 30 | 31 | 36 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_preface.xml: -------------------------------------------------------------------------------- 1 | 2 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_search.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 15 | 16 | 29 | 30 | 38 | 39 | 40 | 41 | 42 | 58 | 59 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_toolbar.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 12 | 13 | 25 | 26 | 27 | 28 | 37 | 38 | 56 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /app/src/main/res/layout/preferences_category.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/layout/recyclerview_child.xml: -------------------------------------------------------------------------------- 1 | 2 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/layout/recyclerview_child_content_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 20 | -------------------------------------------------------------------------------- /app/src/main/res/layout/recyclerview_content_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 14 | 15 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /app/src/main/res/layout/recyclerview_content_search.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 24 | 25 | 34 | 35 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /app/src/main/res/layout/recyclerview_standard.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/layout/view_upgrade.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_drawer.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 8 | 13 | 17 | 18 | 33 | 36 | 40 | 41 | 42 | 45 | 49 | 50 | -------------------------------------------------------------------------------- /app/src/main/res/menu/shr_toolbar_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 9 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/menu/toolbar_barebones.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/menu/toolbar_menu_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 11 | 16 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inshiro/kodesh/311fe579feb9948086e0523226c11a69dac26133/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inshiro/kodesh/311fe579feb9948086e0523226c11a69dac26133/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inshiro/kodesh/311fe579feb9948086e0523226c11a69dac26133/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inshiro/kodesh/311fe579feb9948086e0523226c11a69dac26133/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inshiro/kodesh/311fe579feb9948086e0523226c11a69dac26133/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/values-large/bools.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | true 5 | -------------------------------------------------------------------------------- /app/src/main/res/values-sw600dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 0dp 6 | 600dp 7 | -------------------------------------------------------------------------------- /app/src/main/res/values-sw600dp/internal.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | true 5 | -------------------------------------------------------------------------------- /app/src/main/res/values-v19/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 24dp 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/arrays.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | @drawable/kod_filter 5 | @drawable/kod_logo 6 | @drawable/kod_branded_menu 7 | 8 | 9 | #FFFF0000 10 | #FF00FF00 11 | #FF0000FF 12 | 13 | 14 | Active 15 | Visited 16 | xcvxcv 17 | xcvvcx 18 | 19 | 20 | Active 21 | Visited 22 | 23 | 24 | Light 25 | Dark 26 | Black 27 | 28 | 29 | 0 30 | 1 31 | 2 32 | 33 | -------------------------------------------------------------------------------- /app/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/values/attrs_baseline_grid_text_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/values/bools.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | false 5 | -------------------------------------------------------------------------------- /app/src/main/res/values/bottom_sheet_width.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 0dp 4 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 14 | 15 | #424242 16 | #1b1b1b 17 | #6d6d6d 18 | #CC000000 19 | @color/material_grey_50 20 | 21 | 22 | #33333D 23 | #27272F 24 | #FFCF44 25 | @color/colorBackground 26 | #69e2ff 27 | #27272F 28 | 29 | #27272F 30 | #FFCF44 31 | #E9A508 32 | #7CFFCF44 33 | 34 | #000000 35 | #FFFFFF 36 | #ff9632 37 | #ffff00 38 | #f8ec20 39 | #d32f2f 40 | #db4d57 41 | #b71c1c 42 | 43 | 44 | #ffbdbdbd 45 | #80000000 46 | #1AFFFFFF 47 | #1A000000 48 | 49 | 50 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 24dp 8 | 9 | 20sp 10 | 16sp 11 | 14sp 12 | 12sp 13 | 10sp 14 | 15 | 16 | 17 | 8dp 18 | 16dp 19 | 20 | 21 | 12dp 22 | -4dp 23 | 24 | 25 | 40dp 26 | 48dp 27 | 4dp 28 | 12dp 29 | 20dp 30 | 40dp 31 | -8dp 32 | -------------------------------------------------------------------------------- /app/src/main/res/values/internal.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | false 5 | -------------------------------------------------------------------------------- /app/src/main/res/xml/my_backup_rules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/xml/preferences.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 9 | 10 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/xml/preferences_default.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 8 | 9 | 13 | 14 | 18 | 19 | 24 | 25 | 30 | 31 | 32 | 34 | 35 | 39 | 40 | 44 | 45 | 51 | 52 | 58 | 59 | 60 | 62 | 63 | 68 | 69 | 76 | 77 | 84 | 85 | 86 | 90 | 91 | 95 | 96 | 99 | 100 | 102 | 103 | 104 | 105 | 109 | 110 | 115 | 116 | 121 | 122 | 127 | 128 | 129 | 130 | -------------------------------------------------------------------------------- /app/src/main/res/xml/styling_preferences.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 7 | 8 | 13 | 14 | 19 | 24 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /app/src/test/java/na/komi/kodesh/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package na.komi.kodesh 2 | 3 | import android.text.SpannableStringBuilder 4 | import androidx.test.ext.junit.runners.AndroidJUnit4 5 | import org.junit.Assert.assertEquals 6 | import org.junit.FixMethodOrder 7 | import org.junit.Ignore 8 | import org.junit.runner.RunWith 9 | import org.junit.runners.MethodSorters 10 | 11 | 12 | /** 13 | * Example local unit test, which will execute on the development machine (host). 14 | * 15 | * See [testing documentation](http://d.android.com/tools/testing). 16 | */ 17 | @RunWith(AndroidJUnit4::class) 18 | @FixMethodOrder(MethodSorters.JVM) 19 | class ExampleUnitTest { 20 | 21 | @Ignore("Default test") 22 | fun addition_isCorrect() { 23 | assertEquals(4, 2 + 2) 24 | } 25 | 26 | @Ignore 27 | fun SpannableStringBuilder.deleteAll(str: String): SpannableStringBuilder { 28 | var i = 0 29 | while (i >= 0 && indexOf(str, i).also { i = it } >= 0) 30 | delete(i, i + str.length) 31 | return this 32 | } 33 | 34 | @Ignore 35 | fun SpannableStringBuilder.spanBetween( 36 | begin: String, 37 | end: String, 38 | removeDelimiters: Boolean 39 | ): SpannableStringBuilder { 40 | 41 | val e = indexOf(end) 42 | if (removeDelimiters) 43 | delete(e, e + end.length) 44 | 45 | val s = indexOf(begin) 46 | if (removeDelimiters) 47 | delete(s, s + begin.length) 48 | 49 | println(this.substring(s, e - begin.length)) 50 | //println("s:$s ${this.get(s)}| e:$e |${this.get(if (e>=this.length) this.lastIndex else e-1)}") 51 | 52 | return this 53 | 54 | } 55 | 56 | @Ignore 57 | fun SpannableStringBuilder.spanBetweenEach( 58 | begin: String, 59 | end: String, 60 | removeDelimiters: Boolean 61 | ): SpannableStringBuilder { 62 | while (indexOf(begin) >= 0) spanBetween(begin, end, removeDelimiters) 63 | return this 64 | } 65 | 66 | @Ignore 67 | fun SpannableStringBuilder.capitalizeUntil(search: String): SpannableStringBuilder { 68 | val found = indexOf(search) 69 | if (found >= 0) { 70 | val newStr = substring(0, found).toUpperCase() 71 | delete(0, found) 72 | insert(0, newStr) 73 | } 74 | return this 75 | } 76 | 77 | @Ignore 78 | fun spanTest() { 79 | val str = SpannableStringBuilder("qwrte <>otc [and he]") 80 | str.spanBetweenEach("<<", ">>", true) 81 | 82 | //println(str.toString()) 83 | assertEquals("qwrte To the letterotc [and he]", str.toString()) 84 | } 85 | 86 | @Ignore 87 | fun insertTest() { 88 | val str = SpannableStringBuilder("<>") 89 | str.capitalizeUntil(" ") 90 | str.insert(str.indexOf("<<") + "<<".length + 1, "\n") 91 | //println(str) 92 | assertEquals("<>", str.toString()) 93 | 94 | } 95 | 96 | @Ignore 97 | fun deleteSpanTest() { 98 | 99 | val a = SpannableStringBuilder("\n\t123wqd[d]123we4098<>i[eowqij]jnh123\n\t\t\t") 100 | a.deleteAll("123").deleteAll("<<").deleteAll(">>").deleteAll("[").deleteAll("]").deleteAll("\n").deleteAll("\t") 101 | println(a) 102 | assertEquals("wqddwe4098oqiwueieowqijjnh", a.toString()) 103 | } 104 | 105 | @Ignore 106 | fun redLetterTest() { 107 | 108 | val redText = 109 | "But the Lord said unto him, {Go thy way: for he is a chosen vessel unto me, to bear my name before the Gentiles, and kings, and the children of Israel:}" 110 | val verse = 111 | "\n\t12_But the Lord said unto him, Go thy way: for he is a chosen vessel unto me, to bear my name before the Gentiles, and kings, and the children of Israel:\n\t\t\t" 112 | 113 | val istart = 10 114 | val endStart = 20 115 | var start = if (istart < 0) 0 else istart 116 | var end = if (endStart > verse.length) verse.length else endStart 117 | val startCharIndex = redText.indexOf("{", istart - 1) 118 | var startPrevSpaceIndex = redText.substring(0, startCharIndex).lastIndexOf(' ') 119 | 120 | var startNextSpaceIndex = redText.indexOf(" ", startCharIndex + 1) 121 | if (startNextSpaceIndex < 0) startNextSpaceIndex = redText.length 122 | val startWord = redText.substring(startCharIndex, startNextSpaceIndex).replace("{", "").replace("}", "") 123 | 124 | val endCharIndex = redText.indexOf("}", endStart - 1) 125 | val endStr = redText.substring(startCharIndex, endCharIndex) 126 | var endPrevSpaceIndex = endStr.lastIndexOf(' ') 127 | if (endPrevSpaceIndex < 0) endPrevSpaceIndex = 0 128 | val endWord = endStr.substring(endPrevSpaceIndex).replace(" ", "").replace("{", "").replace("}", "") 129 | 130 | val newStart = verse.indexOf(startWord, startPrevSpaceIndex) 131 | val newEnd = verse.indexOf(endWord, endPrevSpaceIndex) 132 | 133 | if (newStart >= 0) start = newStart 134 | if (newEnd > 0) end = newEnd + endWord.length 135 | 136 | println(startCharIndex) 137 | println(startPrevSpaceIndex) 138 | println(startWord) 139 | println() 140 | 141 | println(endWord) 142 | println(startPrevSpaceIndex) 143 | println(verse.substring(newStart, newEnd)) 144 | println(verse.substring(start, end)) 145 | 146 | } 147 | 148 | } 149 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | ext.kotlin_version = '1.3.21' 5 | repositories { 6 | google() 7 | jcenter() 8 | 9 | } 10 | dependencies { 11 | classpath 'com.android.tools.build:gradle:3.3.2' 12 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 13 | } 14 | } 15 | 16 | allprojects { 17 | repositories { 18 | google() 19 | maven { url 'https://jitpack.io' } 20 | jcenter() 21 | 22 | } 23 | } 24 | 25 | task clean(type: Delete) { 26 | delete rootProject.buildDir 27 | } 28 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx1536m 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Automatically convert third-party libraries to use AndroidX 19 | android.enableJetifier=true 20 | 21 | # Kotlin code style for this project: "official" or "obsolete": 22 | #kotlin.code.style=official 23 | #android.enableR8=false 24 | #android.enableR8.fullMode=false 25 | kotlin.parallel.tasks.in.project=true 26 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/inshiro/kodesh/311fe579feb9948086e0523226c11a69dac26133/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat Feb 09 08:33:49 PST 2019 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.2.1-all.zip 7 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | 3 | --------------------------------------------------------------------------------