~repos /only-bible-app
GIT_CONFIG_PARAMETERS="'http.version=HTTP/1.1'" git clone
https://git.pyrossh.dev/only-bible-app/.git
only-bible-app
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.
3d32fc8b
—
pyrossh 3 weeks ago
Add Highlight History
- lib/store/actions_navigation.dart +39 -0
- lib/store/actions_state.dart +14 -3
- lib/store/app_state.dart +41 -0
- lib/widgets/highlight_history_sheet.dart +288 -0
- lib/widgets/home_app_bar.dart +7 -0
lib/store/actions_navigation.dart
CHANGED
|
@@ -6,11 +6,50 @@ import "package:only_bible_app/store/app_state.dart";
|
|
|
6
6
|
import "package:only_bible_app/utils.dart";
|
|
7
7
|
import "package:only_bible_app/widgets/book_select_sheet.dart";
|
|
8
8
|
|
|
9
|
+
import "package:only_bible_app/widgets/highlight_history_sheet.dart";
|
|
9
10
|
import "package:only_bible_app/widgets/settings_sheet.dart";
|
|
10
11
|
import "package:share_plus/share_plus.dart";
|
|
11
12
|
|
|
12
13
|
String _stripRedTags(String text) => text.replaceAll(RegExp(r"</?red>"), "");
|
|
13
14
|
|
|
15
|
+
class ShowHighlightHistoryAction extends ReduxAction<AppState> {
|
|
16
|
+
final BuildContext buildContext;
|
|
17
|
+
final Bible bible;
|
|
18
|
+
|
|
19
|
+
ShowHighlightHistoryAction(this.buildContext, this.bible);
|
|
20
|
+
|
|
21
|
+
@override
|
|
22
|
+
AppState? reduce() {
|
|
23
|
+
showModalBottomSheet(
|
|
24
|
+
context: buildContext,
|
|
25
|
+
isDismissible: true,
|
|
26
|
+
enableDrag: true,
|
|
27
|
+
showDragHandle: true,
|
|
28
|
+
useSafeArea: true,
|
|
29
|
+
isScrollControlled: true,
|
|
30
|
+
builder: (context) {
|
|
31
|
+
final surface = Theme.of(context).colorScheme.surface;
|
|
32
|
+
return Theme(
|
|
33
|
+
data: Theme.of(context).copyWith(
|
|
34
|
+
bottomSheetTheme: BottomSheetThemeData(
|
|
35
|
+
backgroundColor: surface,
|
|
36
|
+
dragHandleColor: Theme.of(context).colorScheme.onSurfaceVariant,
|
|
37
|
+
),
|
|
38
|
+
),
|
|
39
|
+
child: DraggableScrollableSheet(
|
|
40
|
+
expand: false,
|
|
41
|
+
initialChildSize: 0.6,
|
|
42
|
+
minChildSize: 0.4,
|
|
43
|
+
maxChildSize: 0.9,
|
|
44
|
+
builder: (_, __) => HighlightHistorySheet(bible: bible),
|
|
45
|
+
),
|
|
46
|
+
);
|
|
47
|
+
},
|
|
48
|
+
);
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
14
53
|
class ShowSettingsAction extends ReduxAction<AppState> {
|
|
15
54
|
final BuildContext buildContext;
|
|
16
55
|
final Bible bible;
|
lib/store/actions_state.dart
CHANGED
|
@@ -139,10 +139,21 @@ class SetHighlightAction extends ReduxAction<AppState> {
|
|
|
139
139
|
@override
|
|
140
140
|
AppState reduce() {
|
|
141
141
|
final updated = Map<String, int>.from(state.highlights);
|
|
142
|
+
final now = DateTime.now();
|
|
142
|
-
|
|
143
|
+
final newEntries = verses.map((v) {
|
|
143
144
|
updated["${v.book}:${v.chapter}:${v.index}"] = colorIndex;
|
|
145
|
+
return HighlightHistoryEntry(
|
|
146
|
+
book: v.book,
|
|
147
|
+
chapter: v.chapter,
|
|
148
|
+
verseIndex: v.index,
|
|
149
|
+
colorIndex: colorIndex,
|
|
150
|
+
timestamp: now,
|
|
144
|
-
|
|
151
|
+
);
|
|
152
|
+
}).toList();
|
|
153
|
+
// Keep last 500 entries
|
|
154
|
+
final updatedHistory = [...state.highlightHistory, ...newEntries];
|
|
155
|
+
final trimmed = updatedHistory.length > 500 ? updatedHistory.sublist(updatedHistory.length - 500) : updatedHistory;
|
|
145
|
-
return state.copy(highlights: updated, selectedVerses: []);
|
|
156
|
+
return state.copy(highlights: updated, selectedVerses: [], highlightHistory: trimmed);
|
|
146
157
|
}
|
|
147
158
|
}
|
|
148
159
|
|
lib/store/app_state.dart
CHANGED
|
@@ -4,6 +4,38 @@ import "package:only_bible_app/theme.dart";
|
|
|
4
4
|
import "package:async_redux/async_redux.dart";
|
|
5
5
|
export "package:async_redux/async_redux.dart" show BuildContextExtensionForProviderAndConnector;
|
|
6
6
|
|
|
7
|
+
class HighlightHistoryEntry {
|
|
8
|
+
final int book;
|
|
9
|
+
final int chapter;
|
|
10
|
+
final int verseIndex;
|
|
11
|
+
final int colorIndex;
|
|
12
|
+
final DateTime timestamp;
|
|
13
|
+
|
|
14
|
+
const HighlightHistoryEntry({
|
|
15
|
+
required this.book,
|
|
16
|
+
required this.chapter,
|
|
17
|
+
required this.verseIndex,
|
|
18
|
+
required this.colorIndex,
|
|
19
|
+
required this.timestamp,
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
Map<String, dynamic> toJson() => {
|
|
23
|
+
"book": book,
|
|
24
|
+
"chapter": chapter,
|
|
25
|
+
"verseIndex": verseIndex,
|
|
26
|
+
"colorIndex": colorIndex,
|
|
27
|
+
"timestamp": timestamp.toIso8601String(),
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
factory HighlightHistoryEntry.fromJson(Map<String, dynamic> json) => HighlightHistoryEntry(
|
|
31
|
+
book: json["book"] as int,
|
|
32
|
+
chapter: json["chapter"] as int,
|
|
33
|
+
verseIndex: json["verseIndex"] as int,
|
|
34
|
+
colorIndex: json["colorIndex"] as int,
|
|
35
|
+
timestamp: DateTime.parse(json["timestamp"] as String),
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
7
39
|
class AppState {
|
|
8
40
|
final bool engTitles;
|
|
9
41
|
final int fontWeight;
|
|
@@ -13,6 +45,7 @@ class AppState {
|
|
|
13
45
|
final int savedChapter;
|
|
14
46
|
final List<Verse> selectedVerses;
|
|
15
47
|
final Map<String, int> highlights;
|
|
48
|
+
final List<HighlightHistoryEntry> highlightHistory;
|
|
16
49
|
final Bible bible;
|
|
17
50
|
|
|
18
51
|
AppState({
|
|
@@ -24,6 +57,7 @@ class AppState {
|
|
|
24
57
|
this.savedChapter = 0,
|
|
25
58
|
this.selectedVerses = const [],
|
|
26
59
|
this.highlights = const {},
|
|
60
|
+
this.highlightHistory = const [],
|
|
27
61
|
required this.bible,
|
|
28
62
|
});
|
|
29
63
|
|
|
@@ -36,6 +70,7 @@ class AppState {
|
|
|
36
70
|
int? savedChapter,
|
|
37
71
|
List<Verse>? selectedVerses,
|
|
38
72
|
Map<String, int>? highlights,
|
|
73
|
+
List<HighlightHistoryEntry>? highlightHistory,
|
|
39
74
|
Bible? bible,
|
|
40
75
|
}) {
|
|
41
76
|
return AppState(
|
|
@@ -47,6 +82,7 @@ class AppState {
|
|
|
47
82
|
savedChapter: savedChapter ?? this.savedChapter,
|
|
48
83
|
selectedVerses: selectedVerses ?? this.selectedVerses,
|
|
49
84
|
highlights: highlights ?? this.highlights,
|
|
85
|
+
highlightHistory: highlightHistory ?? this.highlightHistory,
|
|
50
86
|
bible: bible ?? this.bible,
|
|
51
87
|
);
|
|
52
88
|
}
|
|
@@ -60,6 +96,7 @@ class AppState {
|
|
|
60
96
|
"savedBook": savedBook,
|
|
61
97
|
"savedChapter": savedChapter,
|
|
62
98
|
"highlights": highlights,
|
|
99
|
+
"highlightHistory": highlightHistory.map((e) => e.toJson()).toList(),
|
|
63
100
|
};
|
|
64
101
|
|
|
65
102
|
factory AppState.fromJson(Map<String, dynamic> json, Bible bible) => AppState(
|
|
@@ -70,6 +107,10 @@ class AppState {
|
|
|
70
107
|
savedBook: json["savedBook"] as int? ?? 0,
|
|
71
108
|
savedChapter: json["savedChapter"] as int? ?? 0,
|
|
72
109
|
highlights: (json["highlights"] as Map<String, dynamic>?)?.map((k, v) => MapEntry(k, v as int)) ?? const {},
|
|
110
|
+
highlightHistory: (json["highlightHistory"] as List<dynamic>?)
|
|
111
|
+
?.map((e) => HighlightHistoryEntry.fromJson(e as Map<String, dynamic>))
|
|
112
|
+
.toList() ??
|
|
113
|
+
const [],
|
|
73
114
|
bible: bible,
|
|
74
115
|
);
|
|
75
116
|
|
lib/widgets/highlight_history_sheet.dart
ADDED
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
import "package:flutter/material.dart";
|
|
2
|
+
import "package:only_bible_app/gen/bible.gen.dart";
|
|
3
|
+
import "package:only_bible_app/store/actions_navigation.dart";
|
|
4
|
+
import "package:only_bible_app/store/app_navigator.dart";
|
|
5
|
+
import "package:only_bible_app/store/app_state.dart";
|
|
6
|
+
import "package:only_bible_app/theme.dart";
|
|
7
|
+
|
|
8
|
+
class HighlightHistorySheet extends StatefulWidget {
|
|
9
|
+
final Bible bible;
|
|
10
|
+
|
|
11
|
+
const HighlightHistorySheet({super.key, required this.bible});
|
|
12
|
+
|
|
13
|
+
@override
|
|
14
|
+
State<HighlightHistorySheet> createState() => _HighlightHistorySheetState();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
class _HighlightHistorySheetState extends State<HighlightHistorySheet> {
|
|
18
|
+
// Tracks which day groups are expanded; today (index 0) starts open.
|
|
19
|
+
final Set<int> _expanded = {0};
|
|
20
|
+
|
|
21
|
+
String _formatTime(DateTime dt) {
|
|
22
|
+
final h = dt.hour.toString().padLeft(2, "0");
|
|
23
|
+
final m = dt.minute.toString().padLeft(2, "0");
|
|
24
|
+
return "$h:$m";
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
String _ordinal(int day) {
|
|
28
|
+
if (day >= 11 && day <= 13) return "${day}th";
|
|
29
|
+
switch (day % 10) {
|
|
30
|
+
case 1:
|
|
31
|
+
return "${day}st";
|
|
32
|
+
case 2:
|
|
33
|
+
return "${day}nd";
|
|
34
|
+
case 3:
|
|
35
|
+
return "${day}rd";
|
|
36
|
+
default:
|
|
37
|
+
return "${day}th";
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
String _formatDate(DateTime dt) {
|
|
42
|
+
const weekdays = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"];
|
|
43
|
+
const months = [
|
|
44
|
+
"January",
|
|
45
|
+
"February",
|
|
46
|
+
"March",
|
|
47
|
+
"April",
|
|
48
|
+
"May",
|
|
49
|
+
"June",
|
|
50
|
+
"July",
|
|
51
|
+
"August",
|
|
52
|
+
"September",
|
|
53
|
+
"October",
|
|
54
|
+
"November",
|
|
55
|
+
"December",
|
|
56
|
+
];
|
|
57
|
+
final weekday = weekdays[dt.weekday - 1];
|
|
58
|
+
return "$weekday ${_ordinal(dt.day)} ${months[dt.month - 1]}, ${dt.year}";
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
List<InlineSpan> _parseRedText(String raw, TextStyle base, TextStyle redStyle) {
|
|
62
|
+
final spans = <InlineSpan>[];
|
|
63
|
+
final exp = RegExp(r"<red>(.*?)<\/red>", dotAll: true);
|
|
64
|
+
int cursor = 0;
|
|
65
|
+
for (final match in exp.allMatches(raw)) {
|
|
66
|
+
if (match.start > cursor) {
|
|
67
|
+
spans.add(TextSpan(text: raw.substring(cursor, match.start), style: base));
|
|
68
|
+
}
|
|
69
|
+
spans.add(TextSpan(text: match.group(1), style: redStyle));
|
|
70
|
+
cursor = match.end;
|
|
71
|
+
}
|
|
72
|
+
if (cursor < raw.length) {
|
|
73
|
+
spans.add(TextSpan(text: raw.substring(cursor), style: base));
|
|
74
|
+
}
|
|
75
|
+
return spans;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
void _toggle(int index) {
|
|
79
|
+
setState(() {
|
|
80
|
+
if (_expanded.contains(index)) {
|
|
81
|
+
_expanded.remove(index);
|
|
82
|
+
} else {
|
|
83
|
+
_expanded.add(index);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
@override
|
|
89
|
+
Widget build(BuildContext context) {
|
|
90
|
+
final history = context.select((s) => s.highlightHistory);
|
|
91
|
+
final darkMode = context.select((s) => s.darkMode);
|
|
92
|
+
final colorScheme = Theme.of(context).colorScheme;
|
|
93
|
+
|
|
94
|
+
// Group by date (newest first)
|
|
95
|
+
final reversed = history.reversed.toList();
|
|
96
|
+
final Map<String, List<HighlightHistoryEntry>> grouped = {};
|
|
97
|
+
for (final entry in reversed) {
|
|
98
|
+
final key = _formatDate(entry.timestamp);
|
|
99
|
+
grouped.putIfAbsent(key, () => []).add(entry);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
final dateKeys = grouped.keys.toList();
|
|
103
|
+
|
|
104
|
+
return Padding(
|
|
105
|
+
padding: const EdgeInsets.symmetric(horizontal: 20),
|
|
106
|
+
child: Column(
|
|
107
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
108
|
+
children: [
|
|
109
|
+
Text(
|
|
110
|
+
"Highlight History",
|
|
111
|
+
style: Theme.of(context).textTheme.headlineMedium,
|
|
112
|
+
),
|
|
113
|
+
const SizedBox(height: 12),
|
|
114
|
+
if (grouped.isEmpty)
|
|
115
|
+
Expanded(
|
|
116
|
+
child: Center(
|
|
117
|
+
child: Column(
|
|
118
|
+
mainAxisSize: MainAxisSize.min,
|
|
119
|
+
children: [
|
|
120
|
+
Icon(Icons.highlight_outlined, size: 48, color: colorScheme.outlineVariant),
|
|
121
|
+
const SizedBox(height: 12),
|
|
122
|
+
Text(
|
|
123
|
+
"No highlights yet",
|
|
124
|
+
style: Theme.of(context).textTheme.bodyLarge?.copyWith(color: colorScheme.outline),
|
|
125
|
+
),
|
|
126
|
+
],
|
|
127
|
+
),
|
|
128
|
+
),
|
|
129
|
+
)
|
|
130
|
+
else
|
|
131
|
+
Expanded(
|
|
132
|
+
child: ListView.builder(
|
|
133
|
+
itemCount: dateKeys.length,
|
|
134
|
+
itemBuilder: (context, i) {
|
|
135
|
+
final dateLabel = dateKeys[i];
|
|
136
|
+
final entries = grouped[dateLabel]!;
|
|
137
|
+
final isExpanded = _expanded.contains(i);
|
|
138
|
+
|
|
139
|
+
return Column(
|
|
140
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
141
|
+
children: [
|
|
142
|
+
// Accordion header
|
|
143
|
+
InkWell(
|
|
144
|
+
onTap: () => _toggle(i),
|
|
145
|
+
borderRadius: BorderRadius.circular(6),
|
|
146
|
+
child: Padding(
|
|
147
|
+
padding: const EdgeInsets.symmetric(vertical: 10),
|
|
148
|
+
child: Row(
|
|
149
|
+
children: [
|
|
150
|
+
Expanded(
|
|
151
|
+
child: Text(
|
|
152
|
+
dateLabel,
|
|
153
|
+
style: Theme.of(context).textTheme.labelLarge?.copyWith(
|
|
154
|
+
color: colorScheme.primary,
|
|
155
|
+
fontWeight: FontWeight.bold,
|
|
156
|
+
),
|
|
157
|
+
),
|
|
158
|
+
),
|
|
159
|
+
Text(
|
|
160
|
+
"${entries.length}",
|
|
161
|
+
style: Theme.of(context).textTheme.labelSmall?.copyWith(
|
|
162
|
+
color: colorScheme.outline,
|
|
163
|
+
),
|
|
164
|
+
),
|
|
165
|
+
const SizedBox(width: 4),
|
|
166
|
+
AnimatedRotation(
|
|
167
|
+
turns: isExpanded ? 0.5 : 0,
|
|
168
|
+
duration: const Duration(milliseconds: 200),
|
|
169
|
+
child: Icon(
|
|
170
|
+
Icons.keyboard_arrow_down,
|
|
171
|
+
size: 20,
|
|
172
|
+
color: colorScheme.primary,
|
|
173
|
+
),
|
|
174
|
+
),
|
|
175
|
+
],
|
|
176
|
+
),
|
|
177
|
+
),
|
|
178
|
+
),
|
|
179
|
+
// Accordion body
|
|
180
|
+
AnimatedCrossFade(
|
|
181
|
+
firstChild: const SizedBox.shrink(),
|
|
182
|
+
secondChild: Column(
|
|
183
|
+
children: entries.map((entry) {
|
|
184
|
+
final bookName = widget.bible.books?[entry.book].name ?? "Book ${entry.book + 1}";
|
|
185
|
+
final chapterNum = entry.chapter + 1;
|
|
186
|
+
final verseNum = entry.verseIndex + 1;
|
|
187
|
+
final highlightColor =
|
|
188
|
+
darkMode ? darkHighlights[entry.colorIndex] : lightHighlights[entry.colorIndex];
|
|
189
|
+
final verseText =
|
|
190
|
+
widget.bible.books?[entry.book].chapters?[entry.chapter].verses?[entry.verseIndex].text;
|
|
191
|
+
|
|
192
|
+
return InkWell(
|
|
193
|
+
onTap: () {
|
|
194
|
+
Navigator.of(context).pop();
|
|
195
|
+
context.dispatch(GoToChapterAction(
|
|
196
|
+
context.router,
|
|
197
|
+
entry.book,
|
|
198
|
+
entry.chapter,
|
|
199
|
+
verse: entry.verseIndex,
|
|
200
|
+
));
|
|
201
|
+
},
|
|
202
|
+
borderRadius: BorderRadius.circular(6),
|
|
203
|
+
child: Padding(
|
|
204
|
+
padding: const EdgeInsets.symmetric(vertical: 6, horizontal: 2),
|
|
205
|
+
child: Row(
|
|
206
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
207
|
+
children: [
|
|
208
|
+
SizedBox(
|
|
209
|
+
height: 20,
|
|
210
|
+
child: Padding(
|
|
211
|
+
padding: const EdgeInsets.only(top: 4),
|
|
212
|
+
child: Center(
|
|
213
|
+
child: Container(
|
|
214
|
+
width: 14,
|
|
215
|
+
height: 14,
|
|
216
|
+
decoration: BoxDecoration(
|
|
217
|
+
color: highlightColor,
|
|
218
|
+
shape: BoxShape.circle,
|
|
219
|
+
border:
|
|
220
|
+
Border.all(color: Theme.of(context).colorScheme.onSurface, width: 1),
|
|
221
|
+
),
|
|
222
|
+
),
|
|
223
|
+
),
|
|
224
|
+
),
|
|
225
|
+
),
|
|
226
|
+
const SizedBox(width: 10),
|
|
227
|
+
Expanded(
|
|
228
|
+
child: Column(
|
|
229
|
+
crossAxisAlignment: CrossAxisAlignment.start,
|
|
230
|
+
children: [
|
|
231
|
+
Row(
|
|
232
|
+
children: [
|
|
233
|
+
Expanded(
|
|
234
|
+
child: Text(
|
|
235
|
+
"$bookName $chapterNum:$verseNum",
|
|
236
|
+
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
|
237
|
+
fontWeight: FontWeight.w600,
|
|
238
|
+
),
|
|
239
|
+
),
|
|
240
|
+
),
|
|
241
|
+
Text(
|
|
242
|
+
_formatTime(entry.timestamp),
|
|
243
|
+
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
|
244
|
+
color: colorScheme.outline,
|
|
245
|
+
),
|
|
246
|
+
),
|
|
247
|
+
],
|
|
248
|
+
),
|
|
249
|
+
if (verseText != null && verseText.isNotEmpty) ...[
|
|
250
|
+
const SizedBox(height: 2),
|
|
251
|
+
RichText(
|
|
252
|
+
text: TextSpan(
|
|
253
|
+
children: _parseRedText(
|
|
254
|
+
verseText,
|
|
255
|
+
Theme.of(context).textTheme.bodySmall!.copyWith(
|
|
256
|
+
color: colorScheme.onSurfaceVariant,
|
|
257
|
+
),
|
|
258
|
+
Theme.of(context).textTheme.bodySmall!.copyWith(
|
|
259
|
+
color: Colors.red,
|
|
260
|
+
),
|
|
261
|
+
),
|
|
262
|
+
),
|
|
263
|
+
),
|
|
264
|
+
],
|
|
265
|
+
],
|
|
266
|
+
),
|
|
267
|
+
),
|
|
268
|
+
],
|
|
269
|
+
),
|
|
270
|
+
),
|
|
271
|
+
);
|
|
272
|
+
}).toList(),
|
|
273
|
+
),
|
|
274
|
+
crossFadeState: isExpanded ? CrossFadeState.showSecond : CrossFadeState.showFirst,
|
|
275
|
+
duration: const Duration(milliseconds: 200),
|
|
276
|
+
),
|
|
277
|
+
Divider(color: colorScheme.outlineVariant, height: 1),
|
|
278
|
+
],
|
|
279
|
+
);
|
|
280
|
+
},
|
|
281
|
+
),
|
|
282
|
+
),
|
|
283
|
+
const SizedBox(height: 16),
|
|
284
|
+
],
|
|
285
|
+
),
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
}
|
lib/widgets/home_app_bar.dart
CHANGED
|
@@ -69,6 +69,13 @@ class HomeAppBar extends StatelessWidget implements PreferredSizeWidget {
|
|
|
69
69
|
// ),
|
|
70
70
|
// ),
|
|
71
71
|
// ),
|
|
72
|
+
IconButton(
|
|
73
|
+
enableFeedback: true,
|
|
74
|
+
key: const Key("historyButton"),
|
|
75
|
+
padding: EdgeInsets.zero,
|
|
76
|
+
icon: const Icon(Icons.history, size: 28),
|
|
77
|
+
onPressed: () => context.dispatch(ShowHighlightHistoryAction(context, bible)),
|
|
78
|
+
),
|
|
72
79
|
IconButton(
|
|
73
80
|
enableFeedback: true,
|
|
74
81
|
key: const Key("bible"),
|