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


b07b6912 Peter John

1 year ago
navigation
.idea/vcs.xml ADDED
@@ -0,0 +1,6 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <project version="4">
3
+ <component name="VcsDirectoryMappings">
4
+ <mapping directory="$PROJECT_DIR$" vcs="Git" />
5
+ </component>
6
+ </project>
app/build.gradle.kts CHANGED
@@ -71,4 +71,5 @@ dependencies {
71
71
  androidTestImplementation(libs.androidx.ui.test.junit4)
72
72
  debugImplementation(libs.androidx.ui.tooling)
73
73
  debugImplementation(libs.androidx.ui.test.manifest)
74
+ implementation(libs.androidx.foundation)
74
75
  }
app/src/main/java/dev/pyros/bibleapp/AppHost.kt ADDED
@@ -0,0 +1,104 @@
1
+ package dev.pyros.bibleapp
2
+
3
+ import androidx.compose.animation.AnimatedContentTransitionScope
4
+ import androidx.compose.animation.core.tween
5
+ import androidx.compose.material3.rememberModalBottomSheetState
6
+ import androidx.compose.runtime.Composable
7
+ import androidx.compose.runtime.rememberCoroutineScope
8
+ import androidx.compose.ui.Modifier
9
+ import androidx.navigation.NavType
10
+ import androidx.navigation.compose.NavHost
11
+ import androidx.navigation.compose.composable
12
+ import androidx.navigation.compose.rememberNavController
13
+ import androidx.navigation.navArgument
14
+
15
+ @Composable
16
+ fun AppHost(verses: List<Verse>, modifier: Modifier = Modifier) {
17
+ val navController = rememberNavController()
18
+ NavHost(
19
+ modifier = modifier,
20
+ navController = navController,
21
+ startDestination = "/books/{book}/chapters/{chapter}",
22
+ enterTransition = {
23
+ slideIntoContainer(
24
+ AnimatedContentTransitionScope.SlideDirection.Left,
25
+ tween(500),
26
+ )
27
+ },
28
+ exitTransition = {
29
+ slideOutOfContainer(
30
+ AnimatedContentTransitionScope.SlideDirection.Left,
31
+ tween(500),
32
+ )
33
+ },
34
+ // popEnterTransition = {
35
+ // slideIntoContainer(
36
+ // AnimatedContentTransitionScope.SlideDirection.Right,
37
+ // tween(500),
38
+ // )
39
+ // },
40
+ // popExitTransition = {
41
+ // slideOutOfContainer(
42
+ // AnimatedContentTransitionScope.SlideDirection.Right,
43
+ // tween(500),
44
+ // )
45
+ // }
46
+ ) {
47
+ composable(
48
+ route = "/books/{book}/chapters/{chapter}",
49
+ arguments = listOf(
50
+ navArgument("book") { type = NavType.IntType },
51
+ navArgument("chapter") { type = NavType.IntType },
52
+ )
53
+ ) {
54
+ ChapterScreen(
55
+ verses = verses,
56
+ bookIndex = it.arguments?.getInt("book") ?: 0,
57
+ chapterIndex = it.arguments?.getInt("chapter") ?: 0,
58
+ navController = navController,
59
+ )
60
+ }
61
+ }
62
+ // val scope = rememberCoroutineScope()
63
+ // val sheetState = rememberModalBottomSheetState()
64
+ // var showBottomSheet by rememberSaveable { mutableStateOf(false) }
65
+ // val showSheet = {
66
+ // showBottomSheet = true
67
+ // }
68
+ // if (showBottomSheet) {
69
+ // ModalBottomSheet(
70
+ // onDismissRequest = {
71
+ // showBottomSheet = false
72
+ // },
73
+ // sheetState = sheetState
74
+ // ) {
75
+ // Text("All Chapters")
76
+ // LazyVerticalGrid(
77
+ // columns = GridCells.Adaptive(minSize = 128.dp)
78
+ // ) {
79
+ // items(chapters.size) { c ->
80
+ // Button(onClick = {
81
+ // changeChapter(c)
82
+ // scope.launch {
83
+ // sheetState.hide()
84
+ // }.invokeOnCompletion {
85
+ // if (!sheetState.isVisible) {
86
+ // showBottomSheet = false
87
+ // }
88
+ // }
89
+ // }) {
90
+ // Text(
91
+ // modifier = Modifier.padding(bottom = 4.dp),
92
+ // style = TextStyle(
93
+ // fontSize = 16.sp,
94
+ // fontWeight = FontWeight.W500,
95
+ // color = Color(0xFF9A1111),
96
+ // ),
97
+ // text = (c + 1).toString(),
98
+ // )
99
+ // }
100
+ // }
101
+ // }
102
+ // }
103
+ // }
104
+ }
app/src/main/java/dev/pyros/bibleapp/ChapterScreen.kt ADDED
@@ -0,0 +1,136 @@
1
+ package dev.pyros.bibleapp
2
+
3
+ import androidx.compose.animation.core.tween
4
+ import androidx.compose.foundation.ExperimentalFoundationApi
5
+ import androidx.compose.foundation.gestures.AnchoredDraggableState
6
+ import androidx.compose.foundation.gestures.DraggableAnchors
7
+ import androidx.compose.foundation.gestures.detectHorizontalDragGestures
8
+ import androidx.compose.foundation.layout.Column
9
+ import androidx.compose.foundation.layout.fillMaxSize
10
+ import androidx.compose.foundation.layout.padding
11
+ import androidx.compose.foundation.lazy.grid.GridCells
12
+ import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
13
+ import androidx.compose.foundation.rememberScrollState
14
+ import androidx.compose.foundation.verticalScroll
15
+ import androidx.compose.material3.Button
16
+ import androidx.compose.material3.ExperimentalMaterial3Api
17
+ import androidx.compose.material3.ModalBottomSheet
18
+ import androidx.compose.material3.Text
19
+ import androidx.compose.material3.rememberModalBottomSheetState
20
+ import androidx.compose.runtime.Composable
21
+ import androidx.compose.runtime.getValue
22
+ import androidx.compose.runtime.mutableStateOf
23
+ import androidx.compose.runtime.remember
24
+ import androidx.compose.runtime.rememberCoroutineScope
25
+ import androidx.compose.runtime.saveable.rememberSaveable
26
+ import androidx.compose.ui.Modifier
27
+ import androidx.compose.ui.graphics.Color
28
+ import androidx.compose.ui.input.pointer.pointerInput
29
+ import androidx.compose.ui.platform.LocalDensity
30
+ import androidx.compose.ui.text.SpanStyle
31
+ import androidx.compose.ui.text.TextStyle
32
+ import androidx.compose.ui.text.buildAnnotatedString
33
+ import androidx.compose.ui.text.font.FontWeight
34
+ import androidx.compose.ui.text.withStyle
35
+ import androidx.compose.ui.unit.dp
36
+ import androidx.compose.ui.unit.sp
37
+ import androidx.navigation.NavController
38
+ import kotlinx.coroutines.launch
39
+ import kotlinx.serialization.Serializable
40
+
41
+ enum class DragAnchors {
42
+ Start,
43
+ Center,
44
+ End,
45
+ }
46
+
47
+ @Serializable
48
+ data class ChapterScreenProps(
49
+ val bookIndex: Int,
50
+ val chapterIndex: Int,
51
+ )
52
+
53
+ @OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class)
54
+ @Composable
55
+ fun ChapterScreen(
56
+ verses: List<Verse>,
57
+ bookIndex: Int,
58
+ chapterIndex: Int,
59
+ navController: NavController
60
+ ) {
61
+ val chapters =
62
+ verses.filter { it.bookIndex == bookIndex }.map { it.chapterIndex }.distinct();
63
+
64
+ val changeChapter = { c: Int ->
65
+ navController.navigate(route = "/books/${bookIndex}/chapters/${c}")
66
+ }
67
+ val density = LocalDensity.current
68
+ val state = remember {
69
+ AnchoredDraggableState(
70
+ initialValue = DragAnchors.Start,
71
+ anchors = DraggableAnchors {
72
+ DragAnchors.Start at 0f
73
+ DragAnchors.End at 1000f
74
+ },
75
+ positionalThreshold = { distance: Float -> distance * 0.5f },
76
+ velocityThreshold = { with(density) { 100.dp.toPx() } },
77
+ animationSpec = tween(),
78
+ confirmValueChange = {
79
+ if (it == DragAnchors.End) {
80
+ changeChapter(chapterIndex + 1)
81
+ true
82
+ } else {
83
+ false
84
+ }
85
+ }
86
+ )
87
+ }
88
+
89
+
90
+ val chapterVerses =
91
+ verses.filter { it.bookIndex == bookIndex && it.chapterIndex == chapterIndex };
92
+ Column(
93
+ modifier = Modifier
94
+ .fillMaxSize()
95
+ .padding(16.dp)
96
+ .verticalScroll(rememberScrollState())
97
+ // .anchoredDraggable(state, Orientation.Horizontal, reverseDirection = true)
98
+ .pointerInput(Unit) {
99
+ detectHorizontalDragGestures { change, dragAmount ->
100
+ change.consume()
101
+ if (dragAmount < -5) {
102
+ changeChapter(chapterIndex - 1)
103
+ }
104
+ if (dragAmount < 5) {
105
+ changeChapter(chapterIndex + 1)
106
+ }
107
+ }
108
+ }
109
+ ) {
110
+ Header(chapterIndex)
111
+ chapterVerses.map {
112
+ Text(
113
+ modifier = Modifier.padding(bottom = 4.dp),
114
+ text = buildAnnotatedString {
115
+ withStyle(
116
+ style = SpanStyle(
117
+ fontSize = 16.sp,
118
+ fontWeight = FontWeight.W500,
119
+ color = Color(0xFF9A1111),
120
+ )
121
+ ) {
122
+ append((it.verseIndex + 1).toString() + " ")
123
+ }
124
+ withStyle(
125
+ style = SpanStyle(
126
+ fontSize = 18.sp,
127
+ fontWeight = FontWeight.W400,
128
+ )
129
+ ) {
130
+ append(it.text)
131
+ }
132
+ }
133
+ )
134
+ }
135
+ }
136
+ }
app/src/main/java/dev/pyros/bibleapp/Header.kt ADDED
@@ -0,0 +1,36 @@
1
+ package dev.pyros.bibleapp
2
+
3
+ import androidx.compose.foundation.ExperimentalFoundationApi
4
+ import androidx.compose.foundation.combinedClickable
5
+ import androidx.compose.foundation.layout.Row
6
+ import androidx.compose.foundation.layout.padding
7
+ import androidx.compose.material3.Text
8
+ import androidx.compose.runtime.Composable
9
+ import androidx.compose.ui.Modifier
10
+ import androidx.compose.ui.text.TextStyle
11
+ import androidx.compose.ui.text.font.FontWeight
12
+ import androidx.compose.ui.unit.dp
13
+ import androidx.compose.ui.unit.sp
14
+
15
+ @OptIn(ExperimentalFoundationApi::class)
16
+ @Composable
17
+ fun Header(
18
+ chapterIndex: Int,
19
+ ) {
20
+ Row(modifier = Modifier.padding(bottom = 8.dp)) {
21
+ Text(
22
+ modifier = Modifier.combinedClickable(
23
+ enabled = true,
24
+ onClick = {
25
+ },
26
+ onDoubleClick = {
27
+ }
28
+ ),
29
+ style = TextStyle(
30
+ fontSize = 22.sp,
31
+ fontWeight = FontWeight.W600,
32
+ ),
33
+ text = "Genesis ${chapterIndex + 1}"
34
+ )
35
+ }
36
+ }
app/src/main/java/dev/pyros/bibleapp/MainActivity.kt CHANGED
@@ -5,64 +5,12 @@ import android.util.Log
5
5
  import androidx.activity.ComponentActivity
