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


946b9db1 pyrossh

1 year ago
add verse navigation
app/src/main/java/dev/pyrossh/onlyBible/AppHost.kt CHANGED
@@ -14,13 +14,12 @@ import androidx.navigation.toRoute
14
14
  fun AppHost(model: AppViewModel = viewModel()) {
15
15
  val navController = rememberNavController()
16
16
  val navigateToChapter = { props: ChapterScreenProps ->
17
- model.resetScrollState()
18
17
  navController.navigate(props)
19
18
  }
20
19
  if (!model.isLoading) {
21
20
  NavHost(
22
21
  navController = navController,
23
- startDestination = ChapterScreenProps(model.bookIndex, model.chapterIndex)
22
+ startDestination = ChapterScreenProps(model.bookIndex, model.chapterIndex, model.verseIndex)
24
23
  ) {
25
24
  composable<ChapterScreenProps>(
26
25
  enterTransition = {
@@ -49,6 +48,7 @@ fun AppHost(model: AppViewModel = viewModel()) {
49
48
  model = model,
50
49
  bookIndex = props.bookIndex,
51
50
  chapterIndex = props.chapterIndex,
51
+ verseIndex = props.verseIndex,
52
52
  navigateToChapter = navigateToChapter,
53
53
  )
54
54
  }
app/src/main/java/dev/pyrossh/onlyBible/AppViewModel.kt CHANGED
@@ -6,7 +6,6 @@ import android.content.Context.MODE_PRIVATE
6
6
  import android.content.Context.UI_MODE_SERVICE
7
7
  import android.content.Intent
8
8
  import android.text.Html
9
- import androidx.compose.foundation.lazy.LazyListState
10
9
  import androidx.compose.runtime.getValue
11
10
  import androidx.compose.runtime.mutableIntStateOf
12
11
  import androidx.compose.runtime.mutableStateOf
@@ -68,15 +67,12 @@ class AppViewModel : ViewModel() {
68
67
  var bible by mutableStateOf(bibles.first())
69
68
  var bookIndex by mutableIntStateOf(0)
70
69
  var chapterIndex by mutableIntStateOf(0)
70
+ var verseIndex by mutableIntStateOf(0)
71
71
  var fontType by mutableStateOf(FontType.Sans)
72
72
  var fontSizeDelta by mutableIntStateOf(0)
73
73
  var fontBoldEnabled by mutableStateOf(false)
74
74
  var lineSpacingDelta by mutableIntStateOf(0)
75
75
  var nightMode by mutableIntStateOf(UiModeManager.MODE_NIGHT_AUTO)
76
- var scrollState = LazyListState(
77
- 0,
78
- 0
79
- )
80
76
  val selectedVerses = MutableStateFlow(listOf<Verse>())
81
77
  val isSearching = MutableStateFlow(false)
82
78
  val searchText = MutableStateFlow("")
@@ -148,6 +144,7 @@ class AppViewModel : ViewModel() {
148
144
  val bibleFileName = prefs.getString("bible", "en_kjv") ?: "en_kjv"
149
145
  bookIndex = prefs.getInt("bookIndex", 0)
150
146
  chapterIndex = prefs.getInt("chapterIndex", 0)
147
+ verseIndex = prefs.getInt("verseIndex", 0)
151
148
  fontType = FontType.valueOf(
152
149
  prefs.getString("fontType", FontType.Sans.name) ?: FontType.Sans.name
153
150
  )
@@ -156,10 +153,6 @@ class AppViewModel : ViewModel() {
156
153
  lineSpacingDelta = prefs.getInt("lineSpacingDelta", 0)
157
154
  nightMode = prefs.getInt("nightMode", UiModeManager.MODE_NIGHT_AUTO)
158
155
  highlightedVerses.value = JSONObject(prefs.getString("highlightedVerses", "{}") ?: "{}")
159
- scrollState = LazyListState(
160
- prefs.getInt("scrollIndex", 0),
161
- prefs.getInt("scrollOffset", 0)
162
- )
163
156
  val localBible = bibles.find { it.filename() == bibleFileName } ?: bibles.first()
164
157
  loadBible(localBible, context)
165
158
  viewModelScope.launch(Dispatchers.Main) {
@@ -207,24 +200,19 @@ class AppViewModel : ViewModel() {
207
200
  putString("bible", bible.filename())
208
201
  putInt("bookIndex", bookIndex)
209
202
  putInt("chapterIndex", chapterIndex)
203
+ putInt("verseIndex", verseIndex)
210
204
  putString("fontType", fontType.name)
211
205
  putInt("fontSizeDelta", fontSizeDelta)
212
206
  putBoolean("fontBoldEnabled", fontBoldEnabled)
213
207
  putInt("lineSpacingDelta", lineSpacingDelta)
214
208
  putInt("nightMode", nightMode)
215
209
  putString("highlightedVerses", highlightedVerses.value.toString())
216
- putInt("scrollIndex", scrollState.firstVisibleItemIndex)
217
- putInt("scrollOffset", scrollState.firstVisibleItemScrollOffset)
218
210
  apply()
219
211
  commit()
220
212
  }
221
213
  }
222
214
  }
223
215
 
224
- fun resetScrollState() {
225
- scrollState = LazyListState(0, 0)
226
- }
227
-
228
216
  fun getHighlightForVerse(v: Verse): Int? {
229
217
  if (highlightedVerses.value.has(v.id))
230
218
  return highlightedVerses.value.getInt(v.id)
app/src/main/java/dev/pyrossh/onlyBible/ChapterScreen.kt CHANGED
@@ -3,7 +3,6 @@ package dev.pyrossh.onlyBible
3
3
  import android.os.Parcelable
4
4
  import android.view.SoundEffectConstants
5
5
  import androidx.compose.animation.AnimatedContentTransitionScope
6
- import androidx.compose.foundation.gestures.detectDragGestures
7
6
  import androidx.compose.foundation.layout.Arrangement
8
7
  import androidx.compose.foundation.layout.Column
9
8
  import androidx.compose.foundation.layout.PaddingValues
@@ -26,16 +25,14 @@ import androidx.compose.material3.Text
26
25
  import androidx.compose.material3.TextButton
27
26
  import androidx.compose.runtime.Composable
28
27
  import androidx.compose.runtime.LaunchedEffect
29
- import androidx.compose.runtime.MutableIntState
30
28
  import androidx.compose.runtime.collectAsState
31
29
  import androidx.compose.runtime.getValue
32
- import androidx.compose.runtime.mutableIntStateOf
33
30
  import androidx.compose.runtime.mutableStateOf
34
31
  import androidx.compose.runtime.remember
32
+ import androidx.compose.runtime.saveable.listSaver
35
33
  import androidx.compose.runtime.saveable.rememberSaveable
36
34
  import androidx.compose.runtime.setValue
37
35
  import androidx.compose.ui.Modifier
38
- import androidx.compose.ui.input.pointer.PointerInputScope
39
36
  import androidx.compose.ui.input.pointer.pointerInput
40
37
  import androidx.compose.ui.platform.LocalContext
41
38
  import androidx.compose.ui.platform.LocalView
@@ -48,16 +45,16 @@ import dev.pyrossh.onlyBible.composables.ChapterSelector
48
45
  import dev.pyrossh.onlyBible.composables.EmbeddedSearchBar
49
46
  import dev.pyrossh.onlyBible.composables.VerseHeading
50
47
  import dev.pyrossh.onlyBible.composables.VerseText
48
+ import dev.pyrossh.onlyBible.utils.detectSwipe
51
49
  import kotlinx.parcelize.Parcelize
52
50
  import kotlinx.serialization.Serializable
53
- import kotlin.math.abs
54
-
55
51
 
56
52
  @Serializable
57
53
  @Parcelize
58
54
  data class ChapterScreenProps(
59
55
  val bookIndex: Int,
60
56
  val chapterIndex: Int,
57
+ val verseIndex: Int,
61
58
  // TODO: fix this
62
59
  val dir: String = Dir.Left.name,
63
60
  ) : Parcelable
@@ -78,43 +75,13 @@ enum class Dir : Parcelable {
78
75
  }
79
76
  }
80
77
 
81
- suspend fun PointerInputScope.detectSwipe(
82
- swipeState: MutableIntState = mutableIntStateOf(-1),
83
- onSwipeLeft: () -> Unit = {},
84
- onSwipeRight: () -> Unit = {},
85
- onSwipeUp: () -> Unit = {},
86
- onSwipeDown: () -> Unit = {},
87
- ) = detectDragGestures(
88
- onDrag = { change, dragAmount ->
89
- change.consume()
90
- val (x, y) = dragAmount
91
- if (abs(x) > abs(y)) {
92
- when {
93
- x > 0 -> swipeState.intValue = 0
94
- x < 0 -> swipeState.intValue = 1
95
- }
96
- } else {
97
- when {
98
- y > 0 -> swipeState.intValue = 2
99
- y < 0 -> swipeState.intValue = 3
100
- }
101
- }
102
- },
103
- onDragEnd = {
104
- when (swipeState.intValue) {
105
- 0 -> onSwipeRight()
106
- 1 -> onSwipeLeft()
107
- 2 -> onSwipeDown()
108
- 3 -> onSwipeUp()
109
- }
110
- }
111
- )
112
78
 
113
79
  @Composable
114
80
  fun ChapterScreen(
115
81
  model: AppViewModel,
116
82
  bookIndex: Int,
117
83
  chapterIndex: Int,
84
+ verseIndex: Int,
118
85
  navigateToChapter: (ChapterScreenProps) -> Unit,
119
86
  ) {
120
87
  val view = LocalView.current
@@ -122,15 +89,30 @@ fun ChapterScreen(
122
89
  val isSearching by model.isSearching.collectAsState()
123
90
  var chapterSelectorShown by remember { mutableStateOf(false) }
124
91
  var bibleSelectorShown by remember { mutableStateOf(false) }
125
- val headingColor = MaterialTheme.colorScheme.onSurface
126
92
  val bookNames by model.bookNames.collectAsState()
127
93
  val verses by model.verses.collectAsState()
94
+ val state = rememberSaveable(saver = listSaver(
95
+ save = {
96
+ model.verseIndex = it.firstVisibleItemIndex
97
+ listOf(it.firstVisibleItemIndex, it.firstVisibleItemScrollOffset)
98
+ },
99
+ restore = {
100
+ LazyListState(
101
+ firstVisibleItemIndex = model.verseIndex,
102
+ )
103
+ }
104
+ )) {
105
+ LazyListState(
106
+ firstVisibleItemIndex = verseIndex,
107
+ )
108
+ }
128
109
  val chapterVerses =
129
110
  verses.filter { it.bookIndex == bookIndex && it.chapterIndex == chapterIndex }
130
111
  LaunchedEffect(Unit) {
131
112
  model.clearSelectedVerses()
132
113
  model.bookIndex = bookIndex
133
114
  model.chapterIndex = chapterIndex
115
+ model.verseIndex = verseIndex
134
116
  }
135
117
  Scaffold(
136
118
  modifier = Modifier
@@ -188,7 +170,7 @@ fun ChapterScreen(
188
170
  style = TextStyle(
189
171
  fontSize = 22.sp,
190
172
  fontWeight = FontWeight.W500,
191
- color = headingColor,
173
+ color = MaterialTheme.colorScheme.onSurface,
192
174
  )
193
175
  )
194
176
  }
@@ -205,7 +187,7 @@ fun ChapterScreen(
205
187
  Icon(
206
188
  imageVector = Icons.Rounded.Search,
207
189
  contentDescription = "Search",
208
- tint = headingColor,
190
+ tint = MaterialTheme.colorScheme.onSurface,
209
191
  )
210
192
  }
211
193
  TextButton(onClick = {
@@ -217,7 +199,7 @@ fun ChapterScreen(
217
199
  style = TextStyle(
218
200
  fontSize = 18.sp,
219
201
  fontWeight = FontWeight.W500,
220
- color = headingColor,
202
+ color = MaterialTheme.colorScheme.onSurface,
221
203
  ),
222
204
  )
223
205
  }
@@ -229,16 +211,14 @@ fun ChapterScreen(
229
211
  Icon(
230
212
  imageVector = Icons.Outlined.MoreVert,
231
213
  contentDescription = "More",
232
- tint = headingColor,
214
+ tint = MaterialTheme.colorScheme.onSurface,
233
215
  )
234
216
  }
235
217
  }
236
218
  }
237
219
 
238
220
  LazyColumn(
239
- state = rememberSaveable(saver = LazyListState.Saver) {
240
- model.scrollState
221
+ state = state,
241
- },
242
222
  verticalArrangement = Arrangement.spacedBy(16.dp + (model.lineSpacingDelta * 2).dp),
243
223
  modifier = Modifier
244
224
  .fillMaxSize()
@@ -250,6 +230,7 @@ fun ChapterScreen(
250
230
  ChapterScreenProps(
251
231
  bookIndex = pair.first,
252
232
  chapterIndex = pair.second,
233
+ verseIndex = 0,
253
234
  )
254
235
  )
255
236
  },
@@ -259,6 +240,7 @@ fun ChapterScreen(
259
240
  ChapterScreenProps(
260
241
  bookIndex = pair.first,
261
242
  chapterIndex = pair.second,
243
+ verseIndex = 0,
262
244
  dir = Dir.Right.name,
263
245
  )
264
246
  )
app/src/main/java/dev/pyrossh/onlyBible/composables/ChapterSelector.kt CHANGED
@@ -151,6 +151,7 @@ fun ChapterSelector(
151
151
  ChapterScreenProps(
152
152
  bookIndex = bookIndex,
153
153
  chapterIndex = c,
154
+ verseIndex = 0,
154
155
  )
155
156
  )
156
157
  }
app/src/main/java/dev/pyrossh/onlyBible/composables/VerseHeading.kt CHANGED
@@ -54,6 +54,7 @@ fun VerseHeading(
54
54
  ChapterScreenProps(
55
55
  bookIndex = parts[0].toInt(),
56
56
  chapterIndex = parts[1].toInt(),
57
+ verseIndex = parts[2].toInt(),
57
58
  )
58
59
  )
59
60
  },
app/src/main/java/dev/pyrossh/onlyBible/utils/Swipe.kt ADDED
@@ -0,0 +1,39 @@
1
+ package dev.pyrossh.onlyBible.utils
2
+
3
+ import androidx.compose.foundation.gestures.detectDragGestures
4
+ import androidx.compose.runtime.MutableIntState
5
+ import androidx.compose.runtime.mutableIntStateOf
6
+ import androidx.compose.ui.input.pointer.PointerInputScope
7
+ import kotlin.math.abs
8
+
9
+ suspend fun PointerInputScope.detectSwipe(
10
+ swipeState: MutableIntState = mutableIntStateOf(-1),
11
+ onSwipeLeft: () -> Unit = {},
12
+ onSwipeRight: () -> Unit = {},
13
+ onSwipeUp: () -> Unit = {},
14
+ onSwipeDown: () -> Unit = {},
15
+ ) = detectDragGestures(
16
+ onDrag = { change, dragAmount ->
17
+ change.consume()
18
+ val (x, y) = dragAmount
19
+ if (abs(x) > abs(y)) {
20
+ when {
21
+ x > 0 -> swipeState.intValue = 0
22
+ x < 0 -> swipeState.intValue = 1
23
+ }
24
+ } else {
25
+ when {
26
+ y > 0 -> swipeState.intValue = 2
27
+ y < 0 -> swipeState.intValue = 3
28
+ }
29
+ }
30
+ },
31
+ onDragEnd = {
32
+ when (swipeState.intValue) {
33
+ 0 -> onSwipeRight()
34
+ 1 -> onSwipeLeft()
35
+ 2 -> onSwipeDown()
36
+ 3 -> onSwipeUp()
37
+ }
38
+ }
39
+ )