import "package:async_redux/async_redux.dart";
import "package:flutter_soloud/flutter_soloud.dart";
import "package:flutter/material.dart";
import "package:only_bible_app/dialog.dart";
import "package:only_bible_app/gen/bible.gen.dart";
import "package:only_bible_app/store/app_state.dart";
import "package:only_bible_app/utils.dart";
class ToggleEngTitlesAction extends ReduxAction<AppState> {
AppState reduce() => state.copy(engTitles: !state.engTitles);
class UpdateFontWeightAction extends ReduxAction<AppState> {
UpdateFontWeightAction(this.value);
AppState reduce() => state.copy(fontWeight: value);
class ToggleDarkModeAction extends ReduxAction<AppState> {
AppState reduce() => state.copy(darkMode: !state.darkMode);
class UpdateFontSizeAction extends ReduxAction<AppState> {
UpdateFontSizeAction(this.value);
AppState reduce() => state.copy(fontSize: value);
class UpdateChapterAction extends ReduxAction<AppState> {
UpdateChapterAction(this.book, this.chapter);
AppState reduce() => state.copy(savedBook: book, savedChapter: chapter);
class TogglePlayAction extends ReduxAction<AppState> {
final BuildContext buildContext;
TogglePlayAction(this.buildContext, this.bible);
Future<AppState?> reduce() async {
if (SoLoud.instance.getActiveVoiceCount() > 0) {
await SoLoud.instance.disposeAllSources();
final versesToPlay = List<Verse>.from(state.selectedVerses);
final audioVoice = bible.voiceName!;
final audioError = "Audio playback error";
for (final v in versesToPlay) {
final pathname = "${bible.name!}_${v.book}_${v.chapter}_${v.index}";
final data = await convertText(audioVoice, v.text ?? "");
final stream = SoLoud.instance.setBufferStream(
bufferingType: BufferingType.preserved,
SoLoud.instance.addAudioDataStream(stream, data);
SoLoud.instance.setDataIsEnded(stream);
final handle = await SoLoud.instance.play(stream);
// Wait for playback to complete
await stream.soundEvents.firstWhere(
(e) => e.event == SoundEventType.handleIsNoMoreValid && e.handle == handle,
await SoLoud.instance.disposeSource(stream);
// If sources were disposed (user stopped playback), don't treat as error
if (SoLoud.instance.getActiveVoiceCount() == 0) {
error: (err.toString(), pathname),
recordError((err.toString(), pathname).toString(), null);
if (buildContext.mounted) {
showError(buildContext, audioError);
class SelectVerseAction extends ReduxAction<AppState> {
SelectVerseAction(this.verse);
final isSelected = state.selectedVerses.any(
(el) => el.book == verse.book && el.chapter == verse.chapter && el.index == verse.index,
final newVerses = isSelected
? state.selectedVerses.where((it) => it.index != verse.index).toList()
: [...state.selectedVerses, verse];
selectedVerses: newVerses,
class ClearSelectedVersesAction extends ReduxAction<AppState> {
AppState reduce() => state.copy(selectedVerses: []);
class SetHighlightAction extends ReduxAction<AppState> {
final List<Verse> verses;
SetHighlightAction(this.verses, this.colorIndex);
final updated = Map<String, int>.from(state.highlights);
for (final v in verses) {
updated["${v.book}:${v.chapter}:${v.index}"] = colorIndex;
return state.copy(highlights: updated, selectedVerses: []);
class RemoveHighlightAction extends ReduxAction<AppState> {
final List<Verse> verses;
RemoveHighlightAction(this.verses);
final updated = Map<String, int>.from(state.highlights);
for (final v in verses) {
updated.remove("${v.book}:${v.chapter}:${v.index}");
return state.copy(highlights: updated, selectedVerses: []);