6
6
  import androidx.activity.compose.setContent
7
7
  import androidx.activity.enableEdgeToEdge
8
- import androidx.compose.animation.core.AnimationSpec
9
- import androidx.compose.animation.core.EaseInOut
10
- import androidx.compose.animation.core.tween
11
- import androidx.compose.foundation.ExperimentalFoundationApi
12
- import androidx.compose.foundation.combinedClickable
13
- import androidx.compose.foundation.gestures.Orientation
14
- import androidx.compose.foundation.gestures.draggable
15
- import androidx.compose.foundation.gestures.rememberDraggableState
16
- import androidx.compose.foundation.layout.Column
17
- import androidx.compose.foundation.layout.Row
18
8
  import androidx.compose.foundation.layout.fillMaxSize
19
- import androidx.compose.foundation.layout.offset
20
9
  import androidx.compose.foundation.layout.padding
21
- import androidx.compose.foundation.lazy.grid.GridCells
22
- import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
23
- import androidx.compose.foundation.pager.HorizontalPager
24
- import androidx.compose.foundation.pager.PagerDefaults
25
- import androidx.compose.foundation.pager.PagerSnapDistance
26
- import androidx.compose.foundation.pager.rememberPagerState
27
- import androidx.compose.foundation.rememberScrollState
28
- import androidx.compose.foundation.verticalScroll
29
- import androidx.compose.material3.Button
30
- import androidx.compose.material3.ExperimentalMaterial3Api
31
- import androidx.compose.material3.ModalBottomSheet
32
10
  import androidx.compose.material3.Scaffold
