~repos /only-bible-app

#kotlin#android#ios

git clone https://pyrossh.dev/repos/only-bible-app.git
Discussions: https://groups.google.com/g/rust-embed-devs

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


b8e89ec9 Peter John

1 year ago
more stuff
app/build.gradle.kts CHANGED
@@ -4,6 +4,8 @@ plugins {
4
4
  alias(libs.plugins.android.application)
5
5
  alias(libs.plugins.compose.compiler)
6
6
  alias(libs.plugins.jetbrains.kotlin.android)
7
+ alias(libs.plugins.kotlinx.serializer)
8
+ alias(libs.plugins.kotlin.parcelize)
7
9
  alias(libs.plugins.secrets.gradle.plugin)
8
10
  }
9
11
 
app/src/main/java/dev/pyrossh/onlyBible/AppHost.kt CHANGED
@@ -1,44 +1,28 @@
1
1
  package dev.pyrossh.onlyBible
2
2
 
3
3
  import Verse
4
- import android.content.Context
5
4
  import androidx.compose.animation.AnimatedContentTransitionScope
6
5
  import androidx.compose.animation.core.tween
7
6
  import androidx.compose.runtime.Composable
8
- import androidx.datastore.core.DataStore
9
- import androidx.datastore.preferences.core.Preferences
10
- import androidx.datastore.preferences.preferencesDataStore
11
- import androidx.navigation.NavType
12
7
  import androidx.navigation.compose.NavHost
13
8
  import androidx.navigation.compose.composable
14
9
  import androidx.navigation.compose.rememberNavController
15
- import androidx.navigation.navArgument
10
+ import androidx.navigation.toRoute
16
-
17
- val Context.dataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")
18
11
 
19
12
  @Composable
