~repos /only-bible-app

#kotlin#android#ios

GIT_CONFIG_PARAMETERS="'http.version=HTTP/1.1'" git clone https://git.pyrossh.dev/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.


Files changed (2) hide show
  1. lib/home.dart +107 -2
  2. lib/widgets/verses_view.dart +0 -120
lib/home.dart CHANGED
@@ -1,7 +1,13 @@
1
+ import "package:flutter/gestures.dart";
1
2
  import "package:flutter/material.dart";
3
+ import "package:only_bible_app/gen/bible.gen.dart";
2
4
  import "package:only_bible_app/store/app_state.dart";
3
5
  import "package:only_bible_app/widgets/home_app_bar.dart";
6
+ import "package:flutter_swipe_detector/flutter_swipe_detector.dart";
7
+ import "package:only_bible_app/store/actions_navigation.dart";
8
+ import "package:only_bible_app/store/app_navigator.dart";
4
- import "package:only_bible_app/widgets/verses_view.dart";
9
+ import "package:only_bible_app/widgets/menu_overlay.dart";
10
+ import "package:only_bible_app/store/actions_state.dart";
5
11
 
6
12
  class Home extends StatelessWidget {
7
13
  final int bookIndex;
@@ -9,16 +15,115 @@ class Home extends StatelessWidget {
9
15
 
10
16
  const Home({super.key, required this.bookIndex, required this.chapterIndex});
11
17
 
18
+ static final _linkPattern = RegExp(r"RF:(\d+):(\d+):(\d+)");
19
+
20
+ List<InlineSpan> _buildHeadingSpans(BuildContext context, Bible bible, String heading, TextStyle style) {
21
+ final text = heading.replaceAll("<br>", "\n");
22
+ final spans = <InlineSpan>[];
23
+ var lastEnd = 0;
24
+
25
+ for (final match in _linkPattern.allMatches(text)) {
26
+ if (match.start > lastEnd) {
27
+ spans.add(TextSpan(text: text.substring(lastEnd, match.start), style: style));
28
+ }
29
+ final bookIndex = int.parse(match.group(1)!);
30
+ final chapterIndex = int.parse(match.group(2)!);
31
+ final verseIndex = int.parse(match.group(3)!);
32
+ final bookName = bible.books![bookIndex].name ?? "";
33
+ final label = "$bookName ${chapterIndex + 1}:${verseIndex + 1}";
34
+ spans.add(
35
+ TextSpan(
36
+ text: label,
37
+ style: style.copyWith(
38
+ color: Theme.of(context).colorScheme.primary,
39
+ decoration: TextDecoration.underline,
40
+ ),
41
+ recognizer: TapGestureRecognizer()
42
+ ..onTap = () => context.dispatch(GoToChapterAction(context.router, bookIndex, chapterIndex)),
43
+ ),
44
+ );
45
+ lastEnd = match.end;
46
+ }
47
+
48
+ if (lastEnd < text.length) {
49
+ spans.add(TextSpan(text: text.substring(lastEnd), style: style));
50
+ }
51
+ spans.add(TextSpan(text: "\n", style: style));
52
+ return spans;
53
+ }
54
+
12
55
  @override
13
56
  Widget build(BuildContext context) {
57
+ final theme = Theme.of(context).textTheme;
14
58
  final bible = context.select((s) => s.bible);
15
59
  final book = bible.books![bookIndex];
16
60
  final chapter = book.chapters![chapterIndex];
61
+ final (boldFont, fontSize, selectedVerses, _, _) =
62
+ context.select((s) => (s.boldFont, s.fontSize, s.selectedVerses, s.highlights, s.darkMode));
63
+ final appState = context.read();
64
+ final baseStyle = theme.bodyMedium!.copyWith(
65
+ fontWeight: boldFont ? FontWeight.w500 : FontWeight.w400,
66
+ fontSize: fontSize,
67
+ );
17
68
  return Scaffold(
18
69
  appBar: HomeAppBar(bible: bible, book: book, chapter: chapter),
19
70
  backgroundColor: Theme.of(context).colorScheme.surface,
20
71
  body: SafeArea(
72
+ child: Stack(
73
+ children: [
74
+ SwipeDetector(
75
+ onSwipeLeft: (offset) =>
76
+ context.dispatch(NextChapterAction(context.router, bible, chapter.book, chapter.index)),
77
+ onSwipeRight: (offset) =>
78
+ context.dispatch(PreviousChapterAction(context.router, bible, chapter.book, chapter.index)),
79
+ child: SingleChildScrollView(
80
+ key: ValueKey("${chapter.book}-${chapter.index}"),
81
+ physics: const BouncingScrollPhysics(),
82
+ padding: const EdgeInsets.symmetric(horizontal: 20),
83
+ child: Column(
84
+ crossAxisAlignment: CrossAxisAlignment.start,
85
+ children: [
86
+ for (final v in chapter.verses!)
87
+ Padding(
88
+ padding: const EdgeInsets.only(bottom: 8),
89
+ child: GestureDetector(
90
+ onTap: () => context.dispatch(SelectVerseAction(v)),
91
+ behavior: HitTestBehavior.opaque,
92
+ child: Text.rich(
93
+ TextSpan(
94
+ style: baseStyle,
95
+ children: [
96
+ if (v.heading != null && v.heading!.isNotEmpty)
97
+ ..._buildHeadingSpans(context, bible, v.heading!, theme.labelLarge!),
98
+ TextSpan(
99
+ text: "${v.index + 1} ",
100
+ style: theme.labelMedium!.copyWith(
101
+ color: const Color(0xFF9A1111),
102
+ ),
103
+ ),
104
+ TextSpan(
105
+ text: v.text ?? "",
106
+ style: appState.getHighlightStyle(context, v, false),
107
+ ),
108
+ ],
109
+ ),
110
+ ),
111
+ ),
112
+ ),
113
+ if (selectedVerses.isNotEmpty) const SizedBox(height: 120),
114
+ ],
115
+ ),
116
+ ),
117
+ ),
118
+ if (selectedVerses.isNotEmpty)
119
+ Positioned(
120
+ left: 20,
121
+ right: 20,
122
+ bottom: MediaQuery.of(context).padding.bottom + 40,
21
- child: VersesView(bible: bible, chapter: chapter),
123
+ child: Center(child: MenuOverlay(bible: bible)),
124
+ ),
125
+ ],
126
+ ),
22
127
  ),
23
128
  );
24
129
  }
lib/widgets/verses_view.dart DELETED
@@ -1,120 +0,0 @@
1
- import "package:flutter/gestures.dart";
2
- import "package:flutter/material.dart";
3
- import "package:flutter_swipe_detector/flutter_swipe_detector.dart";
4
- import "package:only_bible_app/gen/bible.gen.dart";
5
- import "package:only_bible_app/store/actions_navigation.dart";
6
- import "package:only_bible_app/store/app_navigator.dart";
7
- import "package:only_bible_app/widgets/menu_overlay.dart";
8
- import "package:only_bible_app/store/actions_state.dart";
9
- import "package:only_bible_app/store/app_state.dart";
10
-
11
- class VersesView extends StatelessWidget {
12
- final Bible bible;
13
- final Chapter chapter;
14
-
15
- const VersesView({super.key, required this.bible, required this.chapter});
16
-
17
- @override
18
- Widget build(BuildContext context) {
19
- final (boldFont, fontSize, selectedVerses, _, _) =
20
- context.select((s) => (s.boldFont, s.fontSize, s.selectedVerses, s.highlights, s.darkMode));
21
- final appState = context.read();
22
- final theme = Theme.of(context).textTheme;
23
- final baseStyle = theme.bodyMedium!.copyWith(
24
- fontWeight: boldFont ? FontWeight.w500 : FontWeight.w400,
25
- fontSize: fontSize,
26
- );
27
- return Stack(
28
- children: [
29
- SwipeDetector(
30
- onSwipeLeft: (offset) =>
31
- context.dispatch(NextChapterAction(context.router, bible, chapter.book, chapter.index)),
32
- onSwipeRight: (offset) =>
33
- context.dispatch(PreviousChapterAction(context.router, bible, chapter.book, chapter.index)),
34
- child: SingleChildScrollView(
35
- key: ValueKey("${chapter.book}-${chapter.index}"),
36
- physics: const BouncingScrollPhysics(),
37
- padding: const EdgeInsets.symmetric(horizontal: 20),
38
- child: Column(
39
- crossAxisAlignment: CrossAxisAlignment.start,
40
- children: [
41
- for (final v in chapter.verses!)
42
- Padding(
43
- padding: const EdgeInsets.only(bottom: 8),
44
- child: GestureDetector(
45
- onTap: () => context.dispatch(SelectVerseAction(v)),
46
- behavior: HitTestBehavior.opaque,
47
- child: Text.rich(
48
- TextSpan(
49
- style: baseStyle,
50
- children: [
51
- if (v.heading != null && v.heading!.isNotEmpty)
52
- ..._buildHeadingSpans(context, v.heading!, theme.labelLarge!),
53
- TextSpan(
54
- text: "${v.index + 1} ",
55
- style: theme.labelMedium!.copyWith(
56
- color: const Color(0xFF9A1111),
57
- ),
58
- ),
59
- TextSpan(
60
- text: v.text ?? "",
61
- style: appState.getHighlightStyle(context, v, false),
62
- ),
63
- ],
64
- ),
65
- ),
66
- ),
67
- ),
68
- if (selectedVerses.isNotEmpty) const SizedBox(height: 120),
69
- ],
70
- ),
71
- ),
72
- ),
73
- if (selectedVerses.isNotEmpty)
74
- Positioned(
75
- left: 20,
76
- right: 20,
77
- bottom: MediaQuery.of(context).padding.bottom + 40,
78
- child: Center(child: MenuOverlay(bible: bible)),
79
- ),
80
- ],
81
- );
82
- }
83
-
84
- static final _linkPattern = RegExp(r"RF:(\d+):(\d+):(\d+)");
85
-
86
- List<InlineSpan> _buildHeadingSpans(BuildContext context, String heading, TextStyle style) {
87
- final text = heading.replaceAll("<br>", "\n");
88
- final spans = <InlineSpan>[];
89
- var lastEnd = 0;
90
-
91
- for (final match in _linkPattern.allMatches(text)) {
92
- if (match.start > lastEnd) {
93
- spans.add(TextSpan(text: text.substring(lastEnd, match.start), style: style));
94
- }
95
- final bookIndex = int.parse(match.group(1)!);
96
- final chapterIndex = int.parse(match.group(2)!);
97
- final verseIndex = int.parse(match.group(3)!);
98
- final bookName = bible.books![bookIndex].name ?? "";
99
- final label = "$bookName ${chapterIndex + 1}:${verseIndex + 1}";
100
- spans.add(
101
- TextSpan(
102
- text: label,
103
- style: style.copyWith(
104
- color: Theme.of(context).colorScheme.primary,
105
- decoration: TextDecoration.underline,
106
- ),
107
- recognizer: TapGestureRecognizer()
108
- ..onTap = () => context.dispatch(GoToChapterAction(context.router, bookIndex, chapterIndex)),
109
- ),
110
- );
111
- lastEnd = match.end;
112
- }
113
-
114
- if (lastEnd < text.length) {
115
- spans.add(TextSpan(text: text.substring(lastEnd), style: style));
116
- }
117
- spans.add(TextSpan(text: "\n", style: style));
118
- return spans;
119
- }
120
- }