33
- import androidx.compose.material3.Surface
34
- import androidx.compose.material3.Text
35
- import androidx.compose.material3.rememberModalBottomSheetState
36
- import androidx.compose.runtime.Composable
37
- import androidx.compose.runtime.getValue
38
- import androidx.compose.runtime.mutableIntStateOf
39
- import androidx.compose.runtime.mutableStateOf
40
- import androidx.compose.runtime.remember
41
- import androidx.compose.runtime.rememberCoroutineScope
42
- import androidx.compose.runtime.saveable.rememberSaveable
43
- import androidx.compose.runtime.setValue
44
11
  import androidx.compose.ui.Modifier
45
- import androidx.compose.ui.graphics.Color
46
- import androidx.compose.ui.graphics.graphicsLayer
47
- import androidx.compose.ui.text.SpanStyle
48
- import androidx.compose.ui.text.TextStyle
49
- import androidx.compose.ui.text.buildAnnotatedString
50
- import androidx.compose.ui.text.font.FontWeight
51
- import androidx.compose.ui.text.withStyle
52
- import androidx.compose.ui.unit.dp
53
- import androidx.compose.ui.unit.sp
54
- import androidx.compose.ui.util.lerp
55
- import androidx.navigation.NavType
56
- import androidx.navigation.compose.NavHost
57
- import androidx.navigation.compose.composable
58
- import androidx.navigation.compose.rememberNavController
59
- import androidx.navigation.navArgument
60
12
  import dev.pyros.bibleapp.ui.theme.BibleAppTheme
