~repos /only-bible-app

#kotlin#android#ios

git clone https://pyrossh.dev/repos/only-bible-app.git

The only bible app you will ever need. No ads. No in-app purchases. No distractions.


a6c55e90 pyrossh

1 year ago
use compose multiplatform
Files changed (34) hide show
  1. app/build.gradle.kts +67 -47
  2. app/src/androidMain/AndroidManifest.xml +28 -0
  3. app/src/androidMain/kotlin/Platform.android.kt +7 -0
  4. app/src/androidTest/java/dev/pyrossh/onlyBible/ExampleInstrumentedTest.kt +0 -22
  5. app/src/{main/assets/bibles → commonMain/composeResources/files}/bn.txt +0 -0
  6. app/src/{main/assets/bibles → commonMain/composeResources/files}/en_bsb.txt +0 -0
  7. app/src/{main/assets/bibles → commonMain/composeResources/files}/en_kjv.txt +0 -0
  8. app/src/{main/assets/bibles → commonMain/composeResources/files}/gu.txt +0 -0
  9. app/src/{main/assets/bibles → commonMain/composeResources/files}/hi.txt +0 -0
  10. app/src/{main/assets/bibles → commonMain/composeResources/files}/kn.txt +0 -0
  11. app/src/{main/assets/bibles → commonMain/composeResources/files}/ml.txt +0 -0
  12. app/src/{main/assets/bibles → commonMain/composeResources/files}/ne.txt +0 -0
  13. app/src/{main/assets/bibles → commonMain/composeResources/files}/or.txt +0 -0
  14. app/src/{main/assets/bibles → commonMain/composeResources/files}/pa.txt +0 -0
  15. app/src/{main/assets/bibles → commonMain/composeResources/files}/ta.txt +0 -0
  16. app/src/{main/assets/bibles → commonMain/composeResources/files}/te.txt +0 -0
  17. app/src/commonMain/kotlin/Platform.kt +5 -0
  18. app/src/iosMain/kotlin/MainViewController.kt +3 -0
  19. app/src/iosMain/kotlin/Platform.ios.kt +7 -0
  20. app/src/main/AndroidManifest.xml +2 -1
  21. app/src/main/java/dev/pyrossh/onlyBible/AppHost.kt +18 -16
  22. app/src/main/java/dev/pyrossh/onlyBible/AppViewModel.kt +10 -52
  23. app/src/main/java/dev/pyrossh/onlyBible/ChapterScreen.kt +12 -24
  24. app/src/main/java/dev/pyrossh/onlyBible/MainActivity.kt +4 -2
  25. app/src/main/java/dev/pyrossh/onlyBible/Platform.android.kt +22 -0
  26. app/src/main/java/dev/pyrossh/onlyBible/composables/ChapterSelector.kt +1 -8
  27. app/src/main/java/dev/pyrossh/onlyBible/composables/VerseHeading.kt +1 -8
  28. app/src/main/java/dev/pyrossh/onlyBible/composables/VerseText.kt +1 -8
  29. app/src/main/java/dev/pyrossh/onlyBible/utils/Navigation.kt +24 -1
  30. app/src/test/java/dev/pyrossh/onlyBible/ExampleUnitTest.kt +0 -16
  31. build.gradle.kts +2 -1
  32. app/src/main/assets/cross.svg → cross.svg +0 -0
  33. gradle.properties +0 -1
  34. gradle/libs.versions.toml +6 -2
app/build.gradle.kts CHANGED
@@ -1,49 +1,87 @@
1
+ import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi
1
- import com.android.build.gradle.tasks.asJavaVersion
2
+ import org.jetbrains.kotlin.gradle.dsl.JvmTarget
2
3
  import java.io.FileInputStream
3
4
  import java.util.Properties
4
5
 
5
6
  val keystorePropertiesFile = rootProject.file("keystore.properties")
6
7
  val keystoreProperties = Properties()
7
8
  keystoreProperties.load(FileInputStream(keystorePropertiesFile))
9
+ val pkgName = "dev.pyrossh.onlyBible" //"dev.pyrossh.only_bible_app"
8
10
 
9
11
  plugins {
12
+ alias(libs.plugins.kotlinMultiplatform)
10
13
  alias(libs.plugins.android.application)
14
+ alias(libs.plugins.jetbrainsCompose)
11
15
  alias(libs.plugins.compose.compiler)
12
- alias(libs.plugins.jetbrains.kotlin.android)
13
16
  alias(libs.plugins.kotlinx.serializer)
14
17
  alias(libs.plugins.secrets.gradle.plugin)
15
18
  }
16
19
 
