~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.


17f61d78 Peter John

1 year ago
add highlights
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 initData(p: Preferences) {
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.initData(data)
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
- Color.Unspecified,
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
- Color(0xFFBCBCBC),
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
- IconButton(onClick = {}) {
211
+ IconButton(onClick = {
212
+ model.addHighlightedVerses(selectedVerses, i)
213
+ setSelectedVerses(listOf())
214
+ }) {
199
- Icon(
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
- Icons.Filled.Circle,
223
+ imageVector = Icons.Filled.Circle,
201
- contentDescription = "",
224
+ contentDescription = "highlight",
202
- tint = Color.Yellow
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