61
- import kotlinx.coroutines.Job
62
- import kotlinx.coroutines.launch
63
- import kotlinx.serialization.Serializable
64
13
  import java.io.BufferedReader
65
- import kotlin.math.absoluteValue
66
14
 
67
15
  data class Verse(
68
16
  val bookIndex: Int,
@@ -108,190 +56,4 @@ class MainActivity : ComponentActivity() {
108
56
  }
109
57
  }
110
58
 
111
- @Composable
112
- fun AppHost(verses: List<Verse>, modifier: Modifier = Modifier) {
113
- val navController = rememberNavController()
114
- NavHost(
115
- modifier = modifier,
116
- navController = navController,
117
- startDestination = "/books/{book}/chapters/{chapter}"
118
- ) {
119
- composable(
120
- route = "/books/{book}/chapters/{chapter}",
121
- arguments = listOf(
122
- navArgument("book") { type = NavType.IntType },
123
- navArgument("chapter") { type = NavType.IntType },
124
- )
125
- ) {
126
- ChapterScreen(
127
- verses = verses,
128
- bookIndex = it.arguments?.getInt("book") ?: 0,
129
- chapterIndex = it.arguments?.getInt("chapter") ?: 0,
130
- )
131
- }
132
- }
133
- }
134
-
135
59
 
