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


689bcb86 Peter John

1 year ago
fix actions
app/src/main/java/dev/pyrossh/onlyBible/AppTheme.kt CHANGED
@@ -40,8 +40,7 @@ fun getColorScheme(context: Context, themeType: ThemeType, darkTheme: Boolean):
40
40
  themeType == ThemeType.Light || (themeType == ThemeType.Auto && !darkTheme) ->
41
41
  dynamicLightColorScheme(context).copy(
42
42
  outline = Color.LightGray,
43
-
44
- )
43
+ )
45
44
 
46
45
  else ->
47
46
  dynamicDarkColorScheme(context).copy(
app/src/main/java/dev/pyrossh/onlyBible/AppViewModel.kt CHANGED
@@ -19,14 +19,12 @@ import com.microsoft.cognitiveservices.speech.SpeechConfig
19
19
  import com.microsoft.cognitiveservices.speech.SpeechSynthesizer
20
20
  import kotlinx.coroutines.CoroutineScope
21
21
  import kotlinx.coroutines.Dispatchers
22
- import kotlinx.coroutines.delay
23
22
  import kotlinx.coroutines.flow.collectLatest
24
23
  import kotlinx.coroutines.flow.distinctUntilChanged
25
24
  import kotlinx.coroutines.flow.map
26
25
  import kotlinx.coroutines.launch
27
26
  import kotlinx.coroutines.withContext
28
27
  import java.io.IOException
29
- import java.util.concurrent.Future
30
28
 
31
29
  internal val Context.dataStore by preferencesDataStore(name = "onlyBible")
32
30
 
@@ -170,32 +168,28 @@ val speechService = SpeechSynthesizer(
170
168
  )
171
169
  )
172
170
 
173
- suspend fun <T> Future<T>.wait(timeoutMs: Int = 30000): T? {
171
+ //suspend fun <T> Future<T>.wait(timeoutMs: Int = 30000): T? {
174
- val start = System.currentTimeMillis()
172
+ // val start = System.currentTimeMillis()
175
- while (!isDone) {
173
+ // while (!isDone) {
176
- if (System.currentTimeMillis() - start > timeoutMs)
174
+ // if (System.currentTimeMillis() - start > timeoutMs)
177
- return null
175
+ // return null
178
- delay(500)
176
+ // delay(500)
179
- }
177
+ // }
180
- return withContext(Dispatchers.IO) {
178
+ // return withContext(Dispatchers.IO) {
181
- get()
179
+ // get()
180
+ // }
182
- }
181
+ //}
183
- }
184
182
 
