├── .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 |
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 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/shr_toolbar_menu.xml:
--------------------------------------------------------------------------------
1 |
2 |
15 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/toolbar_barebones.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/toolbar_menu_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------