136
- @OptIn(ExperimentalFoundationApi::class, ExperimentalMaterial3Api::class)
137
- @Composable
138
- fun ChapterScreen(verses: List<Verse>, bookIndex: Int, chapterIndex: Int) {
139
- val scope = rememberCoroutineScope()
140
- val sheetState = rememberModalBottomSheetState()
141
- var showBottomSheet by rememberSaveable { mutableStateOf(false) }
142
- var currentBookIndex by rememberSaveable { mutableStateOf(0) }
143
- val chapters =
144
- verses.filter { it.bookIndex == currentBookIndex }.map { it.chapterIndex }.distinct();
145
- val pagerState = rememberPagerState(
146
- pageCount = {
147
- 929
148
- })
149
- val showSheet = {
150
- showBottomSheet = true
151
- }
152
- val setBookIndex = { v: Int ->
153
- scope.launch {
154
- pagerState.animateScrollToPage(
155
- page = v,
156
- animationSpec = tween(
157
- durationMillis = 300,
158
- easing = EaseInOut,
159
- )
160
- )
161
- }
162
- // currentBookIndex = v
163
- }
164
-
165
- // val fling = PagerDefaults.flingBehavior(
166
- // state = pagerState,
167
- // pagerSnapDistance = PagerSnapDistance.atMost(10)
168
- // )
169
-
170
- if (showBottomSheet) {
171
- ModalBottomSheet(
172
- onDismissRequest = {
173
- showBottomSheet = false
174
- },
175
- sheetState = sheetState
176
- ) {
177
- Text("All Chapters")
178
- LazyVerticalGrid(
179
- columns = GridCells.Adaptive(minSize = 128.dp)
180
- ) {
181
- items(chapters.size) { c ->
182
- Button(onClick = {
183
- scope.launch {
184
- sheetState.hide()
185
- pagerState.animateScrollToPage(
186
- page = c,
187
- // animationSpec = tween(
188
- // durationMillis = 300,
189
- // easing = EaseInOut,
190
- // )
191
- )
192
- }.invokeOnCompletion {
193
- if (!sheetState.isVisible) {
194
- showBottomSheet = false
195
- }
196
- }
197
- }) {
198
- Text(
199
- modifier = Modifier.padding(bottom = 4.dp),
200
- style = TextStyle(
201
- fontSize = 16.sp,
202
- fontWeight = FontWeight.W500,
203
- color = Color(0xFF9A1111),
204
- ),
205
- text = (c + 1).toString(),
206
- )
207
- }
208
- }
209
- }
210
- }
211
- }
212
-
213
- HorizontalPager(
214
- state = pagerState,
215
- beyondBoundsPageCount = 10,
216
-
217
- // flingBehavior = fling
218
- ) { page ->
219
- val chapterVerses =
220
- verses.filter { it.bookIndex == currentBookIndex && it.chapterIndex == page };
221
- Column(
222
- modifier = Modifier
223
- .padding(16.dp)
224
- .verticalScroll(rememberScrollState())
225
- .graphicsLayer {
226
- // Calculate the absolute offset for the current page from the
227
- // scroll position. We use the absolute value which allows us to mirror
228
- // any effects for both directions
229
- val pageOffset = (
230
- (pagerState.currentPage - page) + pagerState
231
- .currentPageOffsetFraction
232
- ).absoluteValue
233
-
234
- // We animate the alpha, between 50% and 100%
235
- alpha = lerp(
236
- start = 0.5f,
237
- stop = 1f,
238
- fraction = 1f - pageOffset.coerceIn(0f, 1f)
239
- )
240
- }
241
- ) {
242
- Header(page, chapterVerses[0].chapterIndex, showSheet, setBookIndex)
243
- chapterVerses.map {
244
- Text(
245
- modifier = Modifier.padding(bottom = 4.dp),
246
- text = buildAnnotatedString {
247
- withStyle(
248
- style = SpanStyle(
249
- fontSize = 16.sp,
250
- fontWeight = FontWeight.W500,
251
- color = Color(0xFF9A1111),
252
- )
253
- ) {
254
- append((it.verseIndex + 1).toString() + " ")
255
- }
256
- withStyle(
257
- style = SpanStyle(
258
- fontSize = 18.sp,
259
- fontWeight = FontWeight.W400,
260
- )
261
- ) {
262
- append(it.text)
263
- }
264
- }
265
- )
266
- }
267
- }
268
- }
269
- }
270
-
271
- @OptIn(ExperimentalFoundationApi::class)
272
- @Composable
273
- fun Header(
274
- currentBookIndex: Int,
275
- chapterIndex: Int,
276
- showSheet: () -> Unit,
277
- setBookIndex: (Int) -> Job
278
- ) {
279
- Row(modifier = Modifier.padding(bottom = 8.dp)) {
280
- Text(
281
- modifier = Modifier.combinedClickable(
282
- enabled = true,
283
- onClick = {
284
- showSheet()
285
- },
286
- onDoubleClick = {
287
- showSheet()
288
- }
289
- ),
290
- style = TextStyle(
291
- fontSize = 22.sp,
292
- fontWeight = FontWeight.W600,
293
- ),
294
- text = "Genesis ${chapterIndex + 1}"
295
- )
296
- }
297
- }
gradle/libs.versions.toml CHANGED
@@ -1,5 +1,6 @@
1
1
  [versions]
2
2
  agp = "8.4.0"
3
+ foundation = "1.6.7"
3
4
  kotlin = "1.9.0"
4
5
  coreKtx = "1.10.1"
5
6
  junit = "4.13.2"
@@ -14,6 +15,7 @@ uiUtilAndroid = "1.6.7"
14
15
 
15
16
  [libraries]
16
17
  androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
18
+ androidx-foundation = { module = "androidx.compose.foundation:foundation", version.ref = "foundation" }
17
19
  androidx-navigation-compose = { module = "androidx.navigation:navigation-compose", version.ref = "navigationFragmentKtx" }
18
20
  androidx-navigation-fragment-ktx = { module = "androidx.navigation:navigation-fragment-ktx", version.ref = "navigationFragmentKtx" }
19
21
  androidx-navigation-ui-ktx = { module = "androidx.navigation:navigation-ui-ktx", version.ref = "navigationFragmentKtx" }