185
- suspend fun convertVersesToSpeech(scope: CoroutineScope, verses: List<Verse>) {
186
- withContext(Dispatchers.IO) {
183
+ fun convertVerseToSpeech(v: Verse) {
187
- for (v in verses) {
188
- speechService.SpeakSsmlAsync(
184
+ speechService.SpeakSsml(
189
- """
185
+ """
190
- <speak version="1.0" xmlns="http://www.w3.org/2001/10/synthesis" xml:lang="en-US">
186
+ <speak version="1.0" xmlns="http://www.w3.org/2001/10/synthesis" xml:lang="en-US">
191
- <voice name="en-US-AvaMultilingualNeural">
187
+ <voice name="en-US-AvaMultilingualNeural">
192
- ${v.text}
188
+ ${v.text}
193
- </voice>
189
+ </voice>
194
- </speak>
190
+ </speak>
195
- """.trimIndent()
191
+ """.trimIndent()
196
- )
192
+ )
197
- }
198
- }
199
193
  }
200
194
 
201
195
  fun stopVerses(scope: CoroutineScope, verses: List<Verse>) {
@@ -203,9 +197,8 @@ fun stopVerses(scope: CoroutineScope, verses: List<Verse>) {
203
197
  }
204
198
 
205
199
  fun shareVerses(context: Context, verses: List<Verse>) {
206
- val items = verses.sortedBy { it.verseIndex }
207
200
  val versesThrough =
208
- if (items.size >= 3) "${items.first().verseIndex + 1}-${items.last().verseIndex + 1}" else items.map { it.verseIndex + 1 }
201
+ if (verses.size >= 3) "${verses.first().verseIndex + 1}-${verses.last().verseIndex + 1}" else verses.map { it.verseIndex + 1 }
209
202
  .joinToString(",");
210
203
  val title = "${verses[0].bookName} ${verses[0].chapterIndex + 1}:${versesThrough}"
211
204
  val text = verses.joinToString("\n") {
app/src/main/java/dev/pyrossh/onlyBible/ChapterScreen.kt CHANGED
@@ -13,21 +13,29 @@ import androidx.compose.foundation.gestures.detectHorizontalDragGestures
13
13
  import androidx.compose.foundation.interaction.MutableInteractionSource
14
14
  import androidx.compose.foundation.isSystemInDarkTheme
15
15
  import androidx.compose.foundation.layout.Arrangement
16
+ import androidx.compose.foundation.layout.PaddingValues
16
17
  import androidx.compose.foundation.layout.Row
17
18
  import androidx.compose.foundation.layout.fillMaxSize
18
19
  import androidx.compose.foundation.layout.fillMaxWidth
19
20
  import androidx.compose.foundation.layout.height
20
21
  import androidx.compose.foundation.layout.padding
22
+ import androidx.compose.foundation.layout.size
21
23
  import androidx.compose.foundation.lazy.LazyColumn
22
24
  import androidx.compose.foundation.lazy.items
23
25
  import androidx.compose.material.icons.Icons
24
- import androidx.compose.material.icons.outlined.FaceRetouchingNatural
26
+ import androidx.compose.material.icons.outlined.Cancel
25
27
  import androidx.compose.material.icons.outlined.MoreVert
28
+ import androidx.compose.material.icons.outlined.PauseCircle
29
+ import androidx.compose.material.icons.outlined.PlayCircle
26
30
  import androidx.compose.material.icons.outlined.Share
31
+ import androidx.compose.material3.BottomAppBar
27
32
  import androidx.compose.material3.ExperimentalMaterial3Api
33
+ import androidx.compose.material3.HorizontalDivider
28
34
  import androidx.compose.material3.Icon
35
+ import androidx.compose.material3.IconButton
29
36
  import androidx.compose.material3.MaterialTheme
30
37
  import androidx.compose.material3.Scaffold
38
+ import androidx.compose.material3.Surface
31
39
  import androidx.compose.material3.Text
32
40
  import androidx.compose.material3.TextButton
33
41
  import androidx.compose.material3.TopAppBar
@@ -53,6 +61,7 @@ import androidx.compose.ui.text.withStyle
53
61
  import androidx.compose.ui.unit.dp
54
62
  import androidx.compose.ui.unit.sp
55
63
  import androidx.navigation.NavController
64
+ import kotlinx.coroutines.Dispatchers
56
65
  import kotlinx.coroutines.Job
57
66
  import kotlinx.coroutines.launch
58
67
  import kotlinx.parcelize.Parcelize
@@ -102,6 +111,12 @@ fun ChapterScreen(
102
111
  var selectedVerses by rememberSaveable {
103
112
  mutableStateOf(listOf<Verse>())
104
113
  }
114
+ var isPlaying by rememberSaveable {
115
+ mutableStateOf(false)
116
+ }
117
+ var playingJob by rememberSaveable {
118
+ mutableStateOf<Job?>(null)
119
+ }
105
120
  var dragAmount by remember {
106
121
  mutableFloatStateOf(0.0f)
107
122
  }
@@ -146,32 +161,6 @@ fun ChapterScreen(
146
161
  }
147
162
  },
148
163
  actions = {
149
- if (selectedVerses.isNotEmpty()) {
150
- TextButton(onClick = {
151
- scope.launch {
152
- convertVersesToSpeech(
153
- scope,
154
- selectedVerses.sortedBy { it.verseIndex },
155
- )
156
- }.invokeOnCompletion {
157
- selectedVerses = listOf()
158
- }
159
- }) {
160
- Icon(
161
- imageVector = Icons.Outlined.FaceRetouchingNatural,
162
- contentDescription = "Audio",
163
- )
164
- }
165
- TextButton(onClick = {
166
- shareVerses(context, selectedVerses)
167
- selectedVerses = listOf()
168
- }) {
169
- Icon(
170
- imageVector = Icons.Outlined.Share,
171
- contentDescription = "Share",
172
- )
173
- }
174
- }
175
164
  TextButton(onClick = { openDrawer(MenuType.Bible, bookIndex) }) {
176
165
  Text(
177
166
  text = model.bibleName.substring(0, 2).uppercase(),
@@ -195,10 +184,90 @@ fun ChapterScreen(
195
184
  },
196
185
  )
197
186
  },
198
- // bottomBar = {
187
+ bottomBar = {
199
- // if (selectedVerses.isNotEmpty()) {
188
+ if (selectedVerses.isNotEmpty()) {
189
+ BottomAppBar(
190
+ containerColor = Color.Transparent,
191
+ contentPadding = PaddingValues(0.dp),
192
+ modifier = Modifier
193
+ .height(80.dp),
194
+ actions = {
195
+ Surface(
196
+ color = Color.Transparent,
197
+ contentColor = MaterialTheme.colorScheme.primary,
198
+ ) {
199
+ HorizontalDivider(
200
+ color = MaterialTheme.colorScheme.outline,
201
+ modifier = Modifier
202
+ .height(1.dp)
203
+ .padding(bottom = 12.dp)
204
+ .fillMaxWidth()
205
+ )
206
+ Row(
207
+ modifier = Modifier
208
+ .fillMaxSize(),
209
+ horizontalArrangement = Arrangement.SpaceAround,
210
+ verticalAlignment = Alignment.CenterVertically,
211
+ ) {
212
+ IconButton(onClick = {
213
+ selectedVerses = listOf()
214
+ }) {
215
+ Icon(
216
+ modifier = Modifier.size(36.dp),
217
+ imageVector = Icons.Outlined.Cancel,
218
+ contentDescription = "Clear",
219
+ )
220
+ }
221
+ IconButton(onClick = {
222
+ if (isPlaying) {
223
+ isPlaying = false
224
+ playingJob?.cancel()
225
+ } else {
226
+ isPlaying = true
227
+ playingJob = scope.launch(Dispatchers.IO) {
228
+ for (v in selectedVerses.sortedBy { it.verseIndex }) {
229
+ if (playingJob?.isActive == true) {
230
+ convertVerseToSpeech(v)
231
+ }
232
+ }
233
+ }
234
+ playingJob?.invokeOnCompletion {
235
+ isPlaying = false
236
+ }
237
+ }
238
+ }) {
239
+ Icon(
240
+ modifier = Modifier.size(36.dp),
241
+ imageVector = if (isPlaying)
242
+ Icons.Outlined.PauseCircle
243
+ else
244
+ Icons.Outlined.PlayCircle,
245
+ contentDescription = "Audio",
246
+ )
247
+ }
248
+ // IconButton(onClick = {
249
+ // shareVerses(context, selectedVerses)
200
- // BottomAppBar(
250
+ // }) {
251
+ // Icon(
252
+ // modifier = Modifier.size(48.dp),
253
+ // imageVector = Icons.Outlined.Edit,
254
+ // contentDescription = "Highlight",
255
+ // )
201
- // actions = {
256
+ // }
257
+ IconButton(onClick = {
258
+ scope.launch(Dispatchers.IO) {
259
+ shareVerses(
260
+ context,
261
+ selectedVerses.sortedBy { it.verseIndex })
262
+ }
263
+ }) {
264
+ Icon(
265
+ modifier = Modifier.size(36.dp),
266
+ imageVector = Icons.Outlined.Share,
267
+ contentDescription = "Share",
268
+ )
269
+ }
270
+ }
202
271
  // IconButton(onClick = { /* do something */ }) {
203
272
  // Icon(
204
273
  // Icons.Filled.Circle,
@@ -231,10 +300,11 @@ fun ChapterScreen(
231
300
  // tint = Color.Magenta,
232
301
  // )
233
302
  // }
234
- // },
235
- // )
236
- // }
303
+ }
237
- // },
304
+ },
305
+ )
306
+ }
307
+ },
238
308
  ) { innerPadding ->
239
309
  LazyColumn(
240
310
  verticalArrangement = Arrangement.spacedBy(12.dp),