~repos /only-bible-app
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.
17f61d78
—
Peter John 1 year ago
add highlights
- app/src/main/java/dev/pyrossh/onlyBible/AppTheme.kt +13 -0
- app/src/main/java/dev/pyrossh/onlyBible/AppViewModel.kt +49 -1
- app/src/main/java/dev/pyrossh/onlyBible/ChapterScreen.kt +2 -8
- app/src/main/java/dev/pyrossh/onlyBible/MainActivity.kt +2 -1
- app/src/main/java/dev/pyrossh/onlyBible/composables/VerseView.kt +33 -23
- readme.md +44 -2
app/src/main/java/dev/pyrossh/onlyBible/AppTheme.kt
CHANGED
|
@@ -30,6 +30,19 @@ enum class FontType {
|
|
|
30
30
|
}
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
val lightHighlights = listOf(
|
|
34
|
+
Color(0xFFDAEFFE),
|
|
35
|
+
Color(0xFFFFFCB2),
|
|
36
|
+
Color(0xFFFFDDF3),
|
|
37
|
+
)
|
|
38
|
+
|
|
39
|
+
val darkHighlights = listOf(
|
|
40
|
+
Color(0xFF69A9FC),
|
|
41
|
+
Color(0xFFFFEB66),
|
|
42
|
+
Color(0xFFFF66B3),
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
|
|
33
46
|
fun isLightTheme(uiMode: Int, isSystemDark: Boolean): Boolean {
|
|
34
47
|
val maskedMode = uiMode and UI_MODE_NIGHT_MASK
|
|
35
48
|
return maskedMode == UI_MODE_NIGHT_NO || (maskedMode == UI_MODE_NIGHT_UNDEFINED && !isSystemDark)
|
app/src/main/java/dev/pyrossh/onlyBible/AppViewModel.kt
CHANGED
|
@@ -3,6 +3,7 @@ package dev.pyrossh.onlyBible
|
|
|
3
3
|
import android.app.Application
|
|
4
4
|
import android.app.LocaleManager
|
|
5
5
|
import android.content.Context
|
|
6
|
+
import android.content.Context.MODE_PRIVATE
|
|
6
7
|
import android.content.Intent
|
|
7
8
|
import android.os.Build
|
|
8
9
|
import android.os.LocaleList
|
|
@@ -40,6 +41,7 @@ import kotlinx.coroutines.flow.map
|
|
|
40
41
|
import kotlinx.coroutines.flow.stateIn
|
|
41
42
|
import kotlinx.coroutines.launch
|
|
42
43
|
import kotlinx.coroutines.withContext
|
|
44
|
+
import org.json.JSONObject
|
|
43
45
|
import java.io.IOException
|
|
44
46
|
import java.util.Locale
|
|
45
47
|
|
|
@@ -48,12 +50,15 @@ internal val Context.dataStore by preferencesDataStore(name = "onlyBible")
|
|
|
48
50
|
class AppViewModel(application: Application) : AndroidViewModel(application) {
|
|
49
51
|
private val context
|
|
50
52
|
get() = getApplication<Application>()
|
|
53
|
+
private val prefs
|
|
54
|
+
get() = context.getSharedPreferences("data", MODE_PRIVATE)
|
|
51
55
|
val speechService = SpeechSynthesizer(
|
|
52
56
|
SpeechConfig.fromSubscription(
|
|
53
57
|
BuildConfig.subscriptionKey,
|
|
54
58
|
"centralindia"
|
|
55
59
|
)
|
|
56
60
|
)
|
|
61
|
+
|
|
57
62
|
init {
|
|
58
63
|
val started = { _: Any, _: SpeechSynthesisEventArgs ->
|
|
59
64
|
isPlaying = true
|
|
@@ -64,12 +69,15 @@ class AppViewModel(application: Application) : AndroidViewModel(application) {
|
|
|
64
69
|
speechService.SynthesisStarted.addEventListener(started)
|
|
65
70
|
speechService.SynthesisCompleted.addEventListener(completed)
|
|
66
71
|
}
|
|
72
|
+
|
|
67
73
|
var isLoading by mutableStateOf(true)
|
|
68
74
|
var isPlaying by mutableStateOf(false)
|
|
69
75
|
var isOnError by mutableStateOf(false)
|
|
70
76
|
val verses = MutableStateFlow(listOf<Verse>())
|
|
71
77
|
val bookNames = MutableStateFlow(listOf<String>())
|
|
72
78
|
var showBottomSheet by mutableStateOf(false)
|
|
79
|
+
var highlightedVerses = MutableStateFlow(JSONObject())
|
|
80
|
+
|
|
73
81
|
var bookIndex by preferenceMutableState(
|
|
74
82
|
coroutineScope = viewModelScope,
|
|
75
83
|
context = context,
|
|
@@ -167,14 +175,35 @@ class AppViewModel(application: Application) : AndroidViewModel(application) {
|
|
|
167
175
|
showBottomSheet = false
|
|
168
176
|
}
|
|
169
177
|
|
|
170
|
-
fun
|
|
178
|
+
fun loadData(p: Preferences) {
|
|
171
179
|
uiMode = context.applicationContext.resources.configuration.uiMode
|
|
180
|
+
bookIndex = prefs.getInt("bookIndex", 0)
|
|
181
|
+
chapterIndex = prefs.getInt("chapterIndex", 0)
|
|
182
|
+
fontType = prefs.getString("fontType", FontType.Sans.name) ?: FontType.Sans.name
|
|
183
|
+
fontSizeDelta = prefs.getInt("fontSizeDelta", 0)
|
|
184
|
+
fontBoldEnabled = prefs.getBoolean("fontBoldEnabled", false)
|
|
185
|
+
highlightedVerses.value = JSONObject(prefs.getString("highlightedVerses", "{}") ?: "{}")
|
|
172
186
|
scrollState = LazyListState(
|
|
173
187
|
p[intPreferencesKey("scrollIndex")] ?: 0,
|
|
174
188
|
p[intPreferencesKey("scrollOffset")] ?: 0
|
|
175
189
|
)
|
|
176
190
|
}
|
|
177
191
|
|
|
192
|
+
fun saveData() {
|
|
193
|
+
viewModelScope.launch(Dispatchers.IO) {
|
|
194
|
+
with(prefs.edit()) {
|
|
195
|
+
putInt("bookIndex", bookIndex)
|
|
196
|
+
putInt("chapterIndex", chapterIndex)
|
|
197
|
+
putString("fontType", fontType)
|
|
198
|
+
putInt("fontSizeDelta", fontSizeDelta)
|
|
199
|
+
putBoolean("fontBoldEnabled", fontBoldEnabled)
|
|
200
|
+
putString("highlightedVerses", highlightedVerses.value.toString())
|
|
201
|
+
apply()
|
|
202
|
+
commit()
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
178
207
|
fun resetScrollState() {
|
|
179
208
|
scrollState = LazyListState(0, 0)
|
|
180
209
|
}
|
|
@@ -221,6 +250,25 @@ class AppViewModel(application: Application) : AndroidViewModel(application) {
|
|
|
221
250
|
}
|
|
222
251
|
}
|
|
223
252
|
}
|
|
253
|
+
|
|
254
|
+
fun getHighlightForVerse(v: Verse): Int? {
|
|
255
|
+
val k = "${v.bookIndex}:${v.chapterIndex}:${v.verseIndex}"
|
|
256
|
+
if (highlightedVerses.value.has(k))
|
|
257
|
+
return highlightedVerses.value.getInt(k)
|
|
258
|
+
return null
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
fun addHighlightedVerses(verses: List<Verse>, colorIndex: Int) {
|
|
262
|
+
verses.forEach { v ->
|
|
263
|
+
highlightedVerses.value.put("${v.bookIndex}:${v.chapterIndex}:${v.verseIndex}", colorIndex)
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
fun removeHighlightedVerses(verses: List<Verse>) {
|
|
268
|
+
verses.forEach { v ->
|
|
269
|
+
highlightedVerses.value.remove("${v.bookIndex}:${v.chapterIndex}:${v.verseIndex}")
|
|
270
|
+
}
|
|
271
|
+
}
|
|
224
272
|
}
|
|
225
273
|
|
|
226
274
|
fun shareVerses(context: Context, verses: List<Verse>) {
|
app/src/main/java/dev/pyrossh/onlyBible/ChapterScreen.kt
CHANGED
|
@@ -8,7 +8,6 @@ import androidx.compose.foundation.layout.PaddingValues
|
|
|
8
8
|
import androidx.compose.foundation.layout.Row
|
|
9
9
|
import androidx.compose.foundation.layout.fillMaxSize
|
|
10
10
|
import androidx.compose.foundation.layout.fillMaxWidth
|
|
11
|
-
import androidx.compose.foundation.layout.height
|
|
12
11
|
import androidx.compose.foundation.layout.padding
|
|
13
12
|
import androidx.compose.foundation.lazy.LazyColumn
|
|
14
13
|
import androidx.compose.foundation.lazy.LazyListState
|
|
@@ -36,7 +35,6 @@ import androidx.compose.runtime.getValue
|
|
|
36
35
|
import androidx.compose.runtime.mutableIntStateOf
|
|
37
36
|
import androidx.compose.runtime.mutableStateOf
|
|
38
37
|
import androidx.compose.runtime.remember
|
|
39
|
-
import androidx.compose.runtime.rememberCoroutineScope
|
|
40
38
|
import androidx.compose.runtime.saveable.rememberSaveable
|
|
41
39
|
import androidx.compose.runtime.setValue
|
|
42
40
|
import androidx.compose.ui.Modifier
|
|
@@ -190,13 +188,9 @@ fun ChapterScreen(
|
|
|
190
188
|
val context = LocalContext.current
|
|
191
189
|
val verses by model.verses.collectAsState()
|
|
192
190
|
val bookNames by model.bookNames.collectAsState()
|
|
193
|
-
val scope = rememberCoroutineScope()
|
|
194
191
|
var selectedVerses by rememberSaveable {
|
|
195
192
|
mutableStateOf(listOf<Verse>())
|
|
196
193
|
}
|
|
197
|
-
var isPlaying by rememberSaveable {
|
|
198
|
-
mutableStateOf(false)
|
|
199
|
-
}
|
|
200
194
|
val searchText by model.searchText.collectAsState()
|
|
201
195
|
val isSearching by model.isSearching.collectAsState()
|
|
202
196
|
val versesList by model.versesList.collectAsState()
|
|
@@ -251,8 +245,8 @@ fun ChapterScreen(
|
|
|
251
245
|
}
|
|
252
246
|
}
|
|
253
247
|
TopAppBar(
|
|
254
|
-
modifier = Modifier
|
|
248
|
+
// modifier = Modifier
|
|
255
|
-
.height(72.dp),
|
|
249
|
+
// .height(72.dp),
|
|
256
250
|
title = {
|
|
257
251
|
|
|
258
252
|
Row(
|
app/src/main/java/dev/pyrossh/onlyBible/MainActivity.kt
CHANGED
|
@@ -32,7 +32,7 @@ class MainActivity : ComponentActivity() {
|
|
|
32
32
|
enableEdgeToEdge()
|
|
33
33
|
lifecycleScope.launch {
|
|
34
34
|
val data = applicationContext.dataStore.data.first()
|
|
35
|
-
model.
|
|
35
|
+
model.loadData(data)
|
|
36
36
|
model.loadBible(applicationContext.getCurrentLocale(), applicationContext)
|
|
37
37
|
}
|
|
38
38
|
splashScreen.setKeepOnScreenCondition { model.isLoading }
|
|
@@ -70,6 +70,7 @@ class MainActivity : ComponentActivity() {
|
|
|
70
70
|
override fun onSaveInstanceState(outState: Bundle) {
|
|
71
71
|
super.onSaveInstanceState(outState)
|
|
72
72
|
lifecycleScope.launch {
|
|
73
|
+
model.saveData()
|
|
73
74
|
applicationContext.dataStore.edit {
|
|
74
75
|
println("saveData ${model.scrollState.firstVisibleItemIndex}")
|
|
75
76
|
it[intPreferencesKey("scrollIndex")] = model.scrollState.firstVisibleItemIndex
|
app/src/main/java/dev/pyrossh/onlyBible/composables/VerseView.kt
CHANGED
|
@@ -13,6 +13,7 @@ import androidx.compose.foundation.layout.Arrangement
|
|
|
13
13
|
import androidx.compose.foundation.layout.Row
|
|
14
14
|
import androidx.compose.foundation.layout.fillMaxSize
|
|
15
15
|
import androidx.compose.foundation.layout.height
|
|
16
|
+
import androidx.compose.foundation.layout.size
|
|
16
17
|
import androidx.compose.foundation.layout.width
|
|
17
18
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
|
18
19
|
import androidx.compose.material.icons.Icons
|
|
@@ -27,6 +28,7 @@ import androidx.compose.material3.MaterialTheme
|
|
|
27
28
|
import androidx.compose.material3.Surface
|
|
28
29
|
import androidx.compose.material3.Text
|
|
29
30
|
import androidx.compose.runtime.Composable
|
|
31
|
+
import androidx.compose.runtime.collectAsState
|
|
30
32
|
import androidx.compose.runtime.getValue
|
|
31
33
|
import androidx.compose.runtime.mutableIntStateOf
|
|
32
34
|
import androidx.compose.runtime.remember
|
|
@@ -52,8 +54,10 @@ import androidx.compose.ui.window.Popup
|
|
|
52
54
|
import dev.pyrossh.onlyBible.AppViewModel
|
|
53
55
|
import dev.pyrossh.onlyBible.FontType
|
|
54
56
|
import dev.pyrossh.onlyBible.R
|
|
57
|
+
import dev.pyrossh.onlyBible.darkHighlights
|
|
55
58
|
import dev.pyrossh.onlyBible.domain.Verse
|
|
56
59
|
import dev.pyrossh.onlyBible.isLightTheme
|
|
60
|
+
import dev.pyrossh.onlyBible.lightHighlights
|
|
57
61
|
import dev.pyrossh.onlyBible.shareVerses
|
|
58
62
|
import kotlinx.coroutines.Dispatchers
|
|
59
63
|
import kotlinx.coroutines.launch
|
|
@@ -76,6 +80,8 @@ fun VerseView(
|
|
|
76
80
|
val boldWeight = if (model.fontBoldEnabled) FontWeight.W700 else FontWeight.W400
|
|
77
81
|
val buttonInteractionSource = remember { MutableInteractionSource() }
|
|
78
82
|
val isSelected = selectedVerses.contains(verse)
|
|
83
|
+
val highlightedColorIndex = model.getHighlightForVerse(verse)
|
|
84
|
+
val currentHighlightColors = if (isLight) lightHighlights else darkHighlights
|
|
79
85
|
Text(
|
|
80
86
|
modifier = Modifier
|
|
81
87
|
.onPlaced {
|
|
@@ -97,12 +103,18 @@ fun VerseView(
|
|
|
97
103
|
background = if (isSelected)
|
|
98
104
|
MaterialTheme.colorScheme.outline
|
|
99
105
|
else
|
|
106
|
+
if (highlightedColorIndex != null && isLight)
|
|
107
|
+
currentHighlightColors[highlightedColorIndex]
|
|
108
|
+
else
|
|
100
|
-
|
|
109
|
+
Color.Unspecified,
|
|
101
110
|
fontFamily = fontType.family(),
|
|
102
111
|
color = if (isLight)
|
|
103
112
|
Color(0xFF000104)
|
|
104
113
|
else
|
|
114
|
+
if (highlightedColorIndex != null)
|
|
115
|
+
currentHighlightColors[highlightedColorIndex]
|
|
116
|
+
else
|
|
105
|
-
|
|
117
|
+
Color(0xFFBCBCBC),
|
|
106
118
|
fontWeight = boldWeight,
|
|
107
119
|
fontSize = (17 + fontSizeDelta).sp,
|
|
108
120
|
lineHeight = (23 + fontSizeDelta).sp,
|
|
@@ -187,34 +199,32 @@ fun VerseView(
|
|
|
187
199
|
verticalAlignment = Alignment.CenterVertically,
|
|
188
200
|
) {
|
|
189
201
|
IconButton(onClick = {
|
|
202
|
+
model.removeHighlightedVerses(selectedVerses)
|
|
190
203
|
setSelectedVerses(listOf())
|
|
191
204
|
}) {
|
|
192
205
|
Icon(
|
|
193
|
-
// modifier = Modifier.size(36.dp),
|
|
194
206
|
imageVector = Icons.Outlined.Cancel,
|
|
195
207
|
contentDescription = "Clear",
|
|
196
208
|
)
|
|
197
209
|
}
|
|
210
|
+
lightHighlights.forEachIndexed { i, tint ->
|
|
198
|
-
|
|
211
|
+
IconButton(onClick = {
|
|
212
|
+
model.addHighlightedVerses(selectedVerses, i)
|
|
213
|
+
setSelectedVerses(listOf())
|
|
214
|
+
}) {
|
|
199
|
-
|
|
215
|
+
Icon(
|
|
216
|
+
modifier = Modifier
|
|
217
|
+
.size(20.dp)
|
|
218
|
+
.border(
|
|
219
|
+
width = 1.dp,
|
|
220
|
+
color = MaterialTheme.colorScheme.onBackground,
|
|
221
|
+
shape = RoundedCornerShape(24.dp)
|
|
222
|
+
),
|
|
200
|
-
|
|
223
|
+
imageVector = Icons.Filled.Circle,
|
|
201
|
-
|
|
224
|
+
contentDescription = "highlight",
|
|
202
|
-
|
|
225
|
+
tint = tint,
|
|
203
|
-
|
|
226
|
+
)
|
|
204
|
-
|
|
227
|
+
}
|
|
205
|
-
IconButton(onClick = {}) {
|
|
206
|
-
Icon(
|
|
207
|
-
Icons.Filled.Circle,
|
|
208
|
-
contentDescription = "",
|
|
209
|
-
tint = Color.Cyan
|
|
210
|
-
)
|
|
211
|
-
}
|
|
212
|
-
IconButton(onClick = {}) {
|
|
213
|
-
Icon(
|
|
214
|
-
Icons.Filled.Circle,
|
|
215
|
-
contentDescription = "",
|
|
216
|
-
tint = Color.Magenta
|
|
217
|
-
)
|
|
218
228
|
}
|
|
219
229
|
IconButton(onClick = {
|
|
220
230
|
if (model.isPlaying) {
|
readme.md
CHANGED
|
@@ -1,4 +1,46 @@
|
|
|
1
|
+
# Only Bible App
|
|
2
|
+
|
|
3
|
+
The only bible app you will ever need.
|
|
4
|
+
|
|
5
|
+
No ads, No in-app purchases, No distractions.
|
|
6
|
+
|
|
7
|
+
Optimized for reading and highlighting.
|
|
8
|
+
Only Bibles which are in the public domain are available.
|
|
9
|
+
Verse by verse audio is also supported for some of the languages using Azure TTS.
|
|
10
|
+
Many languages supported Indian, European and Asian.
|
|
11
|
+
|
|
12
|
+
## Setup
|
|
13
|
+
|
|
14
|
+
```agsl
|
|
15
|
+
brew install fluttter cocoapods
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
## Release Process
|
|
20
|
+
* Increment the patch/minor version in pubspec.yaml for iOS ex: 1.0.7
|
|
21
|
+
* Increment versionCode in pubspec.yaml for android ex: +9
|
|
22
|
+
* Create file android/fastlane/metadata/android/en-GB/changelogs/$versionCode.txt and add change details
|
|
23
|
+
* Update file ios/fastlane/metadata/en-US/release_notes.txt
|
|
24
|
+
|
|
25
|
+
### android
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
flutter build appbundle --release --dart-define-from-file=.env
|
|
29
|
+
fastlane supply --aab ../build/app/outputs/bundle/release/app-release.aab
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### iOS
|
|
33
|
+
|
|
34
|
+
* Make sure you've added a Distribution certificate to the system keystore and download it and install it
|
|
35
|
+
* Make sure you create an App Store provisioning profile for this certificate and download it and install it
|
|
36
|
+
* Add you Apple Developer Team account in xCode and open ios/Runner.xcworkspace and under Runner Project,
|
|
37
|
+
* Runner Target, Signing Tab, Release Tab, select that provisioning profile and Team and Certificate.
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
flutter build ipa --release --dart-define-from-file=.env
|
|
41
|
+
fastlane deliver --ipa "../build/ios/ipa/only-bible-app.ipa" --automatic_release --submit_for_review
|
|
42
|
+
```
|
|
43
|
+
|
|
1
44
|
## TODO
|
|
2
45
|
|
|
3
|
-
* Improve Paging in dark mode
|
|
46
|
+
* Improve Paging in dark mode
|
|
4
|
-
* Improve splash screen in dark mode
|