~repos /only-bible-app
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.
a9101f33
—
pyrossh 1 year ago
improve stuff
- app/src/main/AndroidManifest.xml +2 -2
- app/src/main/assets/bibles/en_kjv.txt +1 -1
- app/src/main/java/dev/pyrossh/onlyBible/AppHost.kt +34 -74
- app/src/main/java/dev/pyrossh/onlyBible/AppTheme.kt +9 -4
- app/src/main/java/dev/pyrossh/onlyBible/AppViewModel.kt +70 -73
- app/src/main/java/dev/pyrossh/onlyBible/ChapterScreen.kt +53 -26
- app/src/main/java/dev/pyrossh/onlyBible/MainActivity.kt +11 -4
- app/src/main/java/dev/pyrossh/onlyBible/composables/BibleSelector.kt +6 -7
- app/src/main/java/dev/pyrossh/onlyBible/composables/ChapterSelector.kt +11 -6
- app/src/main/java/dev/pyrossh/onlyBible/composables/EmbeddedSearchBar.kt +10 -3
- app/src/main/java/dev/pyrossh/onlyBible/composables/TextSettingsBottomSheet.kt +12 -1
- app/src/main/java/dev/pyrossh/onlyBible/composables/VerseHeading.kt +62 -0
- app/src/main/java/dev/pyrossh/onlyBible/composables/{VerseView.kt → VerseText.kt} +30 -49
- app/src/main/java/dev/pyrossh/onlyBible/domain/Verse.kt +0 -10
- app/src/main/res/drawable/ic_launcher_foreground.xml +4 -4
- app/src/main/res/values-night/colors.xml +0 -4
- app/src/main/res/values/colors.xml +0 -4
- app/src/main/res/values/themes.xml +0 -4
app/src/main/AndroidManifest.xml
CHANGED
|
@@ -15,12 +15,12 @@
|
|
|
15
15
|
android:icon="@mipmap/ic_launcher"
|
|
16
16
|
android:roundIcon="@mipmap/ic_launcher_round"
|
|
17
17
|
android:supportsRtl="true"
|
|
18
|
-
android:theme="@style/Theme.
|
|
18
|
+
android:theme="@style/Theme.AppCompat.DayNight.NoActionBar">
|
|
19
19
|
<activity
|
|
20
20
|
android:name=".MainActivity"
|
|
21
21
|
android:configChanges="uiMode"
|
|
22
22
|
android:exported="true"
|
|
23
|
-
android:theme="@style/Theme.
|
|
23
|
+
android:theme="@style/Theme.AppCompat.DayNight.NoActionBar">
|
|
24
24
|
<intent-filter>
|
|
25
25
|
<action android:name="android.intent.action.MAIN" />
|
|
26
26
|
<category android:name="android.intent.category.LAUNCHER" />
|
app/src/main/assets/bibles/en_kjv.txt
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
Genesis|0|0|0|The History of Creation|In the beginning God created the heaven and the earth.
|
|
1
|
+
Genesis|0|0|0|The History of Creation <br> (<a href="42:0:0">John 1:1–5</a>;<a href="57:10:1">Hebrews 11:1–3</a>)|In the beginning God created the heaven and the earth.
|
|
2
2
|
Genesis|0|0|1||And the earth was without form, and void; and darkness <em>was</em> upon the face of the deep. And the Spirit of God moved upon the face of the waters.
|
|
3
3
|
Genesis|0|0|2||And God said, Let there be light: and there was light.
|
|
4
4
|
Genesis|0|0|3||And God saw the light, that <em>it was</em> good: and God divided the light from the darkness.
|
app/src/main/java/dev/pyrossh/onlyBible/AppHost.kt
CHANGED
|
@@ -4,94 +4,54 @@ import androidx.compose.animation.EnterTransition
|
|
|
4
4
|
import androidx.compose.animation.ExitTransition
|
|
5
5
|
import androidx.compose.animation.core.tween
|
|
6
6
|
import androidx.compose.runtime.Composable
|
|
7
|
+
import androidx.lifecycle.viewmodel.compose.viewModel
|
|
7
8
|
import androidx.navigation.compose.NavHost
|
|
8
9
|
import androidx.navigation.compose.composable
|
|
9
10
|
import androidx.navigation.compose.rememberNavController
|
|
10
11
|
import androidx.navigation.toRoute
|
|
11
12
|
|
|
12
13
|
@Composable
|
|
13
|
-
fun AppHost(model: AppViewModel) {
|
|
14
|
+
fun AppHost(model: AppViewModel = viewModel()) {
|
|
14
15
|
val navController = rememberNavController()
|
|
15
16
|
val navigateToChapter = { props: ChapterScreenProps ->
|
|
16
17
|
model.resetScrollState()
|
|
17
18
|
navController.navigate(props)
|
|
18
19
|
}
|
|
19
|
-
val onSwipeLeft = {
|
|
20
|
-
|
|
20
|
+
if (!model.isLoading) {
|
|
21
|
-
|
|
21
|
+
NavHost(
|
|
22
|
-
ChapterScreenProps(
|
|
23
|
-
bookIndex = pair.first,
|
|
24
|
-
chapterIndex = pair.second,
|
|
25
|
-
)
|
|
26
|
-
)
|
|
27
|
-
}
|
|
28
|
-
val onSwipeRight = {
|
|
29
|
-
val pair = model.getBackwardPair()
|
|
30
|
-
if (navController.previousBackStackEntry != null) {
|
|
31
|
-
val previousBook =
|
|
32
|
-
navController.previousBackStackEntry?.arguments?.getInt("bookIndex")
|
|
33
|
-
?: 0
|
|
34
|
-
val previousChapter =
|
|
35
|
-
navController.previousBackStackEntry?.arguments?.getInt("chapterIndex")
|
|
36
|
-
?: 0
|
|
37
|
-
// println("currentBackStackEntry ${previousBook} ${previousChapter} || ${pair.first} ${pair.second}")
|
|
38
|
-
if (previousBook == pair.first && previousChapter == pair.second) {
|
|
39
|
-
model.resetScrollState()
|
|
40
|
-
|
|
22
|
+
navController = navController,
|
|
23
|
+
startDestination = ChapterScreenProps(model.bookIndex, model.chapterIndex)
|
|
41
|
-
|
|
24
|
+
) {
|
|
42
|
-
navigateToChapter(
|
|
43
|
-
|
|
25
|
+
composable<ChapterScreenProps>(
|
|
26
|
+
enterTransition = {
|
|
27
|
+
val props = this.targetState.toRoute<ChapterScreenProps>()
|
|
44
|
-
|
|
28
|
+
slideIntoContainer(
|
|
45
|
-
|
|
29
|
+
Dir.valueOf(props.dir).slideDirection(),
|
|
46
|
-
|
|
30
|
+
tween(400),
|
|
47
31
|
)
|
|
32
|
+
},
|
|
33
|
+
exitTransition = {
|
|
34
|
+
ExitTransition.None
|
|
35
|
+
},
|
|
36
|
+
popEnterTransition = {
|
|
37
|
+
EnterTransition.None
|
|
38
|
+
},
|
|
39
|
+
popExitTransition = {
|
|
40
|
+
val props = this.targetState.toRoute<ChapterScreenProps>()
|
|
41
|
+
slideOutOfContainer(
|
|
42
|
+
Dir.valueOf(props.dir).reverse().slideDirection(),
|
|
43
|
+
tween(400),
|
|
44
|
+
)
|
|
45
|
+
}
|
|
46
|
+
) {
|
|
47
|
+
val props = it.toRoute<ChapterScreenProps>()
|
|
48
|
+
ChapterScreen(
|
|
49
|
+
model = model,
|
|
50
|
+
bookIndex = props.bookIndex,
|
|
51
|
+
chapterIndex = props.chapterIndex,
|
|
52
|
+
navigateToChapter = navigateToChapter,
|
|
48
53
|
)
|
|
49
54
|
}
|
|
50
|
-
} else {
|
|
51
|
-
navigateToChapter(
|
|
52
|
-
ChapterScreenProps(
|
|
53
|
-
bookIndex = pair.first,
|
|
54
|
-
chapterIndex = pair.second,
|
|
55
|
-
dir = Dir.Right.name
|
|
56
|
-
)
|
|
57
|
-
)
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
NavHost(
|
|
61
|
-
navController = navController,
|
|
62
|
-
startDestination = ChapterScreenProps(0, 0)
|
|
63
|
-
) {
|
|
64
|
-
composable<ChapterScreenProps>(
|
|
65
|
-
enterTransition = {
|
|
66
|
-
val props = this.targetState.toRoute<ChapterScreenProps>()
|
|
67
|
-
slideIntoContainer(
|
|
68
|
-
Dir.valueOf(props.dir).slideDirection(),
|
|
69
|
-
tween(400),
|
|
70
|
-
)
|
|
71
|
-
},
|
|
72
|
-
exitTransition = {
|
|
73
|
-
ExitTransition.None
|
|
74
|
-
},
|
|
75
|
-
popEnterTransition = {
|
|
76
|
-
EnterTransition.None
|
|
77
|
-
},
|
|
78
|
-
popExitTransition = {
|
|
79
|
-
val props = this.targetState.toRoute<ChapterScreenProps>()
|
|
80
|
-
slideOutOfContainer(
|
|
81
|
-
Dir.valueOf(props.dir).reverse().slideDirection(),
|
|
82
|
-
tween(400),
|
|
83
|
-
)
|
|
84
|
-
}
|
|
85
|
-
) {
|
|
86
|
-
val props = it.toRoute<ChapterScreenProps>()
|
|
87
|
-
ChapterScreen(
|
|
88
|
-
model = model,
|
|
89
|
-
onSwipeLeft = onSwipeLeft,
|
|
90
|
-
onSwipeRight = onSwipeRight,
|
|
91
|
-
bookIndex = props.bookIndex,
|
|
92
|
-
chapterIndex = props.chapterIndex,
|
|
93
|
-
navigateToChapter = navigateToChapter,
|
|
94
|
-
)
|
|
95
55
|
}
|
|
96
56
|
}
|
|
97
57
|
}
|
app/src/main/java/dev/pyrossh/onlyBible/AppTheme.kt
CHANGED
|
@@ -11,9 +11,14 @@ import androidx.compose.ui.graphics.Color
|
|
|
11
11
|
import androidx.compose.ui.platform.LocalContext
|
|
12
12
|
import androidx.compose.ui.text.font.FontFamily
|
|
13
13
|
import androidx.compose.ui.text.font.GenericFontFamily
|
|
14
|
-
import androidx.lifecycle.viewmodel.compose.viewModel
|
|
15
14
|
import com.google.accompanist.systemuicontroller.rememberSystemUiController
|
|
16
15
|
|
|
16
|
+
enum class ThemeType {
|
|
17
|
+
Light,
|
|
18
|
+
Dark,
|
|
19
|
+
Auto;
|
|
20
|
+
}
|
|
21
|
+
|
|
17
22
|
enum class FontType {
|
|
18
23
|
Sans,
|
|
19
24
|
Serif,
|
|
@@ -46,12 +51,12 @@ fun isLightTheme(uiMode: Int, isSystemDark: Boolean): Boolean {
|
|
|
46
51
|
|
|
47
52
|
@Composable
|
|
48
53
|
fun AppTheme(
|
|
49
|
-
|
|
54
|
+
nightMode: Int,
|
|
50
55
|
content: @Composable() () -> Unit
|
|
51
56
|
) {
|
|
52
57
|
val context = LocalContext.current
|
|
53
58
|
val systemUiController = rememberSystemUiController()
|
|
54
|
-
val colorScheme = if (isLightTheme(
|
|
59
|
+
val colorScheme = if (isLightTheme(nightMode, isSystemInDarkTheme()))
|
|
55
60
|
dynamicLightColorScheme(context).copy(
|
|
56
61
|
onSurface = Color.Black,
|
|
57
62
|
outline = Color.LightGray,
|
|
@@ -62,7 +67,7 @@ fun AppTheme(
|
|
|
62
67
|
surface = Color(0xFF090F12),
|
|
63
68
|
outline = Color(0xAA5D4979),
|
|
64
69
|
)
|
|
65
|
-
LaunchedEffect(key1 =
|
|
70
|
+
LaunchedEffect(key1 = nightMode) {
|
|
66
71
|
systemUiController.setSystemBarsColor(
|
|
67
72
|
color = colorScheme.background
|
|
68
73
|
)
|
app/src/main/java/dev/pyrossh/onlyBible/AppViewModel.kt
CHANGED
|
@@ -6,6 +6,7 @@ import android.content.Context
|
|
|
6
6
|
import android.content.Context.MODE_PRIVATE
|
|
7
7
|
import android.content.Context.UI_MODE_SERVICE
|
|
8
8
|
import android.content.Intent
|
|
9
|
+
import android.text.Html
|
|
9
10
|
import androidx.compose.foundation.lazy.LazyListState
|
|
10
11
|
import androidx.compose.runtime.getValue
|
|
11
12
|
import androidx.compose.runtime.mutableIntStateOf
|
|
@@ -17,6 +18,7 @@ import com.microsoft.cognitiveservices.speech.SpeechConfig
|
|
|
17
18
|
import com.microsoft.cognitiveservices.speech.SpeechSynthesisEventArgs
|
|
18
19
|
import com.microsoft.cognitiveservices.speech.SpeechSynthesizer
|
|
19
20
|
import dev.pyrossh.onlyBible.domain.BOOKS_COUNT
|
|
21
|
+
import dev.pyrossh.onlyBible.domain.Bible
|
|
20
22
|
import dev.pyrossh.onlyBible.domain.Verse
|
|
21
23
|
import dev.pyrossh.onlyBible.domain.bibles
|
|
22
24
|
import dev.pyrossh.onlyBible.domain.chapterSizes
|
|
@@ -36,45 +38,31 @@ import java.io.IOException
|
|
|
36
38
|
class AppViewModel(application: Application) : AndroidViewModel(application) {
|
|
37
39
|
private val context
|
|
38
40
|
get() = getApplication<Application>()
|
|
39
|
-
private val prefs
|
|
40
|
-
get() = context.getSharedPreferences("data", MODE_PRIVATE)
|
|
41
41
|
val speechService = SpeechSynthesizer(
|
|
42
42
|
SpeechConfig.fromSubscription(
|
|
43
43
|
BuildConfig.subscriptionKey,
|
|
44
44
|
"centralindia"
|
|
45
45
|
)
|
|
46
46
|
)
|
|
47
|
+
val started = { _: Any, _: SpeechSynthesisEventArgs ->
|
|
48
|
+
isPlaying = true
|
|
49
|
+
}
|
|
50
|
+
val completed = { _: Any, _: SpeechSynthesisEventArgs ->
|
|
51
|
+
isPlaying = false
|
|
52
|
+
}
|
|
47
53
|
|
|
48
54
|
init {
|
|
49
|
-
val started = { _: Any, _: SpeechSynthesisEventArgs ->
|
|
50
|
-
isPlaying = true
|
|
51
|
-
}
|
|
52
|
-
val completed = { _: Any, _: SpeechSynthesisEventArgs ->
|
|
53
|
-
isPlaying = false
|
|
54
|
-
}
|
|
55
55
|
speechService.SynthesisStarted.addEventListener(started)
|
|
56
56
|
speechService.SynthesisCompleted.addEventListener(completed)
|
|
57
|
-
loadData()
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
fun onSpeechStared(sender: Any, e: Any) {
|
|
61
|
-
viewModelScope.launch(Dispatchers.Main) {
|
|
62
|
-
isPlaying = true
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
fun onSpeechCompleted(sender: Any) {
|
|
67
|
-
viewModelScope.launch(Dispatchers.Main) {
|
|
68
|
-
isPlaying = false
|
|
69
|
-
}
|
|
70
57
|
}
|
|
71
58
|
|
|
72
59
|
override fun onCleared() {
|
|
73
60
|
super.onCleared()
|
|
74
|
-
|
|
61
|
+
speechService.SynthesisStarted.removeEventListener(started)
|
|
75
|
-
|
|
62
|
+
speechService.SynthesisCompleted.removeEventListener(completed)
|
|
76
63
|
}
|
|
77
64
|
|
|
65
|
+
var isLoading by mutableStateOf(true)
|
|
78
66
|
var isPlaying by mutableStateOf(false)
|
|
79
67
|
val verses = MutableStateFlow(listOf<Verse>())
|
|
80
68
|
val bookNames = MutableStateFlow(engTitles)
|
|
@@ -94,16 +82,11 @@ class AppViewModel(application: Application) : AndroidViewModel(application) {
|
|
|
94
82
|
0
|
|
95
83
|
)
|
|
96
84
|
val selectedVerses = MutableStateFlow(listOf<Verse>())
|
|
97
|
-
|
|
98
|
-
|
|
85
|
+
val isSearching = MutableStateFlow(false)
|
|
99
|
-
val isSearching = _isSearching.asStateFlow()
|
|
100
|
-
|
|
101
|
-
//second state the text typed by the user
|
|
102
|
-
|
|
86
|
+
val searchText = MutableStateFlow("")
|
|
103
|
-
val searchText = _searchText.asStateFlow()
|
|
104
87
|
|
|
105
88
|
@OptIn(FlowPreview::class)
|
|
106
|
-
val
|
|
89
|
+
val searchedVerses = searchText.asStateFlow()
|
|
107
90
|
.debounce(300)
|
|
108
91
|
.combine(verses.asStateFlow()) { text, verses ->
|
|
109
92
|
verses.filter { verse ->
|
|
@@ -121,19 +104,19 @@ class AppViewModel(application: Application) : AndroidViewModel(application) {
|
|
|
121
104
|
)
|
|
122
105
|
|
|
123
106
|
fun onSearchTextChange(text: String) {
|
|
124
|
-
|
|
107
|
+
searchText.value = text
|
|
125
108
|
}
|
|
126
109
|
|
|
127
110
|
fun onOpenSearch() {
|
|
128
|
-
|
|
111
|
+
isSearching.value = true
|
|
129
|
-
if (!
|
|
112
|
+
if (!isSearching.value) {
|
|
130
113
|
onSearchTextChange("")
|
|
131
114
|
}
|
|
132
115
|
}
|
|
133
116
|
|
|
134
117
|
fun onCloseSearch() {
|
|
135
|
-
|
|
118
|
+
isSearching.value = false
|
|
136
|
-
if (!
|
|
119
|
+
if (!isSearching.value) {
|
|
137
120
|
onSearchTextChange("")
|
|
138
121
|
}
|
|
139
122
|
}
|
|
@@ -160,16 +143,18 @@ class AppViewModel(application: Application) : AndroidViewModel(application) {
|
|
|
160
143
|
selectedVerses.value = listOf()
|
|
161
144
|
}
|
|
162
145
|
|
|
163
|
-
|
|
146
|
+
fun loadData(context: Context) {
|
|
164
147
|
viewModelScope.launch(Dispatchers.IO) {
|
|
148
|
+
viewModelScope.launch(Dispatchers.Main) {
|
|
149
|
+
isLoading = true
|
|
150
|
+
}
|
|
151
|
+
val prefs = context.getSharedPreferences("data", MODE_PRIVATE)
|
|
165
152
|
val bibleFileName = prefs.getString("bible", "en_kjv") ?: "en_kjv"
|
|
166
|
-
bible = bibles.find { it.filename() == bibleFileName } ?: bibles.first()
|
|
167
153
|
bookIndex = prefs.getInt("bookIndex", 0)
|
|
168
154
|
chapterIndex = prefs.getInt("chapterIndex", 0)
|
|
169
|
-
fontType =
|
|
170
|
-
|
|
155
|
+
fontType = FontType.valueOf(
|
|
171
|
-
|
|
156
|
+
prefs.getString("fontType", FontType.Sans.name) ?: FontType.Sans.name
|
|
172
|
-
|
|
157
|
+
)
|
|
173
158
|
fontSizeDelta = prefs.getInt("fontSizeDelta", 0)
|
|
174
159
|
fontBoldEnabled = prefs.getBoolean("fontBoldEnabled", false)
|
|
175
160
|
lineSpacingDelta = prefs.getInt("lineSpacingDelta", 0)
|
|
@@ -179,14 +164,18 @@ class AppViewModel(application: Application) : AndroidViewModel(application) {
|
|
|
179
164
|
prefs.getInt("scrollIndex", 0),
|
|
180
165
|
prefs.getInt("scrollOffset", 0)
|
|
181
166
|
)
|
|
167
|
+
val localBible = bibles.find { it.filename() == bibleFileName } ?: bibles.first()
|
|
182
|
-
loadBible()
|
|
168
|
+
loadBible(localBible, context)
|
|
169
|
+
viewModelScope.launch(Dispatchers.Main) {
|
|
170
|
+
isLoading = false
|
|
171
|
+
}
|
|
183
172
|
}
|
|
184
173
|
}
|
|
185
174
|
|
|
186
|
-
fun loadBible() {
|
|
175
|
+
fun loadBible(b: Bible, context: Context) {
|
|
187
176
|
try {
|
|
188
177
|
val buffer =
|
|
189
|
-
context.assets.open("bibles/${
|
|
178
|
+
context.assets.open("bibles/${b.filename()}.txt").bufferedReader()
|
|
190
179
|
val localVerses = buffer.readLines().filter { it.isNotEmpty() }.map {
|
|
191
180
|
val arr = it.split("|")
|
|
192
181
|
val bookName = arr[0]
|
|
@@ -205,6 +194,7 @@ class AppViewModel(application: Application) : AndroidViewModel(application) {
|
|
|
205
194
|
)
|
|
206
195
|
}
|
|
207
196
|
viewModelScope.launch(Dispatchers.Main) {
|
|
197
|
+
bible = b
|
|
208
198
|
verses.value = localVerses
|
|
209
199
|
bookNames.value = localVerses.distinctBy { it.bookName }.map { it.bookName }
|
|
210
200
|
}
|
|
@@ -213,8 +203,9 @@ class AppViewModel(application: Application) : AndroidViewModel(application) {
|
|
|
213
203
|
}
|
|
214
204
|
}
|
|
215
205
|
|
|
216
|
-
fun saveData() {
|
|
206
|
+
fun saveData(context: Context) {
|
|
217
207
|
viewModelScope.launch(Dispatchers.IO) {
|
|
208
|
+
val prefs = context.getSharedPreferences("data", MODE_PRIVATE)
|
|
218
209
|
with(prefs.edit()) {
|
|
219
210
|
putString("bible", bible.filename())
|
|
220
211
|
putInt("bookIndex", bookIndex)
|
|
@@ -233,27 +224,6 @@ class AppViewModel(application: Application) : AndroidViewModel(application) {
|
|
|
233
224
|
}
|
|
234
225
|
}
|
|
235
226
|
|
|
236
|
-
fun getForwardPair(): Pair<Int, Int> {
|
|
237
|
-
val sizes = chapterSizes[bookIndex]
|
|
238
|
-
if (sizes > chapterIndex + 1) {
|
|
239
|
-
return Pair(bookIndex, chapterIndex + 1)
|
|
240
|
-
}
|
|
241
|
-
if (bookIndex + 1 < BOOKS_COUNT) {
|
|
242
|
-
return Pair(bookIndex + 1, 0)
|
|
243
|
-
}
|
|
244
|
-
return Pair(0, 0)
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
fun getBackwardPair(): Pair<Int, Int> {
|
|
248
|
-
if (chapterIndex - 1 >= 0) {
|
|
249
|
-
return Pair(bookIndex, chapterIndex - 1)
|
|
250
|
-
}
|
|
251
|
-
if (bookIndex - 1 >= 0) {
|
|
252
|
-
return Pair(bookIndex - 1, chapterSizes[bookIndex - 1] - 1)
|
|
253
|
-
}
|
|
254
|
-
return Pair(BOOKS_COUNT - 1, chapterSizes[BOOKS_COUNT - 1] - 1)
|
|
255
|
-
}
|
|
256
|
-
|
|
257
227
|
fun resetScrollState() {
|
|
258
228
|
scrollState = LazyListState(0, 0)
|
|
259
229
|
}
|
|
@@ -278,6 +248,37 @@ class AppViewModel(application: Application) : AndroidViewModel(application) {
|
|
|
278
248
|
highlightedVerses.value.remove(v.key())
|
|
279
249
|
}
|
|
280
250
|
}
|
|
251
|
+
|
|
252
|
+
fun playAudio(text: String) {
|
|
253
|
+
speechService.StartSpeakingSsml("""
|
|
254
|
+
<speak version="1.0" xmlns="http://www.w3.org/2001/10/synthesis" xml:lang="en-US">
|
|
255
|
+
<voice name="${bible.voiceName}">
|
|
256
|
+
$text
|
|
257
|
+
</voice>
|
|
258
|
+
</speak>
|
|
259
|
+
""")
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
fun getForwardPair(bookIndex: Int, chapterIndex: Int): Pair<Int, Int> {
|
|
264
|
+
val sizes = chapterSizes[bookIndex]
|
|
265
|
+
if (sizes > chapterIndex + 1) {
|
|
266
|
+
return Pair(bookIndex, chapterIndex + 1)
|
|
267
|
+
}
|
|
268
|
+
if (bookIndex + 1 < BOOKS_COUNT) {
|
|
269
|
+
return Pair(bookIndex + 1, 0)
|
|
270
|
+
}
|
|
271
|
+
return Pair(0, 0)
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
fun getBackwardPair(bookIndex: Int, chapterIndex: Int): Pair<Int, Int> {
|
|
275
|
+
if (chapterIndex - 1 >= 0) {
|
|
276
|
+
return Pair(bookIndex, chapterIndex - 1)
|
|
277
|
+
}
|
|
278
|
+
if (bookIndex - 1 >= 0) {
|
|
279
|
+
return Pair(bookIndex - 1, chapterSizes[bookIndex - 1] - 1)
|
|
280
|
+
}
|
|
281
|
+
return Pair(BOOKS_COUNT - 1, chapterSizes[BOOKS_COUNT - 1] - 1)
|
|
281
282
|
}
|
|
282
283
|
|
|
283
284
|
fun shareVerses(context: Context, verses: List<Verse>) {
|
|
@@ -285,12 +286,8 @@ fun shareVerses(context: Context, verses: List<Verse>) {
|
|
|
285
286
|
if (verses.size >= 3) "${verses.first().verseIndex + 1}-${verses.last().verseIndex + 1}" else verses.map { it.verseIndex + 1 }
|
|
286
287
|
.joinToString(",")
|
|
287
288
|
val title = "${verses[0].bookName} ${verses[0].chapterIndex + 1}:${versesThrough}"
|
|
289
|
+
val text =
|
|
288
|
-
|
|
290
|
+
Html.fromHtml(verses.joinToString("\n") { it.text }, Html.FROM_HTML_MODE_COMPACT).toString()
|
|
289
|
-
it.text.replace("<span style=\"color:red;\">", "")
|
|
290
|
-
.replace("<em>", "")
|
|
291
|
-
.replace("</span>", "")
|
|
292
|
-
.replace("</em>", "")
|
|
293
|
-
}
|
|
294
291
|
val sendIntent = Intent().apply {
|
|
295
292
|
action = Intent.ACTION_SEND
|
|
296
293
|
putExtra(Intent.EXTRA_TEXT, "${title}\n${text}")
|
app/src/main/java/dev/pyrossh/onlyBible/ChapterScreen.kt
CHANGED
|
@@ -37,6 +37,7 @@ import androidx.compose.runtime.setValue
|
|
|
37
37
|
import androidx.compose.ui.Modifier
|
|
38
38
|
import androidx.compose.ui.input.pointer.PointerInputScope
|
|
39
39
|
import androidx.compose.ui.input.pointer.pointerInput
|
|
40
|
+
import androidx.compose.ui.platform.LocalContext
|
|
40
41
|
import androidx.compose.ui.platform.LocalView
|
|
41
42
|
import androidx.compose.ui.text.TextStyle
|
|
42
43
|
import androidx.compose.ui.text.font.FontWeight
|
|
@@ -45,7 +46,8 @@ import androidx.compose.ui.unit.sp
|
|
|
45
46
|
import dev.pyrossh.onlyBible.composables.BibleSelector
|
|
46
47
|
import dev.pyrossh.onlyBible.composables.ChapterSelector
|
|
47
48
|
import dev.pyrossh.onlyBible.composables.EmbeddedSearchBar
|
|
49
|
+
import dev.pyrossh.onlyBible.composables.VerseHeading
|
|
48
|
-
import dev.pyrossh.onlyBible.composables.
|
|
50
|
+
import dev.pyrossh.onlyBible.composables.VerseText
|
|
49
51
|
import kotlinx.parcelize.Parcelize
|
|
50
52
|
import kotlinx.serialization.Serializable
|
|
51
53
|
import kotlin.math.abs
|
|
@@ -111,22 +113,21 @@ suspend fun PointerInputScope.detectSwipe(
|
|
|
111
113
|
@Composable
|
|
112
114
|
fun ChapterScreen(
|
|
113
115
|
model: AppViewModel,
|
|
114
|
-
onSwipeLeft: () -> Unit,
|
|
115
|
-
onSwipeRight: () -> Any,
|
|
116
116
|
bookIndex: Int,
|
|
117
117
|
chapterIndex: Int,
|
|
118
118
|
navigateToChapter: (ChapterScreenProps) -> Unit,
|
|
119
119
|
) {
|
|
120
120
|
val view = LocalView.current
|
|
121
|
-
val
|
|
121
|
+
val context = LocalContext.current
|
|
122
|
-
val bookNames by model.bookNames.collectAsState()
|
|
123
122
|
val isSearching by model.isSearching.collectAsState()
|
|
124
123
|
var chapterSelectorShown by remember { mutableStateOf(false) }
|
|
125
124
|
var bibleSelectorShown by remember { mutableStateOf(false) }
|
|
126
|
-
val headingColor = MaterialTheme.colorScheme.onSurface
|
|
125
|
+
val headingColor = MaterialTheme.colorScheme.onSurface
|
|
126
|
+
val bookNames by model.bookNames.collectAsState()
|
|
127
|
+
val verses by model.verses.collectAsState()
|
|
127
128
|
val chapterVerses =
|
|
128
129
|
verses.filter { it.bookIndex == bookIndex && it.chapterIndex == chapterIndex }
|
|
129
|
-
LaunchedEffect(
|
|
130
|
+
LaunchedEffect(Unit) {
|
|
130
131
|
model.clearSelectedVerses()
|
|
131
132
|
model.bookIndex = bookIndex
|
|
132
133
|
model.chapterIndex = chapterIndex
|
|
@@ -137,13 +138,20 @@ fun ChapterScreen(
|
|
|
137
138
|
) { innerPadding ->
|
|
138
139
|
if (bibleSelectorShown) {
|
|
139
140
|
BibleSelector(
|
|
140
|
-
|
|
141
|
+
bible = model.bible,
|
|
142
|
+
onSelected = {
|
|
143
|
+
view.playSoundEffect(SoundEffectConstants.CLICK)
|
|
144
|
+
bibleSelectorShown = false
|
|
145
|
+
model.loadBible(it, context)
|
|
146
|
+
},
|
|
141
147
|
onClose = { bibleSelectorShown = false },
|
|
142
148
|
)
|
|
143
149
|
}
|
|
144
150
|
if (chapterSelectorShown) {
|
|
145
151
|
ChapterSelector(
|
|
146
|
-
|
|
152
|
+
bible = model.bible,
|
|
153
|
+
bookNames = bookNames,
|
|
154
|
+
startBookIndex = bookIndex,
|
|
147
155
|
onClose = { chapterSelectorShown = false },
|
|
148
156
|
navigateToChapter = navigateToChapter,
|
|
149
157
|
)
|
|
@@ -166,11 +174,12 @@ fun ChapterScreen(
|
|
|
166
174
|
) {
|
|
167
175
|
TextButton(
|
|
168
176
|
contentPadding = PaddingValues(
|
|
169
|
-
top = ContentPadding.calculateTopPadding(),
|
|
177
|
+
top = ContentPadding.calculateTopPadding(), //8dp
|
|
170
178
|
end = 12.dp,
|
|
171
179
|
bottom = ContentPadding.calculateBottomPadding()
|
|
172
180
|
),
|
|
173
181
|
onClick = {
|
|
182
|
+
view.playSoundEffect(SoundEffectConstants.CLICK)
|
|
174
183
|
chapterSelectorShown = true
|
|
175
184
|
}
|
|
176
185
|
) {
|
|
@@ -188,7 +197,10 @@ fun ChapterScreen(
|
|
|
188
197
|
horizontalArrangement = Arrangement.End,
|
|
189
198
|
) {
|
|
190
199
|
IconButton(
|
|
200
|
+
onClick = {
|
|
201
|
+
view.playSoundEffect(SoundEffectConstants.CLICK)
|
|
191
|
-
|
|
202
|
+
model.onOpenSearch()
|
|
203
|
+
},
|
|
192
204
|
) {
|
|
193
205
|
Icon(
|
|
194
206
|
imageVector = Icons.Rounded.Search,
|
|
@@ -197,6 +209,7 @@ fun ChapterScreen(
|
|
|
197
209
|
)
|
|
198
210
|
}
|
|
199
211
|
TextButton(onClick = {
|
|
212
|
+
view.playSoundEffect(SoundEffectConstants.CLICK)
|
|
200
213
|
bibleSelectorShown = true
|
|
201
214
|
}) {
|
|
202
215
|
Text(
|
|
@@ -231,32 +244,46 @@ fun ChapterScreen(
|
|
|
231
244
|
.fillMaxSize()
|
|
232
245
|
.pointerInput(Unit) {
|
|
233
246
|
detectSwipe(
|
|
234
|
-
onSwipeLeft =
|
|
247
|
+
onSwipeLeft = {
|
|
248
|
+
val pair = getForwardPair(bookIndex, chapterIndex)
|
|
249
|
+
navigateToChapter(
|
|
250
|
+
ChapterScreenProps(
|
|
251
|
+
bookIndex = pair.first,
|
|
252
|
+
chapterIndex = pair.second,
|
|
253
|
+
)
|
|
254
|
+
)
|
|
255
|
+
},
|
|
235
|
-
onSwipeRight = {
|
|
256
|
+
onSwipeRight = {
|
|
257
|
+
val pair = getBackwardPair(bookIndex, chapterIndex)
|
|
258
|
+
navigateToChapter(
|
|
259
|
+
ChapterScreenProps(
|
|
260
|
+
bookIndex = pair.first,
|
|
261
|
+
chapterIndex = pair.second,
|
|
262
|
+
dir = Dir.Right.name,
|
|
263
|
+
)
|
|
264
|
+
)
|
|
265
|
+
},
|
|
236
266
|
)
|
|
237
267
|
}
|
|
238
268
|
) {
|
|
239
269
|
items(chapterVerses) { v ->
|
|
240
270
|
if (v.heading.isNotEmpty()) {
|
|
241
|
-
Text(
|
|
242
|
-
|
|
271
|
+
VerseHeading(
|
|
243
|
-
top = if (v.verseIndex != 0) 12.dp else 0.dp, bottom = 12.dp
|
|
244
|
-
),
|
|
245
|
-
|
|
272
|
+
text = v.heading,
|
|
246
|
-
|
|
273
|
+
fontType = model.fontType,
|
|
247
|
-
|
|
274
|
+
fontSizeDelta = model.fontSizeDelta,
|
|
248
|
-
|
|
275
|
+
navigateToChapter = navigateToChapter,
|
|
249
|
-
color = headingColor,
|
|
250
|
-
),
|
|
251
|
-
text = v.heading.replace("<br>", "\n")
|
|
252
276
|
)
|
|
253
277
|
}
|
|
254
|
-
|
|
278
|
+
VerseText(
|
|
255
279
|
model = model,
|
|
280
|
+
fontType = model.fontType,
|
|
281
|
+
fontSizeDelta = model.fontSizeDelta,
|
|
282
|
+
fontBoldEnabled = model.fontBoldEnabled,
|
|
256
283
|
verse = v,
|
|
257
284
|
)
|
|
258
285
|
}
|
|
259
286
|
}
|
|
260
287
|
}
|
|
261
288
|
}
|
|
262
|
-
}
|
|
289
|
+
}
|
app/src/main/java/dev/pyrossh/onlyBible/MainActivity.kt
CHANGED
|
@@ -16,11 +16,18 @@ class MainActivity : ComponentActivity() {
|
|
|
16
16
|
override fun onCreate(savedInstanceState: Bundle?) {
|
|
17
17
|
super.onCreate(savedInstanceState)
|
|
18
18
|
enableEdgeToEdge()
|
|
19
|
+
if (savedInstanceState == null) {
|
|
20
|
+
lifecycleScope.launch {
|
|
21
|
+
model.loadData(applicationContext)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
19
24
|
setContent {
|
|
20
|
-
AppTheme
|
|
25
|
+
AppTheme(
|
|
21
|
-
|
|
26
|
+
nightMode = model.nightMode
|
|
27
|
+
) {
|
|
28
|
+
AppHost()
|
|
22
29
|
if (model.showBottomSheet) {
|
|
23
|
-
TextSettingsBottomSheet(
|
|
30
|
+
TextSettingsBottomSheet()
|
|
24
31
|
}
|
|
25
32
|
}
|
|
26
33
|
}
|
|
@@ -29,7 +36,7 @@ class MainActivity : ComponentActivity() {
|
|
|
29
36
|
override fun onSaveInstanceState(outState: Bundle) {
|
|
30
37
|
super.onSaveInstanceState(outState)
|
|
31
38
|
lifecycleScope.launch {
|
|
32
|
-
model.saveData()
|
|
39
|
+
model.saveData(applicationContext)
|
|
33
40
|
}
|
|
34
41
|
}
|
|
35
42
|
}
|
app/src/main/java/dev/pyrossh/onlyBible/composables/BibleSelector.kt
CHANGED
|
@@ -18,18 +18,19 @@ import androidx.compose.ui.platform.LocalContext
|
|
|
18
18
|
import androidx.compose.ui.text.font.FontWeight
|
|
19
19
|
import androidx.compose.ui.unit.dp
|
|
20
20
|
import androidx.compose.ui.window.Dialog
|
|
21
|
-
import dev.pyrossh.onlyBible.
|
|
21
|
+
import dev.pyrossh.onlyBible.domain.Bible
|
|
22
22
|
import dev.pyrossh.onlyBible.domain.bibles
|
|
23
23
|
import java.util.Locale
|
|
24
24
|
|
|
25
25
|
@Composable
|
|
26
26
|
fun BibleSelector(
|
|
27
|
-
|
|
27
|
+
bible: Bible,
|
|
28
|
+
onSelected: (Bible) -> Unit,
|
|
28
29
|
onClose: () -> Unit,
|
|
29
30
|
) {
|
|
30
31
|
val context = LocalContext.current
|
|
31
32
|
val height = context.resources.configuration.screenHeightDp.dp / 2
|
|
32
|
-
val bibleList = bibles.sortedBy { it !=
|
|
33
|
+
val bibleList = bibles.sortedBy { it != bible }
|
|
33
34
|
Dialog(onDismissRequest = { onClose() }) {
|
|
34
35
|
Card(
|
|
35
36
|
modifier = Modifier
|
|
@@ -42,12 +43,10 @@ fun BibleSelector(
|
|
|
42
43
|
val loc = Locale(it.languageCode)
|
|
43
44
|
ListItem(
|
|
44
45
|
modifier = Modifier.clickable {
|
|
45
|
-
|
|
46
|
+
onSelected(it)
|
|
46
|
-
model.bible = it
|
|
47
|
-
model.loadBible()
|
|
48
47
|
},
|
|
49
48
|
colors = ListItemDefaults.colors(
|
|
50
|
-
containerColor = if (it ==
|
|
49
|
+
containerColor = if (it == bible)
|
|
51
50
|
MaterialTheme.colorScheme.primaryContainer
|
|
52
51
|
else
|
|
53
52
|
MaterialTheme.colorScheme.background
|
app/src/main/java/dev/pyrossh/onlyBible/composables/ChapterSelector.kt
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
package dev.pyrossh.onlyBible.composables
|
|
2
2
|
|
|
3
|
+
import android.view.SoundEffectConstants
|
|
3
4
|
import androidx.compose.foundation.clickable
|
|
4
5
|
import androidx.compose.foundation.layout.Arrangement
|
|
5
6
|
import androidx.compose.foundation.layout.Spacer
|
|
@@ -25,7 +26,6 @@ import androidx.compose.material3.Text
|
|
|
25
26
|
import androidx.compose.material3.TextButton
|
|
26
27
|
import androidx.compose.runtime.Composable
|
|
27
28
|
import androidx.compose.runtime.LaunchedEffect
|
|
28
|
-
import androidx.compose.runtime.collectAsState
|
|
29
29
|
import androidx.compose.runtime.getValue
|
|
30
30
|
import androidx.compose.runtime.mutableIntStateOf
|
|
31
31
|
import androidx.compose.runtime.mutableStateOf
|
|
@@ -34,26 +34,29 @@ import androidx.compose.runtime.setValue
|
|
|
34
34
|
import androidx.compose.ui.Modifier
|
|
35
35
|
import androidx.compose.ui.graphics.Color
|
|
36
36
|
import androidx.compose.ui.platform.LocalContext
|
|
37
|
+
import androidx.compose.ui.platform.LocalView
|
|
37
38
|
import androidx.compose.ui.text.font.FontWeight
|
|
38
39
|
import androidx.compose.ui.unit.dp
|
|
39
40
|
import androidx.compose.ui.window.Dialog
|
|
40
|
-
import dev.pyrossh.onlyBible.AppViewModel
|
|
41
41
|
import dev.pyrossh.onlyBible.ChapterScreenProps
|
|
42
|
+
import dev.pyrossh.onlyBible.domain.Bible
|
|
42
43
|
import dev.pyrossh.onlyBible.domain.chapterSizes
|
|
43
44
|
import dev.pyrossh.onlyBible.domain.engTitles
|
|
44
45
|
|
|
45
46
|
@OptIn(ExperimentalMaterial3Api::class)
|
|
46
47
|
@Composable
|
|
47
48
|
fun ChapterSelector(
|
|
49
|
+
bible: Bible,
|
|
50
|
+
bookNames: List<String>,
|
|
48
|
-
|
|
51
|
+
startBookIndex: Int,
|
|
49
52
|
onClose: () -> Unit,
|
|
50
53
|
navigateToChapter: (ChapterScreenProps) -> Unit
|
|
51
54
|
) {
|
|
55
|
+
val view = LocalView.current
|
|
52
56
|
val context = LocalContext.current
|
|
53
57
|
val height = context.resources.configuration.screenHeightDp.dp / 2
|
|
54
|
-
val bookNames by model.bookNames.collectAsState()
|
|
55
58
|
var expanded by remember { mutableStateOf(false) }
|
|
56
|
-
var bookIndex by remember { mutableIntStateOf(
|
|
59
|
+
var bookIndex by remember { mutableIntStateOf(startBookIndex) }
|
|
57
60
|
val scrollState = rememberLazyListState()
|
|
58
61
|
val bookList = bookNames - bookNames[bookIndex]
|
|
59
62
|
LaunchedEffect(key1 = bookIndex) {
|
|
@@ -69,6 +72,7 @@ fun ChapterSelector(
|
|
|
69
72
|
ListItem(
|
|
70
73
|
modifier = Modifier
|
|
71
74
|
.clickable {
|
|
75
|
+
view.playSoundEffect(SoundEffectConstants.CLICK)
|
|
72
76
|
expanded = !expanded
|
|
73
77
|
},
|
|
74
78
|
colors = ListItemDefaults.colors(
|
|
@@ -106,7 +110,7 @@ fun ChapterSelector(
|
|
|
106
110
|
)
|
|
107
111
|
},
|
|
108
112
|
supportingContent = {
|
|
109
|
-
if (
|
|
113
|
+
if (bible.languageCode != "en") {
|
|
110
114
|
Text(
|
|
111
115
|
modifier = Modifier.padding(start = 4.dp),
|
|
112
116
|
text = engTitles[bookNames.indexOf(it)],
|
|
@@ -141,6 +145,7 @@ fun ChapterSelector(
|
|
|
141
145
|
),
|
|
142
146
|
shape = RoundedCornerShape(8.dp),
|
|
143
147
|
onClick = {
|
|
148
|
+
view.playSoundEffect(SoundEffectConstants.CLICK)
|
|
144
149
|
onClose()
|
|
145
150
|
navigateToChapter(
|
|
146
151
|
ChapterScreenProps(
|
app/src/main/java/dev/pyrossh/onlyBible/composables/EmbeddedSearchBar.kt
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
package dev.pyrossh.onlyBible.composables
|
|
2
2
|
|
|
3
|
+
import android.view.SoundEffectConstants
|
|
3
4
|
import androidx.compose.foundation.layout.fillMaxWidth
|
|
4
5
|
import androidx.compose.foundation.layout.padding
|
|
5
6
|
import androidx.compose.foundation.lazy.LazyColumn
|
|
@@ -22,6 +23,7 @@ import androidx.compose.runtime.remember
|
|
|
22
23
|
import androidx.compose.ui.Modifier
|
|
23
24
|
import androidx.compose.ui.focus.FocusRequester
|
|
24
25
|
import androidx.compose.ui.focus.focusRequester
|
|
26
|
+
import androidx.compose.ui.platform.LocalView
|
|
25
27
|
import androidx.compose.ui.text.TextStyle
|
|
26
28
|
import androidx.compose.ui.text.font.FontWeight
|
|
27
29
|
import androidx.compose.ui.unit.dp
|
|
@@ -33,8 +35,9 @@ import dev.pyrossh.onlyBible.AppViewModel
|
|
|
33
35
|
fun EmbeddedSearchBar(
|
|
34
36
|
model: AppViewModel,
|
|
35
37
|
) {
|
|
38
|
+
val view = LocalView.current
|
|
36
39
|
val searchText by model.searchText.collectAsState()
|
|
37
|
-
val
|
|
40
|
+
val searchedVerses by model.searchedVerses.collectAsState()
|
|
38
41
|
val textFieldFocusRequester = remember { FocusRequester() }
|
|
39
42
|
SideEffect {
|
|
40
43
|
textFieldFocusRequester.requestFocus()
|
|
@@ -73,6 +76,7 @@ fun EmbeddedSearchBar(
|
|
|
73
76
|
trailingIcon = {
|
|
74
77
|
IconButton(
|
|
75
78
|
onClick = {
|
|
79
|
+
view.playSoundEffect(SoundEffectConstants.CLICK)
|
|
76
80
|
model.onCloseSearch()
|
|
77
81
|
},
|
|
78
82
|
) {
|
|
@@ -85,7 +89,7 @@ fun EmbeddedSearchBar(
|
|
|
85
89
|
},
|
|
86
90
|
tonalElevation = 0.dp,
|
|
87
91
|
) {
|
|
88
|
-
val groups =
|
|
92
|
+
val groups = searchedVerses.groupBy { "${it.bookName} ${it.chapterIndex + 1}" }
|
|
89
93
|
LazyColumn {
|
|
90
94
|
groups.forEach {
|
|
91
95
|
item(
|
|
@@ -105,8 +109,11 @@ fun EmbeddedSearchBar(
|
|
|
105
109
|
)
|
|
106
110
|
}
|
|
107
111
|
items(it.value) { v ->
|
|
108
|
-
|
|
112
|
+
VerseText(
|
|
109
113
|
model = model,
|
|
114
|
+
fontType = model.fontType,
|
|
115
|
+
fontSizeDelta = model.fontSizeDelta,
|
|
116
|
+
fontBoldEnabled = model.fontBoldEnabled,
|
|
110
117
|
verse = v,
|
|
111
118
|
)
|
|
112
119
|
}
|
app/src/main/java/dev/pyrossh/onlyBible/composables/TextSettingsBottomSheet.kt
CHANGED
|
@@ -3,6 +3,7 @@ package dev.pyrossh.onlyBible.composables
|
|
|
3
3
|
import android.app.UiModeManager.MODE_NIGHT_AUTO
|
|
4
4
|
import android.app.UiModeManager.MODE_NIGHT_NO
|
|
5
5
|
import android.app.UiModeManager.MODE_NIGHT_YES
|
|
6
|
+
import android.view.SoundEffectConstants
|
|
6
7
|
import androidx.compose.foundation.BorderStroke
|
|
7
8
|
import androidx.compose.foundation.background
|
|
8
9
|
import androidx.compose.foundation.layout.Arrangement
|
|
@@ -34,17 +35,20 @@ import androidx.compose.runtime.Composable
|
|
|
34
35
|
import androidx.compose.runtime.rememberCoroutineScope
|
|
35
36
|
import androidx.compose.ui.Alignment
|
|
36
37
|
import androidx.compose.ui.Modifier
|
|
38
|
+
import androidx.compose.ui.platform.LocalView
|
|
37
39
|
import androidx.compose.ui.text.TextStyle
|
|
38
40
|
import androidx.compose.ui.text.font.FontWeight
|
|
39
41
|
import androidx.compose.ui.unit.dp
|
|
40
42
|
import androidx.compose.ui.unit.sp
|
|
43
|
+
import androidx.lifecycle.viewmodel.compose.viewModel
|
|
41
44
|
import dev.pyrossh.onlyBible.AppViewModel
|
|
42
45
|
import dev.pyrossh.onlyBible.FontType
|
|
43
46
|
import kotlinx.coroutines.launch
|
|
44
47
|
|
|
45
48
|
@Composable
|
|
46
49
|
@OptIn(ExperimentalMaterial3Api::class)
|
|
47
|
-
fun TextSettingsBottomSheet(model: AppViewModel) {
|
|
50
|
+
fun TextSettingsBottomSheet(model: AppViewModel = viewModel()) {
|
|
51
|
+
val view = LocalView.current
|
|
48
52
|
val scope = rememberCoroutineScope()
|
|
49
53
|
val sheetState = rememberModalBottomSheetState()
|
|
50
54
|
return ModalBottomSheet(
|
|
@@ -78,6 +82,7 @@ fun TextSettingsBottomSheet(model: AppViewModel) {
|
|
|
78
82
|
)
|
|
79
83
|
Row(horizontalArrangement = Arrangement.End) {
|
|
80
84
|
IconButton(onClick = {
|
|
85
|
+
view.playSoundEffect(SoundEffectConstants.CLICK)
|
|
81
86
|
scope.launch {
|
|
82
87
|
sheetState.hide()
|
|
83
88
|
}.invokeOnCompletion {
|
|
@@ -103,6 +108,7 @@ fun TextSettingsBottomSheet(model: AppViewModel) {
|
|
|
103
108
|
.padding(end = 16.dp)
|
|
104
109
|
.weight(1f),
|
|
105
110
|
onClick = {
|
|
111
|
+
view.playSoundEffect(SoundEffectConstants.CLICK)
|
|
106
112
|
model.fontSizeDelta -= 1
|
|
107
113
|
}) {
|
|
108
114
|
Column(
|
|
@@ -124,6 +130,7 @@ fun TextSettingsBottomSheet(model: AppViewModel) {
|
|
|
124
130
|
.padding(end = 16.dp)
|
|
125
131
|
.weight(1f),
|
|
126
132
|
onClick = {
|
|
133
|
+
view.playSoundEffect(SoundEffectConstants.CLICK)
|
|
127
134
|
model.fontSizeDelta += 1
|
|
128
135
|
}) {
|
|
129
136
|
Column(
|
|
@@ -144,6 +151,7 @@ fun TextSettingsBottomSheet(model: AppViewModel) {
|
|
|
144
151
|
.padding(end = 16.dp)
|
|
145
152
|
.weight(1f),
|
|
146
153
|
onClick = {
|
|
154
|
+
view.playSoundEffect(SoundEffectConstants.CLICK)
|
|
147
155
|
model.fontBoldEnabled = !model.fontBoldEnabled
|
|
148
156
|
}) {
|
|
149
157
|
Column(
|
|
@@ -165,6 +173,7 @@ fun TextSettingsBottomSheet(model: AppViewModel) {
|
|
|
165
173
|
.padding(end = 16.dp)
|
|
166
174
|
.weight(1f),
|
|
167
175
|
onClick = {
|
|
176
|
+
view.playSoundEffect(SoundEffectConstants.CLICK)
|
|
168
177
|
if (model.lineSpacingDelta > 5) {
|
|
169
178
|
model.lineSpacingDelta = 0
|
|
170
179
|
} else {
|
|
@@ -202,6 +211,7 @@ fun TextSettingsBottomSheet(model: AppViewModel) {
|
|
|
202
211
|
.padding(end = 16.dp)
|
|
203
212
|
.weight(1f),
|
|
204
213
|
onClick = {
|
|
214
|
+
view.playSoundEffect(SoundEffectConstants.CLICK)
|
|
205
215
|
model.fontType = it
|
|
206
216
|
}) {
|
|
207
217
|
Column(
|
|
@@ -254,6 +264,7 @@ fun TextSettingsBottomSheet(model: AppViewModel) {
|
|
|
254
264
|
.padding(end = 16.dp)
|
|
255
265
|
.weight(1f),
|
|
256
266
|
onClick = {
|
|
267
|
+
view.playSoundEffect(SoundEffectConstants.CLICK)
|
|
257
268
|
scope.launch {
|
|
258
269
|
sheetState.hide()
|
|
259
270
|
model.closeSheet()
|
app/src/main/java/dev/pyrossh/onlyBible/composables/VerseHeading.kt
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
package dev.pyrossh.onlyBible.composables
|
|
2
|
+
|
|
3
|
+
import android.view.SoundEffectConstants
|
|
4
|
+
import androidx.compose.foundation.layout.padding
|
|
5
|
+
import androidx.compose.material3.MaterialTheme
|
|
6
|
+
import androidx.compose.material3.Text
|
|
7
|
+
import androidx.compose.runtime.Composable
|
|
8
|
+
import androidx.compose.ui.Modifier
|
|
9
|
+
import androidx.compose.ui.graphics.Color
|
|
10
|
+
import androidx.compose.ui.platform.LocalView
|
|
11
|
+
import androidx.compose.ui.text.AnnotatedString
|
|
12
|
+
import androidx.compose.ui.text.LinkAnnotation
|
|
13
|
+
import androidx.compose.ui.text.SpanStyle
|
|
14
|
+
import androidx.compose.ui.text.TextLinkStyles
|
|
15
|
+
import androidx.compose.ui.text.TextStyle
|
|
16
|
+
import androidx.compose.ui.text.font.FontStyle
|
|
17
|
+
import androidx.compose.ui.text.font.FontWeight
|
|
18
|
+
import androidx.compose.ui.text.fromHtml
|
|
19
|
+
import androidx.compose.ui.unit.dp
|
|
20
|
+
import androidx.compose.ui.unit.sp
|
|
21
|
+
import dev.pyrossh.onlyBible.ChapterScreenProps
|
|
22
|
+
import dev.pyrossh.onlyBible.FontType
|
|
23
|
+
|
|
24
|
+
@Composable
|
|
25
|
+
fun VerseHeading(
|
|
26
|
+
text: String,
|
|
27
|
+
fontType: FontType,
|
|
28
|
+
fontSizeDelta: Int,
|
|
29
|
+
navigateToChapter: (ChapterScreenProps) -> Unit
|
|
30
|
+
) {
|
|
31
|
+
val view = LocalView.current
|
|
32
|
+
Text(
|
|
33
|
+
modifier = Modifier.padding(bottom = 24.dp),
|
|
34
|
+
style = TextStyle(
|
|
35
|
+
fontFamily = fontType.family(),
|
|
36
|
+
fontSize = (16 + fontSizeDelta).sp,
|
|
37
|
+
fontWeight = FontWeight.W700,
|
|
38
|
+
color = MaterialTheme.colorScheme.onSurface,
|
|
39
|
+
),
|
|
40
|
+
text = AnnotatedString.fromHtml(
|
|
41
|
+
htmlString = text,
|
|
42
|
+
linkStyles = TextLinkStyles(
|
|
43
|
+
style = SpanStyle(
|
|
44
|
+
fontSize = (14 + fontSizeDelta).sp,
|
|
45
|
+
fontStyle = FontStyle.Italic,
|
|
46
|
+
color = Color(0xFF008AE6),
|
|
47
|
+
)
|
|
48
|
+
),
|
|
49
|
+
linkInteractionListener = {
|
|
50
|
+
view.playSoundEffect(SoundEffectConstants.CLICK)
|
|
51
|
+
val url = (it as LinkAnnotation.Url).url
|
|
52
|
+
val parts = url.split(":")
|
|
53
|
+
navigateToChapter(
|
|
54
|
+
ChapterScreenProps(
|
|
55
|
+
bookIndex = parts[0].toInt(),
|
|
56
|
+
chapterIndex = parts[1].toInt(),
|
|
57
|
+
)
|
|
58
|
+
)
|
|
59
|
+
},
|
|
60
|
+
),
|
|
61
|
+
)
|
|
62
|
+
}
|
app/src/main/java/dev/pyrossh/onlyBible/composables/{VerseView.kt → VerseText.kt}
RENAMED
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
package dev.pyrossh.onlyBible.composables
|
|
2
2
|
|
|
3
|
-
import android.graphics.Typeface
|
|
4
|
-
import android.text.Html
|
|
5
|
-
import android.text.style.BulletSpan
|
|
6
|
-
import android.text.style.ForegroundColorSpan
|
|
7
|
-
import android.text.style.StyleSpan
|
|
8
3
|
import androidx.compose.foundation.border
|
|
9
4
|
import androidx.compose.foundation.clickable
|
|
10
5
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
|
@@ -42,17 +37,22 @@ import androidx.compose.ui.graphics.Color
|
|
|
42
37
|
import androidx.compose.ui.layout.onPlaced
|
|
43
38
|
import androidx.compose.ui.layout.positionInRoot
|
|
44
39
|
import androidx.compose.ui.platform.LocalContext
|
|
40
|
+
import androidx.compose.ui.text.AnnotatedString
|
|
41
|
+
import androidx.compose.ui.text.LinkAnnotation
|
|
45
42
|
import androidx.compose.ui.text.SpanStyle
|
|
43
|
+
import androidx.compose.ui.text.TextLinkStyles
|
|
46
44
|
import androidx.compose.ui.text.TextStyle
|
|
47
45
|
import androidx.compose.ui.text.buildAnnotatedString
|
|
48
46
|
import androidx.compose.ui.text.font.FontStyle
|
|
49
47
|
import androidx.compose.ui.text.font.FontWeight
|
|
48
|
+
import androidx.compose.ui.text.fromHtml
|
|
50
49
|
import androidx.compose.ui.text.withStyle
|
|
51
50
|
import androidx.compose.ui.unit.IntOffset
|
|
52
51
|
import androidx.compose.ui.unit.dp
|
|
53
52
|
import androidx.compose.ui.unit.sp
|
|
54
53
|
import androidx.compose.ui.window.Popup
|
|
55
54
|
import dev.pyrossh.onlyBible.AppViewModel
|
|
55
|
+
import dev.pyrossh.onlyBible.FontType
|
|
56
56
|
import dev.pyrossh.onlyBible.darkHighlights
|
|
57
57
|
import dev.pyrossh.onlyBible.domain.Verse
|
|
58
58
|
import dev.pyrossh.onlyBible.isLightTheme
|
|
@@ -62,8 +62,11 @@ import kotlinx.coroutines.Dispatchers
|
|
|
62
62
|
import kotlinx.coroutines.launch
|
|
63
63
|
|
|
64
64
|
@Composable
|
|
65
|
-
fun
|
|
65
|
+
fun VerseText(
|
|
66
66
|
model: AppViewModel,
|
|
67
|
+
fontType: FontType,
|
|
68
|
+
fontSizeDelta: Int,
|
|
69
|
+
fontBoldEnabled: Boolean,
|
|
67
70
|
verse: Verse,
|
|
68
71
|
) {
|
|
69
72
|
var barYPosition by remember {
|
|
@@ -71,8 +74,6 @@ fun VerseView(
|
|
|
71
74
|
}
|
|
72
75
|
val selectedVerses by model.selectedVerses.collectAsState()
|
|
73
76
|
val isLight = isLightTheme(model.nightMode, isSystemInDarkTheme())
|
|
74
|
-
val fontSizeDelta = model.fontSizeDelta
|
|
75
|
-
val boldWeight = if (model.fontBoldEnabled) FontWeight.W700 else FontWeight.W400
|
|
76
77
|
val buttonInteractionSource = remember { MutableInteractionSource() }
|
|
77
78
|
val isSelected = selectedVerses.contains(verse)
|
|
78
79
|
val highlightedColorIndex = model.getHighlightForVerse(verse)
|
|
@@ -102,7 +103,7 @@ fun VerseView(
|
|
|
102
103
|
currentHighlightColors[highlightedColorIndex]
|
|
103
104
|
else
|
|
104
105
|
Color.Unspecified,
|
|
105
|
-
fontFamily =
|
|
106
|
+
fontFamily = fontType.family(),
|
|
106
107
|
color = if (isLight)
|
|
107
108
|
Color(0xFF000104)
|
|
108
109
|
else
|
|
@@ -110,15 +111,15 @@ fun VerseView(
|
|
|
110
111
|
currentHighlightColors[highlightedColorIndex]
|
|
111
112
|
else
|
|
112
113
|
Color(0xFFBCBCBC),
|
|
113
|
-
fontWeight =
|
|
114
|
+
fontWeight = if (fontBoldEnabled)
|
|
115
|
+
FontWeight.W700
|
|
116
|
+
else
|
|
117
|
+
FontWeight.W400,
|
|
114
118
|
fontSize = (17 + fontSizeDelta).sp,
|
|
115
119
|
lineHeight = (23 + fontSizeDelta).sp,
|
|
116
120
|
letterSpacing = 0.sp,
|
|
117
121
|
),
|
|
118
122
|
text = buildAnnotatedString {
|
|
119
|
-
val spanned = Html.fromHtml(verse.text, Html.FROM_HTML_MODE_COMPACT)
|
|
120
|
-
val spans = spanned.getSpans(0, spanned.length, Any::class.java)
|
|
121
|
-
val verseNo = "${verse.verseIndex + 1} "
|
|
122
123
|
withStyle(
|
|
123
124
|
style = SpanStyle(
|
|
124
125
|
fontSize = (13 + fontSizeDelta).sp,
|
|
@@ -129,41 +130,23 @@ fun VerseView(
|
|
|
129
130
|
fontWeight = FontWeight.W700,
|
|
130
131
|
)
|
|
131
132
|
) {
|
|
132
|
-
append(
|
|
133
|
+
append("${verse.verseIndex + 1} ")
|
|
133
134
|
}
|
|
134
|
-
append(spanned.toString())
|
|
135
|
-
|
|
135
|
+
append(
|
|
136
|
-
.filter { it !is BulletSpan }
|
|
137
|
-
.forEach { span ->
|
|
138
|
-
|
|
136
|
+
AnnotatedString.Companion.fromHtml(
|
|
139
|
-
val end = spanned.getSpanEnd(span)
|
|
140
|
-
when (span) {
|
|
141
|
-
|
|
137
|
+
htmlString = verse.text,
|
|
142
|
-
if (isLight) SpanStyle(color = Color(0xFFFF0000))
|
|
143
|
-
else SpanStyle(color = Color(0xFFFF636B))
|
|
144
|
-
|
|
145
|
-
|
|
138
|
+
linkStyles = TextLinkStyles(
|
|
146
|
-
Typeface.BOLD -> SpanStyle(fontWeight = FontWeight.Bold)
|
|
147
|
-
Typeface.ITALIC -> SpanStyle(fontStyle = FontStyle.Italic)
|
|
148
|
-
|
|
139
|
+
style = SpanStyle(
|
|
149
|
-
|
|
140
|
+
fontSize = (14 + model.fontSizeDelta).sp,
|
|
150
|
-
|
|
141
|
+
fontStyle = FontStyle.Italic,
|
|
151
|
-
)
|
|
152
|
-
|
|
153
|
-
else -> null
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
else -> {
|
|
157
|
-
null
|
|
158
|
-
}
|
|
159
|
-
}?.let { spanStyle ->
|
|
160
|
-
addStyle(
|
|
161
|
-
spanStyle,
|
|
162
|
-
|
|
142
|
+
color = Color(0xFF008AE6),
|
|
163
|
-
end + verseNo.length
|
|
164
143
|
)
|
|
144
|
+
),
|
|
145
|
+
linkInteractionListener = {
|
|
146
|
+
println("SOUTT ${(it as LinkAnnotation.Url).url}")
|
|
165
|
-
}
|
|
147
|
+
},
|
|
166
|
-
|
|
148
|
+
)
|
|
149
|
+
)
|
|
167
150
|
}
|
|
168
151
|
)
|
|
169
152
|
if (isSelected && selectedVerses.last() == verse) {
|
|
@@ -240,9 +223,7 @@ private fun Menu(
|
|
|
240
223
|
} else {
|
|
241
224
|
scope.launch(Dispatchers.IO) {
|
|
242
225
|
for (v in selectedVerses.sortedBy { it.verseIndex }) {
|
|
243
|
-
model.speechService.StartSpeakingSsml(
|
|
244
|
-
|
|
226
|
+
model.playAudio(v.text)
|
|
245
|
-
)
|
|
246
227
|
}
|
|
247
228
|
}
|
|
248
229
|
}
|
app/src/main/java/dev/pyrossh/onlyBible/domain/Verse.kt
CHANGED
|
@@ -181,14 +181,4 @@ data class Verse(
|
|
|
181
181
|
) : Parcelable {
|
|
182
182
|
|
|
183
183
|
fun key() = "${bookIndex}:${chapterIndex}:${verseIndex}"
|
|
184
|
-
|
|
185
|
-
fun toSSML(voice: String): String {
|
|
186
|
-
return """
|
|
187
|
-
<speak version="1.0" xmlns="http://www.w3.org/2001/10/synthesis" xml:lang="en-US">
|
|
188
|
-
<voice name="$voice">
|
|
189
|
-
$text
|
|
190
|
-
</voice>
|
|
191
|
-
</speak>
|
|
192
|
-
""".trimIndent()
|
|
193
|
-
}
|
|
194
184
|
}
|
app/src/main/res/drawable/ic_launcher_foreground.xml
CHANGED
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
android:viewportWidth="108"
|
|
5
5
|
android:viewportHeight="108">
|
|
6
6
|
<group
|
|
7
|
-
android:scaleX="0.
|
|
7
|
+
android:scaleX="0.11"
|
|
8
|
-
android:scaleY="0.
|
|
8
|
+
android:scaleY="0.11"
|
|
9
|
-
android:translateX="
|
|
9
|
+
android:translateX="26"
|
|
10
|
-
android:translateY="
|
|
10
|
+
android:translateY="26">
|
|
11
11
|
<path
|
|
12
12
|
android:pathData="m389.57,122.38h-93.69V48.43c0,-22.08 -17.9,-39.98 -39.98,-39.98v0c-22.08,0 -39.98,17.9 -39.98,39.98v73.95h-93.69c-22.08,0 -39.98,17.9 -39.98,39.98v0c0,22.08 17.9,39.98 39.98,39.98h93.69v261.23c0,22.08 17.9,39.98 39.98,39.98v0c22.08,0 39.98,-17.9 39.98,-39.98V202.34h93.69c22.08,0 39.98,-17.9 39.98,-39.98v0c0,-22.08 -17.9,-39.98 -39.98,-39.98z"
|
|
13
13
|
android:fillColor="#FFFCC447"/>
|
app/src/main/res/values-night/colors.xml
DELETED
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
-
<resources>
|
|
3
|
-
<color name="splash_bg">#00000000</color>
|
|
4
|
-
</resources>
|
app/src/main/res/values/colors.xml
DELETED
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
-
<resources>
|
|
3
|
-
<color name="splash_bg">#FFFFFFFF</color>
|
|
4
|
-
</resources>
|
app/src/main/res/values/themes.xml
DELETED
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
<?xml version="1.0" encoding="utf-8"?>
|
|
2
|
-
<resources>
|
|
3
|
-
<style name="Theme.BibleApp" parent="Theme.AppCompat.DayNight.NoActionBar" />
|
|
4
|
-
</resources>
|