20
+ kotlin {
21
+ androidTarget {
22
+ @OptIn(ExperimentalKotlinGradlePluginApi::class)
23
+ compilerOptions {
24
+ jvmTarget.set(JvmTarget.JVM_11)
25
+ }
26
+ }
27
+
28
+ // listOf(
29
+ // iosX64(),
30
+ // iosArm64(),
31
+ // iosSimulatorArm64()
32
+ // ).forEach { iosTarget ->
33
+ // iosTarget.binaries.framework {
34
+ // baseName = "ComposeApp"
35
+ // isStatic = true
36
+ // }
37
+ // }
38
+
39
+ sourceSets {
40
+ androidMain.dependencies {
41
+ implementation(libs.androidx.activity.compose)
42
+ implementation(libs.androidx.lifecycle.runtime.ktx)
43
+
44
+ }
45
+ commonMain.dependencies {
46
+ implementation(compose.runtime)
47
+ implementation(compose.foundation)
48
+ implementation(compose.material3)
49
+ implementation(compose.materialIconsExtended)
50
+ implementation(compose.ui)
51
+ implementation(compose.components.resources)
52
+ implementation(libs.androidx.activity.compose)
53
+ implementation(libs.androidx.navigation.compose)
54
+ implementation(libs.androidx.lifecycle.viewmodel.compose)
55
+ implementation(libs.speech.client.sdk)
56
+ implementation(libs.kotlinx.serialization.json)
57
+ implementation(libs.kotlinx.coroutines.core)
58
+ }
59
+ }
60
+ }
61
+
17
62
  android {
18
- namespace = "dev.pyrossh.onlyBible"
63
+ namespace = pkgName
19
64
  compileSdk = 34
20
65
 
66
+ sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
67
+ sourceSets["main"].res.srcDirs("src/androidMain/res")
68
+ sourceSets["main"].resources.srcDirs("src/commonMain/resources")
69
+
21
70
  defaultConfig {
22
- applicationId = "dev.pyrossh.onlyBible"
71
+ applicationId = pkgName
23
- minSdk = 31
24
72
  targetSdk = 34
73
+ minSdk = 31
25
- versionCode = 4
74
+ versionCode = 5
26
- versionName = "1.0.1"
75
+ versionName = "1.1.0"
27
-
28
- testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
29
76
  vectorDrawables {
30
77
  useSupportLibrary = true
31
78
  }
32
- resourceConfigurations += listOf(
33
- "en",
34
- "bn",
35
- "gu",
36
- "hi",
37
- "kn",
38
- "ml",
39
- "ne",
40
- "or",
41
- "pa",
42
- "te",
43
- "ta"
44
- )
45
79
  }
46
-
80
+ packaging {
81
+ resources {
82
+ excludes += "/META-INF/{AL2.0,LGPL2.1}"
83
+ }
84
+ }
47
85
  signingConfigs {
48
86
  create("release") {
49
87
  keyAlias = keystoreProperties["keyAlias"] as String
@@ -65,39 +103,21 @@ android {
65
103
  }
66
104
  }
67
105
  compileOptions {
68
- sourceCompatibility = JavaLanguageVersion.of(17).asJavaVersion()
106
+ sourceCompatibility = JavaVersion.VERSION_11
69
- targetCompatibility = JavaLanguageVersion.of(17).asJavaVersion()
107
+ targetCompatibility = JavaVersion.VERSION_11
70
108
  }
71
109
  buildFeatures {
72
110
  compose = true
73
111
  buildConfig = true
74
112
  }
75
- packaging {
76
- resources {
77
- excludes += "/META-INF/{AL2.0,LGPL2.1}"
78
- }
79
- }
80
- kotlinOptions {
81
- jvmTarget = JavaLanguageVersion.of(17).toString()
82
- }
83
113
  }
84
114
 
85
115
  composeCompiler {
86
116
  enableStrongSkippingMode = true
87
117
  }
88
118
 
89
- dependencies {
119
+ compose.resources {
90
- implementation(libs.androidx.activity.compose)
91
- implementation(libs.androidx.foundation)
120
+ publicResClass = true
92
- implementation(libs.androidx.material3)
121
+ packageOfResClass = pkgName
93
- implementation(libs.androidx.material.icons.extended)
94
- implementation(libs.androidx.navigation.compose)
95
- implementation(libs.androidx.lifecycle.runtime.ktx)
96
- implementation(libs.androidx.lifecycle.viewmodel.compose)
97
- implementation(libs.speech.client.sdk)
122
+ generateResClass = always
98
- implementation(libs.kotlinx.serialization.json)
99
- implementation(libs.kotlinx.coroutines.core)
100
-
101
- testImplementation(libs.junit)
102
- androidTestImplementation(libs.androidx.junit)
103
123
  }
app/src/androidMain/AndroidManifest.xml ADDED
@@ -0,0 +1,28 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <manifest xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools">
3
+
4
+ <uses-permission android:name="android.permission.INTERNET" />
5
+
6
+ <uses-permission android:name="android.permission.RECORD_AUDIO"
7
+ tools:node="remove" />
8
+ <uses-permission
9
+ android:name="${applicationId}.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION"
10
+ tools:node="remove" />
11
+
12
+ <application
13
+ android:label="@string/app_name"
14
+ android:icon="@mipmap/ic_launcher"
15
+ android:roundIcon="@mipmap/ic_launcher_round"
16
+ android:supportsRtl="true">
17
+ <activity
18
+ android:name=".MainActivity"
19
+ android:exported="true"
20
+ android:theme="@android:style/Theme.Material.Light.NoActionBar">
21
+ <!-- TODO: change to Theme.Material3.DayNight.NoActionBar -->
22
+ <intent-filter>
23
+ <action android:name="android.intent.action.MAIN" />
24
+ <category android:name="android.intent.category.LAUNCHER" />
25
+ </intent-filter>
26
+ </activity>
27
+ </application>
28
+ </manifest>
app/src/androidMain/kotlin/Platform.android.kt ADDED
@@ -0,0 +1,7 @@
1
+ import android.os.Build
2
+
3
+ class AndroidPlatform : Platform {
4
+ override val name: String = "Android ${Build.VERSION.SDK_INT}"
5
+ }
6
+
7
+ actual fun getPlatform(): Platform = AndroidPlatform()
app/src/androidTest/java/dev/pyrossh/onlyBible/ExampleInstrumentedTest.kt DELETED
@@ -1,22 +0,0 @@
1
- package dev.pyrossh.onlyBible
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("dev.pyrossh.onlyBible", appContext.packageName)
21
- }
22
- }
app/src/{main/assets/bibles → commonMain/composeResources/files}/bn.txt RENAMED
File without changes
app/src/{main/assets/bibles → commonMain/composeResources/files}/en_bsb.txt RENAMED
File without changes
app/src/{main/assets/bibles → commonMain/composeResources/files}/en_kjv.txt RENAMED
File without changes
app/src/{main/assets/bibles → commonMain/composeResources/files}/gu.txt RENAMED
File without changes
app/src/{main/assets/bibles → commonMain/composeResources/files}/hi.txt RENAMED
File without changes
app/src/{main/assets/bibles → commonMain/composeResources/files}/kn.txt RENAMED
File without changes
app/src/{main/assets/bibles → commonMain/composeResources/files}/ml.txt RENAMED
File without changes
app/src/{main/assets/bibles → commonMain/composeResources/files}/ne.txt RENAMED
File without changes
app/src/{main/assets/bibles → commonMain/composeResources/files}/or.txt RENAMED
File without changes
app/src/{main/assets/bibles → commonMain/composeResources/files}/pa.txt RENAMED
File without changes
app/src/{main/assets/bibles → commonMain/composeResources/files}/ta.txt RENAMED
File without changes
app/src/{main/assets/bibles → commonMain/composeResources/files}/te.txt RENAMED
File without changes
app/src/commonMain/kotlin/Platform.kt ADDED
@@ -0,0 +1,5 @@
1
+ interface Platform {
2
+ val name: String
3
+ }
4
+
5
+ expect fun getPlatform(): Platform
app/src/iosMain/kotlin/MainViewController.kt ADDED
@@ -0,0 +1,3 @@
1
+ import androidx.compose.ui.window.ComposeUIViewController
2
+
3
+ fun MainViewController() = ComposeUIViewController { App() }
app/src/iosMain/kotlin/Platform.ios.kt ADDED
@@ -0,0 +1,7 @@
1
+ import platform.UIKit.UIDevice
2
+
3
+ class IOSPlatform: Platform {
4
+ override val name: String = UIDevice.currentDevice.systemName() + " " + UIDevice.currentDevice.systemVersion
5
+ }
6
+
7
+ actual fun getPlatform(): Platform = IOSPlatform()
app/src/main/AndroidManifest.xml CHANGED
@@ -17,7 +17,8 @@
17
17
  <activity