20
13
  fun AppHost(verses: List<Verse>) {
21
14
  val navController = rememberNavController()
15
+ val state = LocalState.current!!
22
16
  Drawer(navController) { openDrawer ->
23
17
  NavHost(
24
18
  navController = navController,
25
- startDestination = "/books/0/chapters/0?dir=left",
19
+ startDestination = ChapterScreenProps(state.getBookIndex(), state.getChapterIndex())
26
20
  ) {
27
- composable(
28
- route = "/books/{book}/chapters/{chapter}?dir={dir}",
29
- arguments = listOf(
30
- navArgument("book") { type = NavType.IntType },
31
- navArgument("chapter") { type = NavType.IntType },
21
+ composable<ChapterScreenProps>(
32
- navArgument("dir") { type = NavType.StringType },
33
- ),
34
22
  enterTransition = {
35
- val dir = this.targetState.arguments?.getString("dir") ?: "left"
23
+ val props = this.targetState.toRoute<ChapterScreenProps>()
36
- val slideDirection = when (dir) {
37
- "left" -> AnimatedContentTransitionScope.SlideDirection.Left
38
- else -> AnimatedContentTransitionScope.SlideDirection.Right
39
- }
40
24
  slideIntoContainer(
41
- slideDirection,
25
+ Dir.valueOf(props.dir).slideDirection(),
42
26
  tween(400),
43
27
  )
44
28
  },
@@ -61,10 +45,13 @@ fun AppHost(verses: List<Verse>) {
61
45
  )
62
46
  }
63
47
  ) {
48
+ val props = it.toRoute<ChapterScreenProps>()
49
+ state.setBookIndex(props.bookIndex)
50
+ state.setChapterIndex(props.chapterIndex)
64
51
  ChapterScreen(
65
52
  verses = verses,
66
- bookIndex = it.arguments?.getInt("book")!!,
53
+ bookIndex = props.bookIndex,
67
- chapterIndex = it.arguments?.getInt("chapter")!!,
54
+ chapterIndex = props.chapterIndex,
68
55
  navController = navController,
69
56
  openDrawer = openDrawer,
70
57
  )
app/src/main/java/dev/pyrossh/onlyBible/ChapterScreen.kt CHANGED
@@ -1,50 +1,36 @@
1
1
  package dev.pyrossh.onlyBible
2
2
 
3
- import FontType
4
- import PreferencesManager
5
3
  import Verse
6
4
  import android.annotation.SuppressLint
7
- import android.content.Context
5
+ import android.os.Parcelable
6
+ import androidx.compose.animation.AnimatedContentTransitionScope
8
7
  import androidx.compose.foundation.ExperimentalFoundationApi
9
8
  import androidx.compose.foundation.background
9
+ import androidx.compose.foundation.clickable
10
10
  import androidx.compose.foundation.gestures.detectHorizontalDragGestures
11
- import androidx.compose.foundation.gestures.detectTapGestures
12
11
  import androidx.compose.foundation.layout.Arrangement
13
12
  import androidx.compose.foundation.layout.Box
14
- import androidx.compose.foundation.layout.Column
15
13
  import androidx.compose.foundation.layout.Row
16
14
  import androidx.compose.foundation.layout.fillMaxSize
17
15
  import androidx.compose.foundation.layout.fillMaxWidth
18
- import androidx.compose.foundation.layout.height
19
16
  import androidx.compose.foundation.layout.padding
20
- import androidx.compose.foundation.layout.size
21
17
  import androidx.compose.foundation.layout.wrapContentSize
22
18
  import androidx.compose.foundation.lazy.LazyColumn
23
19
  import androidx.compose.foundation.lazy.items
24
- import androidx.compose.foundation.shape.RoundedCornerShape
25
20
  import androidx.compose.foundation.text.selection.DisableSelection
26
21
  import androidx.compose.material.icons.Icons
27
- import androidx.compose.material.icons.filled.Close
28
22
  import androidx.compose.material.icons.filled.FaceRetouchingNatural
29
- import androidx.compose.material.icons.filled.FormatBold
30
- import androidx.compose.material.icons.filled.FormatLineSpacing
31
- import androidx.compose.material.icons.filled.FormatSize
32
23
  import androidx.compose.material.icons.outlined.MoreVert
33
24
  import androidx.compose.material.icons.outlined.Share
34
- import androidx.compose.material3.ExperimentalMaterial3Api
35
- import androidx.compose.material3.HorizontalDivider
36
25
  import androidx.compose.material3.Icon
37
26
  import androidx.compose.material3.IconButton
38
27
  import androidx.compose.material3.MaterialTheme
39
- import androidx.compose.material3.ModalBottomSheet
40
28
  import androidx.compose.material3.Scaffold
41
29
  import androidx.compose.material3.Surface
42
30
  import androidx.compose.material3.Text
43
- import androidx.compose.material3.rememberModalBottomSheetState
44
31
  import androidx.compose.runtime.Composable
45
32
  import androidx.compose.runtime.getValue
46
33
  import androidx.compose.runtime.mutableFloatStateOf
47
- import androidx.compose.runtime.mutableIntStateOf
48
34
  import androidx.compose.runtime.mutableStateOf
49
35
  import androidx.compose.runtime.remember
50
36
  import androidx.compose.runtime.rememberCoroutineScope
@@ -52,54 +38,47 @@ import androidx.compose.runtime.saveable.rememberSaveable
52
38
  import androidx.compose.runtime.setValue
53
39
  import androidx.compose.ui.Alignment
54
40
  import androidx.compose.ui.Modifier
55
- import androidx.compose.ui.geometry.Rect
56
41
  import androidx.compose.ui.graphics.Color
57
42
  import androidx.compose.ui.input.pointer.pointerInput
58
- import androidx.compose.ui.layout.boundsInWindow
59
- import androidx.compose.ui.layout.onGloballyPositioned
60
43
  import androidx.compose.ui.platform.LocalContext
61
- import androidx.compose.ui.res.painterResource
62
44
  import androidx.compose.ui.text.SpanStyle
63
45
  import androidx.compose.ui.text.TextStyle
64
46
  import androidx.compose.ui.text.buildAnnotatedString
65
- import androidx.compose.ui.text.font.FontFamily
66
47
  import androidx.compose.ui.text.font.FontWeight
67
48
  import androidx.compose.ui.text.withStyle
68
49
  import androidx.compose.ui.unit.dp
69
50
  import androidx.compose.ui.unit.sp
70
- import androidx.datastore.preferences.core.edit
71
- import androidx.datastore.preferences.core.intPreferencesKey
72
51
  import androidx.navigation.NavController
73
52
  import convertVersesToSpeech
74
53
  import kotlinx.coroutines.Job
75
54
  import kotlinx.coroutines.launch
55
+ import kotlinx.parcelize.Parcelize
76
56
  import kotlinx.serialization.Serializable
77
57
  import shareVerses
78
58
 
79
- // TODO: once androidx.navigation 2.8.0 is released
80
59
  @Serializable
60
+ @Parcelize
81
61
  data class ChapterScreenProps(
82
62
  val bookIndex: Int,
83
63
  val chapterIndex: Int,
64
+ // TODO: fix this
65
+ val dir: String = Dir.Left.name,
84
- )
66
+ ) : Parcelable
85
67
 
68
+ @Parcelize
86
- val fontSizeDeltaKey = intPreferencesKey("fontSizeDelta");
69
+ enum class Dir : Parcelable {
70
+ Left, Right;
87
71
 
88
- suspend fun incrementFontSize(context: Context) {
89
- context.dataStore.edit { settings ->
90
- val currentCounterValue = settings[fontSizeDeltaKey] ?: 0
72
+ fun slideDirection(): AnimatedContentTransitionScope.SlideDirection {
73
+ return when (this) {
91
- settings[fontSizeDeltaKey] = currentCounterValue + 1
74
+ Left -> AnimatedContentTransitionScope.SlideDirection.Left
75
+ Right -> AnimatedContentTransitionScope.SlideDirection.Right
76
+ }
92
77
  }
93
78
  }
94
79
 
95
- suspend fun decrementFontSize(context: Context) {
96
- context.dataStore.edit { settings ->
97
- val currentCounterValue = settings[fontSizeDeltaKey] ?: 0
98
- settings[fontSizeDeltaKey] = currentCounterValue - 1
99
- }
100
- }
101
80
 
102
- @OptIn(ExperimentalMaterial3Api::class, ExperimentalFoundationApi::class)
81
+ @OptIn(ExperimentalFoundationApi::class)
103
82
  @SuppressLint("MutableCollectionMutableState")
104
83
  @Composable
105
84
  fun ChapterScreen(
@@ -110,18 +89,10 @@ fun ChapterScreen(
110
89
  openDrawer: (MenuType, Int) -> Job,
111
90
  ) {
112
91
  val context = LocalContext.current
113
- val prefs = PreferencesManager(context)
92
+ val state = LocalState.current!!
114
- var fontType by remember { mutableStateOf(prefs.getFontType()) }
115
- var fontSizeDelta by remember { mutableIntStateOf(prefs.getFontSize()) }
116
- var boldEnabled by remember { mutableStateOf(prefs.getBold()) }
117
- val fontFamily = when (fontType) {
93
+ val fontFamily = state.fontType.family()
118
- FontType.Sans -> FontFamily.SansSerif
119
- FontType.Serif -> FontFamily.Serif
120
- FontType.Mono -> FontFamily.Monospace
121
- }
122
- val boldWeight = if (boldEnabled) FontWeight.W700 else FontWeight.W400
94
+ val boldWeight = if (state.boldEnabled) FontWeight.W700 else FontWeight.W400
123
95
  val scope = rememberCoroutineScope()
124
- var selectedVerseBounds: Rect by remember { mutableStateOf(Rect.Zero) }
125
96
  var selectedVerses by rememberSaveable {
126
97
  mutableStateOf(listOf<Verse>())
127
98
  }
@@ -129,380 +100,65 @@ fun ChapterScreen(
129
100
  mutableFloatStateOf(0.0f)
130
101
  }
131
102
  val chapterVerses =
132
- verses.filter { it.bookIndex == bookIndex && it.chapterIndex == chapterIndex };
103
+ verses.filter { it.bookIndex == bookIndex && it.chapterIndex == chapterIndex }
133
104
  Scaffold(
134
105
  modifier = Modifier.fillMaxSize(),
135
106
  ) { innerPadding ->
136
- val sheetState = rememberModalBottomSheetState()
137
- var showBottomSheet by rememberSaveable { mutableStateOf(false) }
138
- val showSheet = {
139
- showBottomSheet = true
140
- }
141
- val closeSheet = {
142
- showBottomSheet = false
143
- }
144
- if (showBottomSheet) {
145
- ModalBottomSheet(
146
- onDismissRequest = {
147
- showBottomSheet = false
148
- },
149
- sheetState = sheetState
150
- ) {
151
- Column(
152
- modifier = Modifier
153
- .fillMaxSize()
154
- .padding(horizontal = 16.dp),
155
- ) {
156
- Row(
157
- modifier = Modifier.fillMaxWidth(),
158
- horizontalArrangement = Arrangement.SpaceBetween,
159
- verticalAlignment = Alignment.CenterVertically,
160
- ) {
161
- Text(
162
- text = "Text Settings",
163
- fontSize = 20.sp,
164
- fontWeight = FontWeight.W500
165
- )
166
- Row(horizontalArrangement = Arrangement.End) {
167
- IconButton(onClick = {
168
- scope.launch {
169
- closeSheet()
170
- }
171
- }) {
172
- Icon(Icons.Filled.Close, "Close")
173
- }
174
- }
175
- }
176
- HorizontalDivider()
177
- Row(
178
- modifier = Modifier
179
- .fillMaxWidth()
180
- .padding(top = 16.dp),
181
- horizontalArrangement = Arrangement.SpaceBetween,
182
- verticalAlignment = Alignment.CenterVertically,
183
- ) {
184
- Surface(
185
- shape = RoundedCornerShape(8.dp),
186
- modifier = Modifier
187
- .fillMaxWidth()
188
- .height(60.dp)
189
- .padding(end = 16.dp)
190
- .weight(1f),
191
- onClick = {
192
- fontSizeDelta -= 1
193
- prefs.setFontSize(fontSizeDelta)
194
- }
195
- ) {
196
- Column(
197
- modifier = Modifier
198
- .background(Color(0xFFFAFAFA)),
199
- verticalArrangement = Arrangement.Center,
200
- horizontalAlignment = Alignment.CenterHorizontally
201
- ) {
202
- Icon(
203
- imageVector = Icons.Filled.FormatSize,
204
- contentDescription = "Bold",
205
- modifier = Modifier.size(14.dp),
206
- )
207
- }
208
- }
209
- Surface(
210
- shape = RoundedCornerShape(8.dp),
211
- modifier = Modifier
212
- .fillMaxWidth()
213
- .height(60.dp)
214
- .padding(end = 16.dp)
215
- .weight(1f),
216
- onClick = {
217
- fontSizeDelta += 1
218
- prefs.setFontSize(fontSizeDelta)
219
- }
220
- ) {
221
- Column(
222
- modifier = Modifier
223
- .background(Color(0xFFFAFAFA)),
224
- verticalArrangement = Arrangement.Center,
225
- horizontalAlignment = Alignment.CenterHorizontally
226
- ) {
227
- Icon(
228
- imageVector = Icons.Filled.FormatSize,
229
- contentDescription = "Bold"
230
- )
231
- }
232
- }
233
- Surface(
234
- shape = RoundedCornerShape(8.dp),
235
- modifier = Modifier
236
- .fillMaxWidth()
237
- .height(60.dp)
238
- .padding(end = 16.dp)
239
- .weight(1f),
240
- onClick = {
241
- boldEnabled = !boldEnabled
242
- prefs.setBold(boldEnabled)
243
- }
244
- ) {
245
- Column(
246
- modifier = Modifier
247
- .background(Color(0xFFFAFAFA)),
248
- verticalArrangement = Arrangement.Center,
249
- horizontalAlignment = Alignment.CenterHorizontally
250
- ) {
251
- Icon(
252
- imageVector = Icons.Filled.FormatBold,
253
- contentDescription = "Bold"
254
- )
255
- }
256
- }
257
- Surface(
258
- shape = RoundedCornerShape(8.dp),
259
- modifier = Modifier
260
- .fillMaxWidth()
261
- .height(60.dp)
262
- .padding(end = 16.dp)
263
- .weight(1f),
264
- onClick = {}
265
- ) {
266
- Column(
267
- modifier = Modifier
268
- .background(Color(0xFFFAFAFA)),
269
- verticalArrangement = Arrangement.Center,
270
- horizontalAlignment = Alignment.CenterHorizontally
271
- ) {
272
- Icon(
273
- imageVector = Icons.Filled.FormatLineSpacing,
274
- contentDescription = "Line Spacing"
275
- )
276
- }
277
- }
278
- }
279
- Row(
280
- modifier = Modifier
281
- .fillMaxWidth()
282
- .padding(top = 16.dp),
283
- horizontalArrangement = Arrangement.SpaceBetween,
284
- verticalAlignment = Alignment.CenterVertically,
285
- ) {
286
- Surface(
287
- shape = RoundedCornerShape(8.dp),
288
- modifier = Modifier
289
- .fillMaxWidth()
290
- .height(60.dp)
291
- .padding(end = 16.dp)
292
- .weight(1f),
293
- onClick = {
294
- fontType = FontType.Sans
295
- prefs.setFontType(fontType)
296
- }
297
- ) {
298
- Column(
299
- modifier = Modifier
300
- .background(Color(0xFFFAFAFA)),
301
- verticalArrangement = Arrangement.Center,
302
- horizontalAlignment = Alignment.CenterHorizontally
303
- ) {
304
- Text(
305
- text = "Sans",
306
- style = TextStyle(
307
- fontSize = 18.sp,
308
- fontWeight = FontWeight.Medium,
309
- )
310
- )
311
- }
312
- }
313
- Surface(
314
- shape = RoundedCornerShape(8.dp),
315
- modifier = Modifier
316
- .fillMaxWidth()
317
- .height(60.dp)
318
- .padding(end = 16.dp)
319
- .weight(1f),
320
- onClick = {
321
- fontType = FontType.Serif
322
- prefs.setFontType(fontType)
323
- }
324
- ) {
325
- Column(
326
- modifier = Modifier
327
- .background(Color(0xFFFAFAFA)),
328
- verticalArrangement = Arrangement.Center,
329
- horizontalAlignment = Alignment.CenterHorizontally
330
- ) {
331
- Text(
332
- text = "Serif",
333
- style = TextStyle(
334
- fontSize = 18.sp,
335
- fontWeight = FontWeight.Medium,
336
- )
337
- )
338
- }
339
- }
340
- Surface(
341
- shape = RoundedCornerShape(8.dp),
342
- modifier = Modifier
343
- .fillMaxWidth()
344
- .height(60.dp)
345
- .padding(end = 16.dp)
346
- .weight(1f),
347
- onClick = {
348
- fontType = FontType.Mono
349
- prefs.setFontType(fontType)
350
- }
351
- ) {
352
- Column(
353
- modifier = Modifier
354
- .background(Color(0xFFFAFAFA)),
355
- verticalArrangement = Arrangement.Center,
356
- horizontalAlignment = Alignment.CenterHorizontally
357
- ) {
358
- Text(
359
- text = "Mono",
360
- style = TextStyle(
361
- fontSize = 18.sp,
362
- fontWeight = FontWeight.Medium,
363
- )
364
- )
365
- }
366
- }
367
- }
368
- Row(
369
- modifier = Modifier
370
- .fillMaxWidth()
371
- .padding(top = 16.dp),
372
- horizontalArrangement = Arrangement.SpaceBetween,
373
- verticalAlignment = Alignment.CenterVertically,
374
- ) {
375
- // #72abbf on active
376
- // #ebe0c7 on yellow
377
- // #424547 on dark
378
- Surface(
379
- shape = RoundedCornerShape(8.dp),
380
- modifier = Modifier
381
- .fillMaxWidth()
382
- .height(80.dp)
383
- .padding(end = 16.dp)
384
- .weight(1f),
385
- onClick = {}
386
- ) {
387
- Icon(
388
- painter = painterResource(id = R.drawable.text_theme),
389
- contentDescription = "Light",
390
- tint = Color(0xFF424547),
391
- modifier = Modifier
392
- .background(Color.White)
393
- .padding(8.dp)
394
- )
395
- }
396
- Surface(
397
- shape = RoundedCornerShape(8.dp),
398
- modifier = Modifier
399
- .fillMaxWidth()
400
- .height(80.dp)
401
- .padding(end = 16.dp)
402
- .weight(1f),
403
- onClick = {}
404
- ) {
405
- Icon(
406
- painter = painterResource(id = R.drawable.text_theme),
407
- contentDescription = "Warm",
408
- tint = Color(0xFF424547),
409
- modifier = Modifier
410
- .background(Color(0xFFe5e0d1))
411
- .padding(8.dp)
412
- )
413
- }
414
- Surface(
415
- shape = RoundedCornerShape(8.dp),
416
- modifier = Modifier
417
- .fillMaxWidth()
418
- .height(80.dp)
419
- .padding(end = 16.dp)
420
- .weight(1f),
421
- onClick = {}
422
- ) {
423
- Icon(
424
- painter = painterResource(id = R.drawable.text_theme),
425
- contentDescription = "Dark",
426
- tint = Color(0xFFd3d7da),
427
- modifier = Modifier
428
- .background(Color(0xFF2c2e30))
429
- .padding(8.dp)
430
- )
431
- }
432
- Surface(
433
- shape = RoundedCornerShape(8.dp),
434
- modifier = Modifier
435
- .fillMaxWidth()
436
- .height(80.dp)
437
- .padding(end = 16.dp)
438
- .weight(1f),
439
- onClick = {}
440
- ) {
441
- Column(
442
- modifier = Modifier
443
- .background(Color(0xFFFAFAFA)),
444
- verticalArrangement = Arrangement.Center,
445
- horizontalAlignment = Alignment.CenterHorizontally
446
- ) {
447
- Text(
448
- text = "Auto",
449
- style = TextStyle(
450
- fontSize = 18.sp,
451
- fontWeight = FontWeight.Medium,
452
- )
453
- )
454
- }
455
- }
456
- }
457
- }
458
- }
459
- }
460
107
  LazyColumn(
461
- // verticalArrangement = Arrangement.spacedBy(4.dp),
108
+ verticalArrangement = Arrangement.spacedBy(12.dp),
462
109
  modifier = Modifier
463
110
  .fillMaxSize()
464
111
  .padding(innerPadding)
465
112
  .padding(horizontal = 16.dp)
466
113
  .pointerInput(Unit) {
467
- detectHorizontalDragGestures(
114
+ detectHorizontalDragGestures(onDragEnd = {
468
- onDragEnd = {
469
115
  // println("END " + dragAmount);
470
- if (dragAmount < 0) {
116
+ if (dragAmount < 0) {
471
- val pair = Verse.getForwardPair(bookIndex, chapterIndex)
117
+ val pair = Verse.getForwardPair(bookIndex, chapterIndex)
472
- navController.navigate(route = "/books/${pair.first}/chapters/${pair.second}?dir=left")
118
+ navController.navigate(
119
+ ChapterScreenProps(
120
+ bookIndex = pair.first,
121
+ chapterIndex = pair.second,
122
+ )
123
+ )
473
- } else if (dragAmount > 0) {
124
+ } else if (dragAmount > 0) {
474
- val pair = Verse.getBackwardPair(bookIndex, chapterIndex)
125
+ val pair = Verse.getBackwardPair(bookIndex, chapterIndex)
475
- if (navController.previousBackStackEntry != null) {
126
+ if (navController.previousBackStackEntry != null) {
476
- val previousBook =
127
+ val previousBook =
477
- navController.previousBackStackEntry?.arguments?.getInt("book")
128
+ navController.previousBackStackEntry?.arguments?.getInt("book")
478
- ?: 0
129
+ ?: 0
479
- val previousChapter =
130
+ val previousChapter =
480
- navController.previousBackStackEntry?.arguments?.getInt("chapter")
131
+ navController.previousBackStackEntry?.arguments?.getInt("chapter")
481
- ?: 0
132
+ ?: 0
482
133
  // println("currentBackStackEntry ${previousBook} ${previousChapter} || ${pair.first} ${pair.second}")
483
- if (previousBook == pair.first && previousChapter == pair.second) {
134
+ if (previousBook == pair.first && previousChapter == pair.second) {
484
- println("Popped")
135
+ println("Popped")
485
- navController.popBackStack()
136
+ navController.popBackStack()
486
- } else {
487
- navController.navigate(
488
- route = "/books/${pair.first}/chapters/${pair.second}?dir=right",
489
- )
490
- }
491
137
  } else {
492
- // println("navigated navigate")
493
138
  navController.navigate(
139
+ ChapterScreenProps(
140
+ bookIndex = pair.first,
494
- route = "/books/${pair.first}/chapters/${pair.second}?dir=right",
141
+ chapterIndex = pair.second,
142
+ dir = Dir.Right.name,
143
+ )
495
144
  )
496
145
  }
146
+ } else {
147
+ // println("navigated navigate")
148
+ navController.navigate(
149
+ ChapterScreenProps(
150
+ bookIndex = pair.first,
151
+ chapterIndex = pair.second,
152
+ dir = Dir.Right.name
153
+ )
154
+ )
497
155
  }
498
- },
499
- onHorizontalDrag = { change, da ->
500
- dragAmount = da
501
- change.consume()
502
156
  }
157
+ }, onHorizontalDrag = { change, da ->
158
+ dragAmount = da
159
+ change.consume()
503
- )
160
+ })
504
- }
505
- ) {
161
+ }) {
506
162
  stickyHeader {
507
163
  Row(
508
164
  modifier = Modifier
@@ -541,8 +197,7 @@ fun ChapterScreen(
541
197
  if (selectedVerses.isNotEmpty()) {
542
198
  IconButton(onClick = {
543
199
  scope.launch {
544
- convertVersesToSpeech(
200
+ convertVersesToSpeech(scope,
545
- scope,
546
201
  selectedVerses.sortedBy { it.verseIndex })
547
202
  }.invokeOnCompletion {
548
203
  selectedVerses = listOf()
@@ -559,7 +214,7 @@ fun ChapterScreen(
559
214
  }
560
215
  Box(modifier = Modifier.wrapContentSize(Alignment.TopEnd)) {
561
216
  IconButton(onClick = {
562
- showSheet()
217
+ state.showSheet()
563
218
  }) {
564
219
  Icon(Icons.Outlined.MoreVert, "More")
565
220
  }
@@ -572,12 +227,11 @@ fun ChapterScreen(
572
227
  DisableSelection {
573
228
  Text(
574
229
  modifier = Modifier.padding(
575
- top = if (v.verseIndex != 0) 12.dp else 0.dp,
230
+ top = if (v.verseIndex != 0) 12.dp else 0.dp, bottom = 12.dp
576
- bottom = 12.dp
577
231
  ),
578
232
  style = TextStyle(
579
233
  fontFamily = fontFamily,
580
- fontSize = (16 + fontSizeDelta).sp,
234
+ fontSize = (16 + state.fontSizeDelta).sp,
581
235
  fontWeight = FontWeight.W700,
582
236
  color = Color.Black,
583
237
  ),
@@ -588,41 +242,27 @@ fun ChapterScreen(
588
242
  val isSelected = selectedVerses.contains(v);
589
243
  val background =
590
244
  if (isSelected) Color(0xFFEEEEEE) else MaterialTheme.colorScheme.background
591
- Text(
592
- modifier = Modifier
245
+ Text(modifier = Modifier
593
- .padding(bottom = 16.dp)
246
+ .clickable {
594
- .onGloballyPositioned { coordinates ->
595
- val boundsInWindow = coordinates.boundsInWindow()
596
- selectedVerseBounds = coordinates.boundsInWindow()
247
+ selectedVerses = if (selectedVerses.contains(v)) {
248
+ selectedVerses - v
249
+ } else {
597
- // println("boundsInWindow blx:" + boundsInWindow.bottomLeft.x.dp)
250
+ selectedVerses + v
598
- // println("boundsInWindow bly:" + boundsInWindow.bottomLeft.y.dp)
599
- // println("boundsInWindow brx:"+boundsInWindow.bottomRight.x)
600
- // println("boundsInWindow bry:"+boundsInWindow.bottomRight.y)
601
251
  }
602
- .pointerInput(Unit) {
603
- detectTapGestures(
604
- onTap = {
605
- selectedVerses = if (selectedVerses.contains(v)) {
606
- selectedVerses - v
607
- } else {
608
- selectedVerses + v
609
- }
610
- }
611
- )
612
- },
252
+ },
613
253
  style = TextStyle(
614
254
  background = background,
615
255
  fontFamily = fontFamily,
616
256
  color = Color.Black,
617
257
  fontWeight = boldWeight,
618
- fontSize = (16 + fontSizeDelta).sp,
258
+ fontSize = (16 + state.fontSizeDelta).sp,
619
- lineHeight = (22 + fontSizeDelta).sp,
259
+ lineHeight = (22 + state.fontSizeDelta).sp,
620
260
  letterSpacing = 0.sp,
621
261
  ),
622
262
  text = buildAnnotatedString {
623
263
  withStyle(
624
264
  style = SpanStyle(
625
- fontSize = (13 + fontSizeDelta).sp,
265
+ fontSize = (13 + state.fontSizeDelta).sp,
626
266
  color = Color(0xFF9A1111),
627
267
  fontWeight = FontWeight.W700,
628
268
  )
@@ -630,8 +270,7 @@ fun ChapterScreen(
630
270
  append("${v.verseIndex + 1} ")
631
271
  }
632
272
  append(v.text)
633
- }
273
+ })
634
- )
635
274
  }
636
275
  }
637
276
  }
app/src/main/java/dev/pyrossh/onlyBible/Drawer.kt CHANGED
@@ -1,13 +1,7 @@
1
1
  package dev.pyrossh.onlyBible
2
2
 
3
3
  import Verse
4
- import android.annotation.SuppressLint
5
- import android.util.Log
6
- import androidx.compose.foundation.background
7
- import androidx.compose.foundation.gestures.detectTapGestures
8
- import androidx.compose.foundation.interaction.MutableInteractionSource
9
4
  import androidx.compose.foundation.layout.Arrangement
10
- import androidx.compose.foundation.layout.BoxWithConstraints
11
5
  import androidx.compose.foundation.layout.Column
12
6
  import androidx.compose.foundation.layout.PaddingValues
13
7
  import androidx.compose.foundation.layout.Row
@@ -15,6 +9,9 @@ import androidx.compose.foundation.layout.fillMaxSize
15
9
  import androidx.compose.foundation.layout.fillMaxWidth
16
10
  import androidx.compose.foundation.layout.padding
17
11
  import androidx.compose.foundation.lazy.grid.GridCells
12
+ import androidx.compose.foundation.lazy.grid.GridItemSpan
13
+ import androidx.compose.foundation.lazy.grid.LazyGridItemScope
14
+ import androidx.compose.foundation.lazy.grid.LazyGridScope
18
15
  import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
19
16
  import androidx.compose.foundation.shape.RoundedCornerShape
20
17
  import androidx.compose.material.icons.Icons
@@ -22,8 +19,6 @@ import androidx.compose.material.icons.filled.Close
22
19
  import androidx.compose.material3.Button
23
20
  import androidx.compose.material3.ButtonDefaults
24
21
  import androidx.compose.material3.DrawerValue
25
- import androidx.compose.material3.DropdownMenu
26
- import androidx.compose.material3.DropdownMenuItem
27
22
  import androidx.compose.material3.Icon
28
23
  import androidx.compose.material3.IconButton
29
24
  import androidx.compose.material3.ModalDrawerSheet
@@ -34,19 +29,14 @@ import androidx.compose.runtime.Composable
34
29
  import androidx.compose.runtime.getValue
35
30
  import androidx.compose.runtime.mutableIntStateOf
36
31
  import androidx.compose.runtime.mutableStateOf
37
- import androidx.compose.runtime.remember
38
32
  import androidx.compose.runtime.rememberCoroutineScope
39
33
  import androidx.compose.runtime.saveable.rememberSaveable
40
34
  import androidx.compose.runtime.setValue
41
35
  import androidx.compose.ui.Alignment
42
36
  import androidx.compose.ui.Modifier
43
- import androidx.compose.ui.geometry.Offset
44
37
  import androidx.compose.ui.graphics.Color
45
- import androidx.compose.ui.input.pointer.pointerInput
46
- import androidx.compose.ui.platform.LocalDensity
47
38
  import androidx.compose.ui.text.TextStyle
48
39
  import androidx.compose.ui.text.font.FontWeight
49
- import androidx.compose.ui.unit.DpOffset
50
40
  import androidx.compose.ui.unit.dp
51
41
  import androidx.compose.ui.unit.sp
52
42
  import androidx.navigation.NavController
@@ -66,59 +56,10 @@ fun shortName(name: String): String {
66
56
  return "${name[0].uppercase()}${name.substring(1, 3).lowercase()}"
67
57
  }
68
58
 
69
- @SuppressLint("UnrememberedMutableInteractionSource")
70
- @Composable
71
- fun DropDownSample() {
59
+ fun LazyGridScope.header(
72
- var expanded by remember { mutableStateOf(false) }
60
+ content: @Composable LazyGridItemScope.() -> Unit
73
- var touchPoint: Offset by remember { mutableStateOf(Offset.Zero) }
74
- val density = LocalDensity.current
75
-
76
- BoxWithConstraints(
77
- Modifier
78
- .fillMaxSize()
79
- .background(Color.Cyan)
80
- .pointerInput(Unit) {
81
- detectTapGestures {
82
- Log.d("TAG", "onCreate: ${it}")
83
- touchPoint = it
84
- expanded = true
85
-
86
- }
87
-
88
- }
89
- ) {
61
+ ) {
90
- val (xDp, yDp) = with(density) {
91
- (touchPoint.x.toDp()) to (touchPoint.y.toDp())
92
- }
93
- DropdownMenu(
94
- expanded = expanded,
95
- offset = DpOffset(xDp, -maxHeight + yDp),
96
- onDismissRequest = {
97
- expanded = false
98
- }
99
- ) {
100
-
101
- DropdownMenuItem(
102
- onClick = {
103
- expanded = false
104
- },
105
- interactionSource = MutableInteractionSource(),
62
+ item(span = { GridItemSpan(this.maxLineSpan) }, content = content)
106
- text = {
107
- Text("Copy")
108
- }
109
- )
110
-
111
- DropdownMenuItem(
112
- onClick = {
113
- expanded = false
114
- },
115
- interactionSource = MutableInteractionSource(),
116
- text = {
117
- Text("Get Balance")
118
- }
119
- )
120
- }
121
- }
122
63
  }
123
64
 
124
65
  @Composable
@@ -157,80 +98,104 @@ fun Drawer(
157
98
  .fillMaxSize()
158
99
  .padding(horizontal = 16.dp),
159
100
  ) {
160
- Row(
161
- modifier = Modifier.fillMaxWidth(),
162
- horizontalArrangement = Arrangement.SpaceBetween,
163
- verticalAlignment = Alignment.CenterVertically,
164
- ) {
165
- Text(
166
- "Select a ${menuType.name.lowercase()}",
167
- fontSize = 20.sp,
168
- fontWeight = FontWeight.W500
101
+ if (menuType == MenuType.Bible) {
169
- )
170
- IconButton(onClick = {
171
- scope.launch {
172
- drawerState.close();
173
- }
174
- }) {
175
- Icon(Icons.Filled.Close, "Close")
176
- }
177
102
  }
103
+ if (menuType == MenuType.Book) {
178
- LazyVerticalGrid(
104
+ LazyVerticalGrid(
179
- modifier = Modifier.fillMaxSize(),
105
+ modifier = Modifier.fillMaxSize(),
180
- verticalArrangement = Arrangement.spacedBy(10.dp),
106
+ verticalArrangement = Arrangement.spacedBy(10.dp),
181
- horizontalArrangement = Arrangement.spacedBy(10.dp),
107
+ horizontalArrangement = Arrangement.spacedBy(10.dp),
182
- columns = GridCells.Fixed(
108
+ columns = GridCells.Fixed(5)
109
+ ) {
110
+ header {
111
+ Row(
112
+ modifier = Modifier.fillMaxWidth(),
113
+ horizontalArrangement = Arrangement.SpaceBetween,
114
+ verticalAlignment = Alignment.CenterVertically,
115
+ ) {
116
+ Text(
117
+ text = "Old Testament",
118
+ fontSize = 18.sp,
183
- count = when (menuType) {
119
+ fontWeight = FontWeight.W500
184
- MenuType.Bible -> 2
185
- MenuType.Book -> 4
186
- MenuType.Chapter -> 5
187
- }
188
- )
120
+ )
189
- ) {
190
- items(
191
- when (menuType) {
192
- MenuType.Bible -> 1
193
- MenuType.Book -> Verse.bookNames.size
194
- MenuType.Chapter -> Verse.chapterSizes[bookIndex]
195
- }
196
- ) { c ->
197
- Button(
198
- shape = RoundedCornerShape(2.dp),
199
- colors = ButtonDefaults.buttonColors(
200
- containerColor = Color(0xFFEAE9E9),
201
- contentColor = Color.Black,
202
- ),
203
- contentPadding = PaddingValues(4.dp),
204
- elevation = ButtonDefaults.elevatedButtonElevation(),
205
- onClick = {
121
+ IconButton(onClick = {
206
- scope.launch {
122
+ scope.launch {
207
- when (menuType) {
208
- MenuType.Bible -> ""
209
- MenuType.Book -> {
210
- bookIndex = c
211
- menuType = MenuType.Chapter
212
- }
213
- MenuType.Chapter -> {
214
- navController.navigate(route = "/books/${bookIndex}/chapters/${c}?dir=left")
215
123
  drawerState.close();
216
124
  }
125
+ }) {
126
+ Icon(Icons.Filled.Close, "Close")
217
127
  }
218
128
  }
129
+ }
130
+ items(39) { b ->
131
+ QuickButton(shortName(Verse.bookNames[b])) {
132
+ bookIndex = b
133
+ menuType = MenuType.Chapter
134
+ }
135
+ }
136
+ header {
137
+ Row(
138
+ modifier = Modifier.padding(top = 16.dp, bottom = 8.dp),
139
+ horizontalArrangement = Arrangement.SpaceBetween,
140
+ verticalAlignment = Alignment.CenterVertically,
219
- }) {
141
+ ) {
220
- Text(
142
+ Text(
221
- modifier = Modifier.padding(bottom = 4.dp),
143
+ text = "New Testament",
222
- style = TextStyle(
223
- // fontFamily = FontFamily.Monospace,
224
144
  fontSize = 18.sp,
225
- fontWeight = FontWeight.W500,
145
+ fontWeight = FontWeight.W500
226
- color = Color(0xFF8A4242)
227
- ),
146
+ )
147
+ }
148
+ }
228
- text = when (menuType) {
149
+ items(27) { i ->
229
- MenuType.Bible -> ""
150
+ val b = 39 + i
230
- MenuType.Book -> shortName(Verse.bookNames[c])
151
+ QuickButton(shortName(Verse.bookNames[b])) {
152
+ bookIndex = b
153
+ menuType = MenuType.Chapter
154
+ }
155
+ }
156
+ }
157
+ }
158
+
159
+ if (menuType == MenuType.Chapter) {
160
+ LazyVerticalGrid(
161
+ modifier = Modifier.fillMaxSize(),
162
+ verticalArrangement = Arrangement.spacedBy(10.dp),
163
+ horizontalArrangement = Arrangement.spacedBy(10.dp),
164
+ columns = GridCells.Fixed(5)
165
+ ) {
166
+ header {
167
+ Row(
168
+ modifier = Modifier.fillMaxWidth(),
169
+ horizontalArrangement = Arrangement.SpaceBetween,
170
+ verticalAlignment = Alignment.CenterVertically,
171
+ ) {
172
+ Text(
173
+ text = Verse.bookNames[bookIndex],
174
+ fontSize = 20.sp,
175
+ fontWeight = FontWeight.W500
176
+ )
177
+ IconButton(onClick = {
178
+ scope.launch {
179
+ drawerState.close();
180
+ }
181
+ }) {
231
- MenuType.Chapter -> "${c + 1}"
182
+ Icon(Icons.Filled.Close, "Close")
232
183
  }
184
+ }
185
+ }
186
+
187
+ items(Verse.chapterSizes[bookIndex]) { c ->
188
+ QuickButton("${c + 1}") {
189
+ scope.launch {
190
+ navController.navigate(
191
+ ChapterScreenProps(
192
+ bookIndex = bookIndex,
193
+ chapterIndex = c,
233
- )
194
+ )
195
+ )
196
+ drawerState.close();
197
+ }
198
+ }
234
199
  }
235
200
  }
236
201
  }
@@ -240,4 +205,28 @@ fun Drawer(
240
205
  ) {
241
206
  content(openDrawer)
242
207
  }
208
+ }
209
+
210
+ @Composable
211
+ fun QuickButton(text: String, onClick: () -> Unit) {
212
+ Button(
213
+ shape = RoundedCornerShape(2.dp),
214
+ colors = ButtonDefaults.buttonColors(
215
+ containerColor = Color(0xFFEAE9E9),
216
+ contentColor = Color.Black,
217
+ ),
218
+ contentPadding = PaddingValues(4.dp),
219
+ elevation = ButtonDefaults.elevatedButtonElevation(),
220
+ onClick = onClick
221
+ ) {
222
+ Text(
223
+ modifier = Modifier.padding(bottom = 4.dp),
224
+ style = TextStyle(
225
+ fontSize = 18.sp,
226
+ fontWeight = FontWeight.W500,
227
+ color = Color(0xFF8A4242)
228
+ ),
229
+ text = text,
230
+ )
231
+ }
243
232
  }
app/src/main/java/dev/pyrossh/onlyBible/MainActivity.kt CHANGED
@@ -1,20 +1,32 @@
1
1
  package dev.pyrossh.onlyBible
2
2
 
3
3
  import Verse
4
+ import android.content.Context
4
5
  import android.os.Bundle
5
6
  import androidx.activity.ComponentActivity
6
7
  import androidx.activity.compose.setContent
7
8
  import androidx.activity.enableEdgeToEdge
9
+ import androidx.compose.runtime.CompositionLocalProvider
8
10
  import dev.pyrossh.onlyBible.ui.theme.BibleAppTheme
9
11
 
10
12
  class MainActivity : ComponentActivity() {
13
+
11
14
  override fun onCreate(savedInstanceState: Bundle?) {
12
15
  super.onCreate(savedInstanceState)
13
16
  enableEdgeToEdge()
17
+ val prefs = applicationContext.getSharedPreferences("settings", Context.MODE_PRIVATE)
14
18
  val verses = Verse.parseFromBibleTxt("English", assets.open("English.txt").bufferedReader())
19
+ val state = State(prefs)
15
20
  setContent {
16
21
  BibleAppTheme {
22
+ CompositionLocalProvider(LocalState provides state) {
23
+ AppHost(
17
- AppHost(verses = verses)
24
+ verses = verses
25
+ )
26
+ if (state.showBottomSheet) {
27
+ TextSettingsBottomSheet()
28
+ }
29
+ }
18
30
  }
19
31
  }
20
32
  }
app/src/main/java/dev/pyrossh/onlyBible/Model.kt CHANGED
@@ -151,6 +151,7 @@ data class Verse(
151
151
  1,
152
152
  22
153
153
  )
154
+
154
155
  fun parseFromBibleTxt(name: String, buffer: BufferedReader): List<Verse> {
155
156
  Log.i("loading", "parsing bible $name")
156
157
  return buffer.readLines().filter { it.isNotEmpty() }.map {
@@ -187,9 +188,9 @@ data class Verse(
187
188
  return Pair(book, chapter - 1)
188
189
  }
189
190
  if (book - 1 >= 0) {
190
- return Pair(book-1, chapterSizes[book-1]-1)
191
+ return Pair(book - 1, chapterSizes[book - 1] - 1)
191
192
  }
192
- return Pair(bookNames.size-1, chapterSizes[bookNames.size-1]-1)
193
+ return Pair(bookNames.size - 1, chapterSizes[bookNames.size - 1] - 1)
193
194
  }
194
195
  }
195
196
  }
app/src/main/java/dev/pyrossh/onlyBible/PreferencesManager.kt DELETED
@@ -1,44 +0,0 @@
1
- import android.content.Context
2
- import android.content.SharedPreferences
3
-
4
- enum class FontType {
5
- Sans,
6
- Serif,
7
- Mono
8
- }
9
-
10
- class PreferencesManager(context: Context) {
11
- private val sharedPreferences: SharedPreferences =
12
- context.getSharedPreferences("settings", Context.MODE_PRIVATE)
13
-
14
- fun getFontSize(): Int {
15
- return sharedPreferences.getInt("fontSizeDelta", 0)
16
- }
17
-
18
- fun setFontSize(v: Int) {
19
- val editor = sharedPreferences.edit()
20
- editor.putInt("fontSizeDelta", v)
21
- editor.apply()
22
- }
23
-
24
- fun getBold(): Boolean {
25
- return sharedPreferences.getBoolean("bold", false)
26
- }
27
-
28
- fun setBold(v: Boolean) {
29
- val editor = sharedPreferences.edit()
30
- editor.putBoolean("bold", v)
31
- editor.apply()
32
- }
33
-
34
- fun getFontType(): FontType {
35
- val ft = sharedPreferences.getString("fontType", FontType.Sans.name) ?: FontType.Sans.name
36
- return FontType.valueOf(ft)
37
- }
38
-
39
- fun setFontType(v: FontType) {
40
- val editor = sharedPreferences.edit()
41
- editor.putString("fontType", v.name)
42
- editor.apply()
43
- }
44
- }
app/src/main/java/dev/pyrossh/onlyBible/State.kt ADDED
@@ -0,0 +1,89 @@
1
+ package dev.pyrossh.onlyBible
2
+
3
+ import android.content.SharedPreferences
4
+ import androidx.compose.runtime.getValue
5
+ import androidx.compose.runtime.mutableIntStateOf
6
+ import androidx.compose.runtime.mutableStateOf
7
+ import androidx.compose.runtime.setValue
8
+ import androidx.compose.runtime.staticCompositionLocalOf
9
+ import androidx.compose.ui.text.font.FontFamily
10
+ import androidx.compose.ui.text.font.GenericFontFamily
11
+ import androidx.lifecycle.ViewModel
12
+
13
+ val LocalState = staticCompositionLocalOf<State?> { null }
14
+
15
+ enum class FontType {
16
+ Sans,
17
+ Serif,
18
+ Mono;
19
+
20
+ fun family(): GenericFontFamily {
21
+ return when (this) {
22
+ Sans -> FontFamily.SansSerif
23
+ Serif -> FontFamily.Serif
24
+ Mono -> FontFamily.Monospace
25
+ }
26
+ }
27
+ }
28
+
29
+ class State(p: SharedPreferences) : ViewModel() {
30
+ private val prefs: SharedPreferences = p
31
+ var showBottomSheet by mutableStateOf(false)
32
+ var fontType by mutableStateOf(
33
+ FontType.valueOf(
34
+ prefs.getString("fontType", FontType.Sans.name) ?: FontType.Sans.name
35
+ )
36
+ )
37
+ var fontSizeDelta by mutableIntStateOf(prefs.getInt("fontSizeDelta", 0))
38
+ var boldEnabled by mutableStateOf(prefs.getBoolean("bold", false))
39
+
40
+ fun getBookIndex(): Int {
41
+ return prefs.getInt("bookIndex", 0)
42
+ }
43
+
44
+ fun setBookIndex(v: Int) {
45
+ val editor = prefs.edit()
46
+ editor.putInt("bookIndex", v)
47
+ editor.apply()
48
+ }
49
+
50
+ fun getChapterIndex(): Int {
51
+ return prefs.getInt("chapterIndex", 0)
52
+ }
53
+
54
+ fun setChapterIndex(v: Int) {
55
+ val editor = prefs.edit()
56
+ editor.putInt("chapterIndex", v)
57
+ editor.apply()
58
+ }
59
+
60
+ fun showSheet() {
61
+ showBottomSheet = true
62
+ }
63
+
64
+ fun closeSheet() {
65
+ showBottomSheet = false
66
+ }
67
+
68
+ fun updateFontSize(v: Int) {
69
+ fontSizeDelta = v
70
+ val editor = prefs.edit()
71
+ editor.putInt("fontSizeDelta", v)
72
+ editor.apply()
73
+ }
74
+
75
+ fun updateBoldEnabled(v: Boolean) {
76
+ boldEnabled = v
77
+ val editor = prefs.edit()
78
+ editor.putBoolean("bold", v)
79
+ editor.apply()
80
+ }
81
+
82
+ fun updateFontType(v: FontType) {
83
+ fontType = v
84
+ val editor = prefs.edit()
85
+ editor.putString("fontType", v.name)
86
+ editor.apply()
87
+ }
88
+
89
+ }
app/src/main/java/dev/pyrossh/onlyBible/TextSettings.kt ADDED
@@ -0,0 +1,283 @@
1
+ package dev.pyrossh.onlyBible
2
+
3
+ import androidx.compose.foundation.BorderStroke
4
+ import androidx.compose.foundation.background
5
+ import androidx.compose.foundation.layout.Arrangement
6
+ import androidx.compose.foundation.layout.Column
7
+ import androidx.compose.foundation.layout.Row
8
+ import androidx.compose.foundation.layout.fillMaxSize
9
+ import androidx.compose.foundation.layout.fillMaxWidth
10
+ import androidx.compose.foundation.layout.height
11
+ import androidx.compose.foundation.layout.padding
12
+ import androidx.compose.foundation.layout.size
13
+ import androidx.compose.foundation.shape.RoundedCornerShape
14
+ import androidx.compose.material.icons.Icons
15
+ import androidx.compose.material.icons.filled.Close
16
+ import androidx.compose.material.icons.filled.FormatBold
17
+ import androidx.compose.material.icons.filled.FormatLineSpacing
18
+ import androidx.compose.material.icons.filled.FormatSize
19
+ import androidx.compose.material3.ExperimentalMaterial3Api
20
+ import androidx.compose.material3.HorizontalDivider
21
+ import androidx.compose.material3.Icon
22
+ import androidx.compose.material3.IconButton
23
+ import androidx.compose.material3.ModalBottomSheet
24
+ import androidx.compose.material3.Surface
25
+ import androidx.compose.material3.Text
26
+ import androidx.compose.material3.rememberModalBottomSheetState
27
+ import androidx.compose.runtime.Composable
28
+ import androidx.compose.runtime.rememberCoroutineScope
29
+ import androidx.compose.ui.Alignment
30
+ import androidx.compose.ui.Modifier
31
+ import androidx.compose.ui.graphics.Color
32
+ import androidx.compose.ui.res.painterResource
33
+ import androidx.compose.ui.text.TextStyle
34
+ import androidx.compose.ui.text.font.FontWeight
35
+ import androidx.compose.ui.unit.dp
36
+ import androidx.compose.ui.unit.sp
37
+ import kotlinx.coroutines.launch
38
+
39
+ @Composable
40
+ @OptIn(ExperimentalMaterial3Api::class)
41
+ fun TextSettingsBottomSheet() {
42
+ val scope = rememberCoroutineScope()
43
+ val sheetState = rememberModalBottomSheetState()
44
+ val state = LocalState.current!!
45
+ return ModalBottomSheet(
46
+ onDismissRequest = {
47
+ scope.launch {
48
+ state.closeSheet()
49
+ }
50
+ }, sheetState = sheetState
51
+ ) {
52
+ Column(
53
+ modifier = Modifier
54
+ .fillMaxSize()
55
+ .padding(horizontal = 16.dp),
56
+ ) {
57
+ Row(
58
+ modifier = Modifier.fillMaxWidth(),
59
+ horizontalArrangement = Arrangement.SpaceBetween,
60
+ verticalAlignment = Alignment.CenterVertically,
61
+ ) {
62
+ Text(
63
+ text = "Text Settings", fontSize = 20.sp, fontWeight = FontWeight.W500
64
+ )
65
+ Row(horizontalArrangement = Arrangement.End) {
66
+ IconButton(onClick = {
67
+ scope.launch {
68
+ state.closeSheet()
69
+ }
70
+ }) {
71
+ Icon(Icons.Filled.Close, "Close")
72
+ }
73
+ }
74
+ }
75
+ HorizontalDivider()
76
+ Row(
77
+ modifier = Modifier
78
+ .fillMaxWidth()
79
+ .padding(top = 16.dp),
80
+ horizontalArrangement = Arrangement.SpaceBetween,
81
+ verticalAlignment = Alignment.CenterVertically,
82
+ ) {
83
+ Surface(shape = RoundedCornerShape(8.dp),
84
+ modifier = Modifier
85
+ .fillMaxWidth()
86
+ .height(60.dp)
87
+ .padding(end = 16.dp)
88
+ .weight(1f),
89
+ onClick = {
90
+ state.updateFontSize(state.fontSizeDelta - 1)
91
+ }) {
92
+ Column(
93
+ modifier = Modifier.background(Color(0xFFFAFAFA)),
94
+ verticalArrangement = Arrangement.Center,
95
+ horizontalAlignment = Alignment.CenterHorizontally
96
+ ) {
97
+ Icon(
98
+ imageVector = Icons.Filled.FormatSize,
99
+ contentDescription = "Bold",
100
+ modifier = Modifier.size(14.dp),
101
+ )
102
+ }
103
+ }
104
+ Surface(shape = RoundedCornerShape(8.dp),
105
+ modifier = Modifier
106
+ .fillMaxWidth()
107
+ .height(60.dp)
108
+ .padding(end = 16.dp)
109
+ .weight(1f),
110
+ onClick = {
111
+ state.updateFontSize(state.fontSizeDelta + 1)
112
+ }) {
113
+ Column(
114
+ modifier = Modifier.background(Color(0xFFFAFAFA)),
115
+ verticalArrangement = Arrangement.Center,
116
+ horizontalAlignment = Alignment.CenterHorizontally
117
+ ) {
118
+ Icon(
119
+ imageVector = Icons.Filled.FormatSize,
120
+ contentDescription = "Bold"
121
+ )
122
+ }
123
+ }
124
+ Surface(shape = RoundedCornerShape(8.dp),
125
+ modifier = Modifier
126
+ .fillMaxWidth()
127
+ .height(60.dp)
128
+ .padding(end = 16.dp)
129
+ .weight(1f),
130
+ onClick = {
131
+ state.updateBoldEnabled(!state.boldEnabled)
132
+ }) {
133
+ Column(
134
+ modifier = Modifier.background(Color(0xFFFAFAFA)),
135
+ verticalArrangement = Arrangement.Center,
136
+ horizontalAlignment = Alignment.CenterHorizontally
137
+ ) {
138
+ Icon(
139
+ imageVector = Icons.Filled.FormatBold,
140
+ contentDescription = "Bold"
141
+ )
142
+ }
143
+ }
144
+ Surface(shape = RoundedCornerShape(8.dp),
145
+ modifier = Modifier
146
+ .fillMaxWidth()
147
+ .height(60.dp)
148
+ .padding(end = 16.dp)
149
+ .weight(1f),
150
+ onClick = {}) {
151
+ Column(
152
+ modifier = Modifier.background(Color(0xFFFAFAFA)),
153
+ verticalArrangement = Arrangement.Center,
154
+ horizontalAlignment = Alignment.CenterHorizontally
155
+ ) {
156
+ Icon(
157
+ imageVector = Icons.Filled.FormatLineSpacing,
158
+ contentDescription = "Line Spacing"
159
+ )
160
+ }
161
+ }
162
+ }
163
+ Row(
164
+ modifier = Modifier
165
+ .fillMaxWidth()
166
+ .padding(top = 16.dp),
167
+ horizontalArrangement = Arrangement.SpaceBetween,
168
+ verticalAlignment = Alignment.CenterVertically,
169
+ ) {
170
+ FontType.entries.map {
171
+ Surface(shape = RoundedCornerShape(8.dp),
172
+ border = if (state.fontType == it) BorderStroke(
173
+ 2.dp, Color(0xFF72abbf)
174
+ ) else null,
175
+ modifier = Modifier
176
+ .fillMaxWidth()
177
+ .height(60.dp)
178
+ .padding(end = 16.dp)
179
+ .weight(1f),
180
+ onClick = {
181
+ state.updateFontType(it)
182
+ }) {
183
+ Column(
184
+ modifier = Modifier.background(Color(0xFFFAFAFA)),
185
+ verticalArrangement = Arrangement.Center,
186
+ horizontalAlignment = Alignment.CenterHorizontally
187
+ ) {
188
+ Text(
189
+ text = it.name, style = TextStyle(
190
+ fontFamily = it.family(),
191
+ fontSize = 18.sp,
192
+ fontWeight = FontWeight.Medium,
193
+ )
194
+ )
195
+ }
196
+ }
197
+ }
198
+ }
199
+ Row(
200
+ modifier = Modifier
201
+ .fillMaxWidth()
202
+ .padding(top = 16.dp),
203
+ horizontalArrangement = Arrangement.SpaceBetween,
204
+ verticalAlignment = Alignment.CenterVertically,
205
+ ) {
206
+ // #72abbf on active
207
+ // #ebe0c7 on yellow
208
+ // #424547 on dark
209
+ Surface(shape = RoundedCornerShape(8.dp),
210
+ border = BorderStroke(
211
+ 2.dp, Color(0xFF72abbf)
212
+ ),
213
+ modifier = Modifier
214
+ .fillMaxWidth()
215
+ .height(80.dp)
216
+ .padding(end = 16.dp)
217
+ .weight(1f),
218
+ onClick = {}) {
219
+ Icon(
220
+ painter = painterResource(id = R.drawable.text_theme),
221
+ contentDescription = "Light",
222
+ tint = Color(0xFF424547),
223
+ modifier = Modifier
224
+ .background(Color.White)
225
+ .padding(8.dp)
226
+ )
227
+ }
228
+ Surface(shape = RoundedCornerShape(8.dp),
229
+ modifier = Modifier
230
+ .fillMaxWidth()
231
+ .height(80.dp)
232
+ .padding(end = 16.dp)
233
+ .weight(1f),
234
+ onClick = {}) {
235
+ Icon(
236
+ painter = painterResource(id = R.drawable.text_theme),
237
+ contentDescription = "Warm",
238
+ tint = Color(0xFF424547),
239
+ modifier = Modifier
240
+ .background(Color(0xFFe5e0d1))
241
+ .padding(8.dp)
242
+ )
243
+ }
244
+ Surface(shape = RoundedCornerShape(8.dp),
245
+ modifier = Modifier
246
+ .fillMaxWidth()
247
+ .height(80.dp)
248
+ .padding(end = 16.dp)
249
+ .weight(1f),
250
+ onClick = {}) {
251
+ Icon(
252
+ painter = painterResource(id = R.drawable.text_theme),
253
+ contentDescription = "Dark",
254
+ tint = Color(0xFFd3d7da),
255
+ modifier = Modifier
256
+ .background(Color(0xFF2c2e30))
257
+ .padding(8.dp)
258
+ )
259
+ }
260
+ Surface(shape = RoundedCornerShape(8.dp),
261
+ modifier = Modifier
262
+ .fillMaxWidth()
263
+ .height(80.dp)
264
+ .padding(end = 16.dp)
265
+ .weight(1f),
266
+ onClick = {}) {
267
+ Column(
268
+ modifier = Modifier.background(Color(0xFFFAFAFA)),
269
+ verticalArrangement = Arrangement.Center,
270
+ horizontalAlignment = Alignment.CenterHorizontally
271
+ ) {
272
+ Text(
273
+ text = "Auto", style = TextStyle(
274
+ fontSize = 18.sp,
275
+ fontWeight = FontWeight.Medium,
276
+ )
277
+ )
278
+ }
279
+ }
280
+ }
281
+ }
282
+ }
283
+ }
build.gradle.kts CHANGED
@@ -4,6 +4,8 @@ plugins {
4
4
  alias(libs.plugins.compose.compiler) apply false
5
5
  alias(libs.plugins.jetbrains.kotlin.android) apply false
6
6
  alias(libs.plugins.secrets.gradle.plugin) apply false
7
+ alias(libs.plugins.kotlinx.serializer) apply false
8
+ alias(libs.plugins.kotlin.parcelize) apply false
7
9
  }
8
10
 
9
11
  buildscript {
gradle/libs.versions.toml CHANGED
@@ -15,7 +15,7 @@ lifecycleRuntimeKtx = "2.8.1"
15
15
  activityCompose = "1.9.0"
16
16
  composeBom = "2024.05.00"
17
17
  materialIconsExtended = "1.6.8"
18
- navigationFragmentKtx = "2.7.7"
18
+ navigationFragmentKtx = "2.8.0-beta03"
19
19
  secretsGradlePlugin = "2.0.1"
20
20
  uiTextGoogleFonts = "1.6.7"
21
21
  uiUtilAndroid = "1.6.7"
@@ -53,5 +53,6 @@ secrets-gradle-plugin = { module = "com.google.android.libraries.mapsplatform.se
53
53
  android-application = { id = "com.android.application", version.ref = "agp" }
54
54
  compose-compiler = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
55
55
  jetbrains-kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" }
56
+ kotlinx-serializer = { id = "org.jetbrains.kotlin.plugin.serialization", version.ref = "kotlin"}
57
+ kotlin-parcelize = { id = "org.jetbrains.kotlin.plugin.parcelize", version.ref = "kotlin" }
56
58
  secrets-gradle-plugin = { id = "com.google.android.libraries.mapsplatform.secrets-gradle-plugin", version.ref = "secretsGradlePlugin" }
57
-