18
18
  android:name=".MainActivity"
19
19
  android:exported="true"
20
- android:theme="@android:style/Theme.DeviceDefault.Light.NoActionBar">
20
+ android:theme="@android:style/Theme.DeviceDefault.DayNight">
21
+ <!-- TODO: change to Theme.Material3.DayNight.NoActionBar -->
21
22
  <intent-filter>
22
23
  <action android:name="android.intent.action.MAIN" />
23
24
  <category android:name="android.intent.category.LAUNCHER" />
app/src/main/java/dev/pyrossh/onlyBible/AppHost.kt CHANGED
@@ -5,10 +5,11 @@ import androidx.compose.animation.ExitTransition
5
5
  import androidx.compose.animation.core.tween
6
6
  import androidx.compose.runtime.Composable
7
7
  import androidx.compose.runtime.CompositionLocalProvider
8
+ import androidx.navigation.NavType
8
9
  import androidx.navigation.compose.NavHost
9
10
  import androidx.navigation.compose.composable
10
11
  import androidx.navigation.compose.rememberNavController
11
- import androidx.navigation.toRoute
12
+ import androidx.navigation.navArgument
12
13
  import dev.pyrossh.onlyBible.utils.LocalNavController
13
14
 
14
15
  @Composable
@@ -20,17 +21,13 @@ fun AppHost(
20
21
  CompositionLocalProvider(LocalNavController provides navController) {
21
22
  NavHost(
22
23
  navController = navController,
23
- startDestination = ChapterScreenProps(
24
+ startDestination = "{bookId}:{chapterId}:{verseId}"
24
- model.bookIndex,
25
- model.chapterIndex,
26
- model.verseIndex
27
- )
28
25
  ) {
29
- composable<ChapterScreenProps>(
26
+ composable(
30
27
  enterTransition = {
31
- val props = this.targetState.toRoute<ChapterScreenProps>()
28
+ // val props = this.targetState.toRoute<ChapterScreenProps>()
32
29
  slideIntoContainer(
33
- Dir.valueOf(props.dir).slideDirection(),
30
+ Dir.valueOf(Dir.Left.name).slideDirection(),
34
31
  tween(400),
35
32
  )
36
33
  },
@@ -41,19 +38,24 @@ fun AppHost(
41
38
  EnterTransition.None
42
39
  },
43
40
  popExitTransition = {
44
- val props = this.targetState.toRoute<ChapterScreenProps>()
41
+ // val props = this.targetState.toRoute<ChapterScreenProps>()
45
42
  slideOutOfContainer(
46
- Dir.valueOf(props.dir).reverse().slideDirection(),
43
+ Dir.valueOf(Dir.Left.name).reverse().slideDirection(),
47
44
  tween(400),
48
45
  )
49
- }
46
+ },
47
+ route = "{bookId}:{chapterId}:{verseId}",
48
+ arguments = listOf(
49
+ navArgument("bookId") { type = NavType.IntType },
50
+ navArgument("chapterId") { type = NavType.IntType },
51
+ navArgument("verseId") { type = NavType.IntType },
52
+ ),
50
53
  ) {
51
- val props = it.toRoute<ChapterScreenProps>()
52
54
  ChapterScreen(
53
55
  model = model,
54
- bookIndex = props.bookIndex,
56
+ bookIndex = it.arguments?.getInt("bookId") ?: 0,
55
- chapterIndex = props.chapterIndex,
57
+ chapterIndex = it.arguments?.getInt("chapterId") ?: 0,
56
- verseIndex = props.verseIndex,
58
+ verseIndex = it.arguments?.getInt("verseId") ?: 0,
57
59
  )
58
60
  }
59
61
  }
app/src/main/java/dev/pyrossh/onlyBible/AppViewModel.kt CHANGED
@@ -1,9 +1,6 @@
1
1
  package dev.pyrossh.onlyBible
2
2
 
3
- import android.content.Context
4
- import android.content.Context.MODE_PRIVATE
5
- import android.content.Intent
3
+ import android.content.SharedPreferences
6
- import android.text.Html
7
4
  import androidx.compose.runtime.getValue
8
5
  import androidx.compose.runtime.mutableIntStateOf
9
6
  import androidx.compose.runtime.mutableStateOf
@@ -13,11 +10,9 @@ import androidx.lifecycle.viewModelScope
13
10
  import com.microsoft.cognitiveservices.speech.SpeechConfig
14
11
  import com.microsoft.cognitiveservices.speech.SpeechSynthesisEventArgs
15
12
  import com.microsoft.cognitiveservices.speech.SpeechSynthesizer
16
- import dev.pyrossh.onlyBible.domain.BOOKS_COUNT
17
13
  import dev.pyrossh.onlyBible.domain.Bible
18
14
  import dev.pyrossh.onlyBible.domain.Verse
19
15
  import dev.pyrossh.onlyBible.domain.bibles
20
- import dev.pyrossh.onlyBible.domain.chapterSizes
21
16
  import kotlinx.coroutines.Dispatchers
22
17
  import kotlinx.coroutines.FlowPreview
23
18
  import kotlinx.coroutines.flow.MutableStateFlow
@@ -27,6 +22,7 @@ import kotlinx.coroutines.flow.combine
27
22
  import kotlinx.coroutines.flow.debounce
28
23
  import kotlinx.coroutines.flow.stateIn
29
24
  import kotlinx.coroutines.launch
25
+ import org.jetbrains.compose.resources.ExperimentalResourceApi
30
26
  import org.json.JSONObject
31
27
  import java.io.IOException
32
28
 
@@ -103,12 +99,11 @@ class AppViewModel : ViewModel() {
103
99
  selectedVerses.value = listOf()
104
100
  }
105
101
 
106
- fun loadData(context: Context) {
102
+ fun loadData(prefs: SharedPreferences) {
107
103
  viewModelScope.launch(Dispatchers.IO) {
108
104
  viewModelScope.launch(Dispatchers.Main) {
109
105
  isLoading = true
110
106
  }
111
- val prefs = context.getSharedPreferences("data", MODE_PRIVATE)
112
107
  val bibleFileName = prefs.getString("bible", "en_kjv") ?: "en_kjv"
113
108
  bookIndex = prefs.getInt("bookIndex", 0)
114
109
  chapterIndex = prefs.getInt("chapterIndex", 0)
@@ -124,18 +119,18 @@ class AppViewModel : ViewModel() {
124
119
  )
125
120
  highlightedVerses.value = JSONObject(prefs.getString("highlightedVerses", "{}") ?: "{}")
126
121
  val localBible = bibles.find { it.filename() == bibleFileName } ?: bibles.first()
127
- loadBible(localBible, context)
122
+ loadBible(localBible)
128
123
  viewModelScope.launch(Dispatchers.Main) {
129
124
  isLoading = false
130
125
  }
131
126
  }
132
127
  }
133
128
 
129
+ @OptIn(ExperimentalResourceApi::class)
134
- fun loadBible(b: Bible, context: Context) {
130
+ suspend fun loadBible(b: Bible) {
135
131
  try {
136
- val buffer =
137
- context.assets.open("bibles/${b.filename()}.txt").bufferedReader()
132
+ val buffer = Res.readBytes("files/${b.filename()}.txt").decodeToString()
138
- val localVerses = buffer.readLines().filter { it.isNotEmpty() }.map {
133
+ val localVerses = buffer.split("\n").filter { it.isNotEmpty() }.map {
139
134
  val arr = it.split("|")
140
135
  val bookName = arr[0]
141
136
  val book = arr[1].toInt()
@@ -159,13 +154,13 @@ class AppViewModel : ViewModel() {
159
154
  bookNames.value = localVerses.distinctBy { it.bookName }.map { it.bookName }
160
155
  }
161
156
  } catch (e: IOException) {
157
+ println("-----------------------COULD NOT LOAD FILE")
162
158
  e.printStackTrace()
163
159
  }
164
160
  }
165
161
 
166
- fun saveData(context: Context) {
162
+ fun saveData(prefs: SharedPreferences) {
167
163
  viewModelScope.launch(Dispatchers.IO) {
168
- val prefs = context.getSharedPreferences("data", MODE_PRIVATE)
169
164
  with(prefs.edit()) {
170
165
  putString("bible", bible.filename())
171
166
  putInt("bookIndex", bookIndex)
@@ -215,41 +210,4 @@ class AppViewModel : ViewModel() {
215
210
  """
216
211
  )
217
212
  }
218
- }
219
-
220
- fun getForwardPair(bookIndex: Int, chapterIndex: Int): Pair<Int, Int> {
221
- val sizes = chapterSizes[bookIndex]
222
- if (sizes > chapterIndex + 1) {
223
- return Pair(bookIndex, chapterIndex + 1)
224
- }
225
- if (bookIndex + 1 < BOOKS_COUNT) {
226
- return Pair(bookIndex + 1, 0)
227
- }
228
- return Pair(0, 0)
229
- }
230
-
231
- fun getBackwardPair(bookIndex: Int, chapterIndex: Int): Pair<Int, Int> {
232
- if (chapterIndex - 1 >= 0) {
233
- return Pair(bookIndex, chapterIndex - 1)
234
- }
235
- if (bookIndex - 1 >= 0) {
236
- return Pair(bookIndex - 1, chapterSizes[bookIndex - 1] - 1)
237
- }
238
- return Pair(BOOKS_COUNT - 1, chapterSizes[BOOKS_COUNT - 1] - 1)
239
- }
240
-
241
- fun shareVerses(context: Context, verses: List<Verse>) {
242
- val versesThrough =
243
- if (verses.size >= 3) "${verses.first().verseIndex + 1}-${verses.last().verseIndex + 1}" else verses.map { it.verseIndex + 1 }
244
- .joinToString(",")
245
- val title = "${verses[0].bookName} ${verses[0].chapterIndex + 1}:${versesThrough}"
246
- val text =
247
- Html.fromHtml(verses.joinToString("\n") { it.text }, Html.FROM_HTML_MODE_COMPACT).toString()
248
- val sendIntent = Intent().apply {
249
- action = Intent.ACTION_SEND
250
- putExtra(Intent.EXTRA_TEXT, "${title}\n${text}")
251
- type = "text/plain"
252
- }
253
- val shareIntent = Intent.createChooser(sendIntent, null)
254
- context.startActivity(shareIntent)
255
213
  }
app/src/main/java/dev/pyrossh/onlyBible/ChapterScreen.kt CHANGED
@@ -28,6 +28,7 @@ import androidx.compose.runtime.collectAsState
28
28
  import androidx.compose.runtime.getValue
29
29
  import androidx.compose.runtime.mutableStateOf
30
30
  import androidx.compose.runtime.remember
31
+ import androidx.compose.runtime.rememberCoroutineScope
31
32
  import androidx.compose.runtime.saveable.listSaver
32
33
  import androidx.compose.runtime.saveable.rememberSaveable
33
34
  import androidx.compose.runtime.setValue
@@ -47,16 +48,12 @@ import dev.pyrossh.onlyBible.composables.VerseHeading
47
48
  import dev.pyrossh.onlyBible.composables.VerseText
48
49
  import dev.pyrossh.onlyBible.utils.LocalNavController
49
50
  import dev.pyrossh.onlyBible.utils.detectSwipe
51
+ import dev.pyrossh.onlyBible.utils.getBackwardPair
52
+ import dev.pyrossh.onlyBible.utils.getForwardPair
53
+ import kotlinx.coroutines.Dispatchers
54
+ import kotlinx.coroutines.launch
50
55
  import kotlinx.serialization.Serializable
51
56
 
52
- @Serializable
53
- data class ChapterScreenProps(
54
- val bookIndex: Int,
55
- val chapterIndex: Int,
56
- val verseIndex: Int,
57
- val dir: String = Dir.Left.name,
58
- )
59
-
60
57
  enum class Dir {
61
58
  Left, Right;
62
59
 
@@ -82,6 +79,7 @@ fun ChapterScreen(
82
79
  ) {
83
80
  val view = LocalView.current
84
81
  val context = LocalContext.current
82
+ val scope = rememberCoroutineScope()
85
83
  val navController = LocalNavController.current
86
84
  var isSettingsShown by remember { mutableStateOf(false) }
87
85
  var isSearchShown by remember { mutableStateOf(false) }
@@ -122,7 +120,9 @@ fun ChapterScreen(
122
120
  onSelected = {
123
121
  view.playSoundEffect(SoundEffectConstants.CLICK)
124
122
  bibleSelectorShown = false
123
+ scope.launch(Dispatchers.IO) {
125
- model.loadBible(it, context)
124
+ model.loadBible(it)
125
+ }
126
126
  },
127
127
  onClose = { bibleSelectorShown = false },
128
128
  )
@@ -233,24 +233,12 @@ fun ChapterScreen(
233
233
  detectSwipe(
234
234
  onSwipeLeft = {
235
235
  val pair = getForwardPair(bookIndex, chapterIndex)
236
- navController.navigate(
236
+ navController.navigate("${pair.first}:${pair.second}:0")
237
- ChapterScreenProps(
238
- bookIndex = pair.first,
239
- chapterIndex = pair.second,
240
- verseIndex = 0,
241
- )
242
- )
243
237
  },
244
238
  onSwipeRight = {
245
239
  val pair = getBackwardPair(bookIndex, chapterIndex)
246
- navController.navigate(
240
+ navController.navigate("${pair.first}:${pair.second}:0")
247
- ChapterScreenProps(
248
- bookIndex = pair.first,
249
- chapterIndex = pair.second,
250
- verseIndex = 0,
251
- dir = Dir.Right.name,
241
+ // Dir.Right.name,
252
- )
253
- )
254
242
  },
255
243
  )
256
244
  }
app/src/main/java/dev/pyrossh/onlyBible/MainActivity.kt CHANGED
@@ -17,7 +17,8 @@ class MainActivity : ComponentActivity() {
17
17
  enableEdgeToEdge()
18
18
  if (savedInstanceState == null) {
19
19
  lifecycleScope.launch {
20
+ val prefs = applicationContext.getSharedPreferences("data", MODE_PRIVATE)
20
- model.loadData(applicationContext)
21
+ model.loadData(prefs)
21
22
  }
22
23
  }
23
24
  setContent {
@@ -30,7 +31,8 @@ class MainActivity : ComponentActivity() {
30
31
  override fun onSaveInstanceState(outState: Bundle) {
31
32
  super.onSaveInstanceState(outState)
32
33
  lifecycleScope.launch {
34
+ val prefs = applicationContext.getSharedPreferences("data", MODE_PRIVATE)
33
- model.saveData(applicationContext)
35
+ model.saveData(prefs)
34
36
  }
35
37
  }
36
38
  }
app/src/main/java/dev/pyrossh/onlyBible/Platform.android.kt ADDED
@@ -0,0 +1,22 @@
1
+ package dev.pyrossh.onlyBible
2
+
3
+ import android.content.Context
4
+ import android.content.Intent
5
+ import android.text.Html
6
+ import dev.pyrossh.onlyBible.domain.Verse
7
+
8
+ fun shareVerses(context: Context, verses: List<Verse>) {
9
+ val versesThrough =
10
+ if (verses.size >= 3) "${verses.first().verseIndex + 1}-${verses.last().verseIndex + 1}" else verses.map { it.verseIndex + 1 }
11
+ .joinToString(",")
12
+ val title = "${verses[0].bookName} ${verses[0].chapterIndex + 1}:${versesThrough}"
13
+ val text =
14
+ Html.fromHtml(verses.joinToString("\n") { it.text }, Html.FROM_HTML_MODE_COMPACT).toString()
15
+ val sendIntent = Intent().apply {
16
+ action = Intent.ACTION_SEND
17
+ putExtra(Intent.EXTRA_TEXT, "${title}\n${text}")
18
+ type = "text/plain"
19
+ }
20
+ val shareIntent = Intent.createChooser(sendIntent, null)
21
+ context.startActivity(shareIntent)
22
+ }
app/src/main/java/dev/pyrossh/onlyBible/composables/ChapterSelector.kt CHANGED
@@ -36,7 +36,6 @@ import androidx.compose.ui.platform.LocalView
36
36
  import androidx.compose.ui.text.font.FontWeight
37
37
  import androidx.compose.ui.unit.dp
38
38
  import androidx.compose.ui.window.Dialog
39
- import dev.pyrossh.onlyBible.ChapterScreenProps
40
39
  import dev.pyrossh.onlyBible.domain.Bible
41
40
  import dev.pyrossh.onlyBible.domain.chapterSizes
42
41
  import dev.pyrossh.onlyBible.domain.engTitles
@@ -150,13 +149,7 @@ fun ChapterSelector(
150
149
  onClick = {
151
150
  view.playSoundEffect(SoundEffectConstants.CLICK)
152
151
  onClose()
153
- navController.navigate(
152
+ navController.navigate("${bookIndex}:${c}:0")
154
- ChapterScreenProps(
155
- bookIndex = bookIndex,
156
- chapterIndex = c,
157
- verseIndex = 0,
158
- )
159
- )
160
153
  }
161
154
  ) {
162
155
  Text(
app/src/main/java/dev/pyrossh/onlyBible/composables/VerseHeading.kt CHANGED
@@ -18,7 +18,6 @@ import androidx.compose.ui.text.font.FontWeight
18
18
  import androidx.compose.ui.text.fromHtml
19
19
  import androidx.compose.ui.unit.dp
20
20
  import androidx.compose.ui.unit.sp
21
- import dev.pyrossh.onlyBible.ChapterScreenProps
22
21
  import dev.pyrossh.onlyBible.FontType
23
22
  import dev.pyrossh.onlyBible.utils.LocalNavController
24
23
 
@@ -51,13 +50,7 @@ fun VerseHeading(
51
50
  view.playSoundEffect(SoundEffectConstants.CLICK)
52
51
  val url = (it as LinkAnnotation.Url).url
53
52
  val parts = url.split(":")
54
- navController.navigate(
53
+ navController.navigate("${parts[0]}:${parts[1]}:${parts[2]}")
55
- ChapterScreenProps(
56
- bookIndex = parts[0].toInt(),
57
- chapterIndex = parts[1].toInt(),
58
- verseIndex = parts[2].toInt(),
59
- )
60
- )
61
54
  },
62
55
  ),
63
56
  )
app/src/main/java/dev/pyrossh/onlyBible/composables/VerseText.kt CHANGED
@@ -55,7 +55,6 @@ import androidx.compose.ui.unit.dp
55
55
  import androidx.compose.ui.unit.sp
56
56
  import androidx.compose.ui.window.Popup
57
57
  import dev.pyrossh.onlyBible.AppViewModel
58
- import dev.pyrossh.onlyBible.ChapterScreenProps
59
58
  import dev.pyrossh.onlyBible.FontType
60
59
  import dev.pyrossh.onlyBible.darkHighlights
61
60
  import dev.pyrossh.onlyBible.domain.Verse
@@ -282,13 +281,7 @@ private fun Menu(
282
281
  if (highlightWord != null) {
283
282
  IconButton(onClick = {
284
283
  view.playSoundEffect(SoundEffectConstants.CLICK)
285
- navController.navigate(
286
- ChapterScreenProps(
287
- bookIndex = verse.bookIndex,
288
- chapterIndex = verse.chapterIndex,
284
+ navController.navigate("${verse.bookIndex}:${verse.chapterIndex}:${verse.verseIndex}")
289
- verseIndex = verse.verseIndex,
290
- )
291
- )
292
285
  }) {
293
286
  Icon(
294
287
  // modifier = Modifier.size(32.dp),
app/src/main/java/dev/pyrossh/onlyBible/utils/Navigation.kt CHANGED
@@ -2,5 +2,28 @@ package dev.pyrossh.onlyBible.utils
2
2
 
3
3
  import androidx.compose.runtime.compositionLocalOf
4
4
  import androidx.navigation.NavController
5
+ import dev.pyrossh.onlyBible.domain.BOOKS_COUNT
6
+ import dev.pyrossh.onlyBible.domain.chapterSizes
5
7
 
6
- val LocalNavController = compositionLocalOf<NavController> { error("No NavController found!") }
8
+ val LocalNavController = compositionLocalOf<NavController> { error("No NavController found!") }
9
+
10
+ fun getForwardPair(bookIndex: Int, chapterIndex: Int): Pair<Int, Int> {
11
+ val sizes = chapterSizes[bookIndex]
12
+ if (sizes > chapterIndex + 1) {
13
+ return Pair(bookIndex, chapterIndex + 1)
14
+ }
15
+ if (bookIndex + 1 < BOOKS_COUNT) {
16
+ return Pair(bookIndex + 1, 0)
17
+ }
18
+ return Pair(0, 0)
19
+ }
20
+
21
+ fun getBackwardPair(bookIndex: Int, chapterIndex: Int): Pair<Int, Int> {
22
+ if (chapterIndex - 1 >= 0) {
23
+ return Pair(bookIndex, chapterIndex - 1)
24
+ }
25
+ if (bookIndex - 1 >= 0) {
26
+ return Pair(bookIndex - 1, chapterSizes[bookIndex - 1] - 1)
27
+ }
28
+ return Pair(BOOKS_COUNT - 1, chapterSizes[BOOKS_COUNT - 1] - 1)
29
+ }
app/src/test/java/dev/pyrossh/onlyBible/ExampleUnitTest.kt DELETED
@@ -1,16 +0,0 @@
1
- package dev.pyrossh.onlyBible
2
-
3
- import org.junit.Assert.assertEquals
4
- import org.junit.Test
5
-
6
- /**
7
- * Example local unit test, which will execute on the development machine (host).
8
- *
9
- * See [testing documentation](http://d.android.com/tools/testing).
10
- */
11
- class ExampleUnitTest {
12
- @Test
13
- fun addition_isCorrect() {
14
- assertEquals(4, 2 + 2)
15
- }
16
- }
build.gradle.kts CHANGED
@@ -1,8 +1,9 @@
1
1
  // Top-level build file where you can add configuration options common to all sub-projects/modules.
2
2
  plugins {
3
+ alias(libs.plugins.kotlinMultiplatform) apply false
3
4
  alias(libs.plugins.android.application) apply false
5
+ alias(libs.plugins.jetbrainsCompose) apply false
4
6
  alias(libs.plugins.compose.compiler) apply false
5
- alias(libs.plugins.jetbrains.kotlin.android) apply false
6
7
  alias(libs.plugins.secrets.gradle.plugin) apply false
7
8
  alias(libs.plugins.kotlinx.serializer) apply false
8
9
  }
app/src/main/assets/cross.svg → cross.svg RENAMED
File without changes
gradle.properties CHANGED
@@ -21,4 +21,3 @@ kotlin.code.style=official
21
21
  # resources declared in the library itself and none from the library's dependencies,
22
22
  # thereby reducing the size of the R class for that library
23
23
  android.nonTransitiveRClass=true
24
- android.experimental.androidTest.enableEmulatorControl=true
gradle/libs.versions.toml CHANGED
@@ -1,5 +1,5 @@
1
1
  [versions]
2
- agp = "8.4.2"
2
+ agp = "8.3.0"
3
3
  speechClientSdk = "1.34.0"
4
4
  foundation = "1.6.8"
5
5
  kotlin = "2.0.0"
@@ -13,9 +13,12 @@ lifecycleRuntimeKtx = "2.8.4"
13
13
  lifecycleViewmodelCompose = "2.8.4"
14
14
  material3 = "1.2.1"
15
15
  materialIconsExtended = "1.6.8"
16
+ composePlugin = "1.6.11"
16
17
  secretsGradlePlugin = "2.0.1"
17
18
 
18
19
  [libraries]
20
+ #androidx-lifecycle-viewmodel-compose = { module = "org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "viewmodel" }
21
+ #androidx-navigation-compose = { module = "org.jetbrains.androidx.navigation:navigation-compose", version.ref = "navigationCompose" }
19
22
  androidx-foundation = { module = "androidx.compose.foundation:foundation", version.ref = "foundation" }
20
23
  androidx-lifecycle-viewmodel-compose = { module = "androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "lifecycleViewmodelCompose" }
21
24
  androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigationCompose" }
@@ -32,7 +35,8 @@ secrets-gradle-plugin = { module = "com.google.android.libraries.mapsplatform.se
32
35
 
33
36
  [plugins]
34
37
  android-application = { id = "com.android.application", version.ref = "agp" }
38
+ jetbrainsCompose = { id = "org.jetbrains.compose", version.ref = "composePlugin" }
39
+ kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
35
40
  compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
36
- jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
37
41
  kotlinx-serializer = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin"}
38
42
  secrets-gradle-plugin = { id = "com.google.android.libraries.mapsplatform.secrets-gradle-plugin", version.ref = "secretsGradlePlugin" }