Files
Tracker/lib/providers/scripture_provider.dart
Sterlen b4b2bfe749 feat: Implement husband features and fix iOS Safari web startup
Implement initial features for husband's companion app, including mock data
service and husband notes screen. Refactor scripture and cycle services
for improved stability and testability. Address iOS Safari web app
startup issue by removing deprecated initialization.

- Implemented MockDataService and HusbandNotesScreen.
- Converted _DashboardTab and DevotionalScreen to StatefulWidgets for robust
  scripture provider initialization.
- Refactored CycleService to use immutable CycleInfo class, reducing UI rebuilds.
- Removed deprecated window.flutterConfiguration from index.html, resolving
  Flutter web app startup failure on iOS Safari.
- Updated and fixed related tests.
2025-12-26 22:40:52 -06:00

114 lines
3.9 KiB
Dart

import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../models/scripture.dart';
import '../models/cycle_entry.dart';
import 'user_provider.dart';
import 'package:collection/collection.dart'; // For IterableExtension
// State for ScriptureProvider
class ScriptureState {
final Scripture? currentScripture;
final CyclePhase? currentPhase;
final int currentIndex; // Index within the phase-specific list
final int? maxIndex; // Max number of scriptures for the current phase
ScriptureState({
this.currentScripture,
this.currentPhase,
this.currentIndex = 0,
this.maxIndex,
});
ScriptureState copyWith({
Scripture? currentScripture,
CyclePhase? currentPhase,
int? currentIndex,
int? maxIndex,
}) {
return ScriptureState(
currentScripture: currentScripture ?? this.currentScripture,
currentPhase: currentPhase ?? this.currentPhase,
currentIndex: currentIndex ?? this.currentIndex,
maxIndex: maxIndex ?? this.maxIndex,
);
}
}
// StateNotifier for ScriptureProvider
class ScriptureNotifier extends StateNotifier<ScriptureState> {
final ScriptureDatabase _scriptureDatabase;
final Ref _ref;
ScriptureNotifier(this._scriptureDatabase, this._ref) : super(ScriptureState()) {
// We don't initialize here directly, as we need the phase from other providers.
// Initialization will be triggered by the UI.
}
// Initialize/refresh scripture for a given phase
// This should be called by the consuming widget when the phase changes or on initial load.
Future<void> initializeScripture(CyclePhase phase) async {
// Only re-initialize if the phase has changed or no scripture is currently set
if (state.currentPhase != phase || state.currentScripture == null) {
final scriptureCount = _scriptureDatabase.getScriptureCountForPhase(phase.name);
if (scriptureCount > 0) {
// Use day of year to get a stable initial scripture for the day
final dayOfYear =
DateTime.now().difference(DateTime(DateTime.now().year, 1, 1)).inDays;
final initialIndex = dayOfYear % scriptureCount;
state = state.copyWith(
currentPhase: phase,
currentIndex: initialIndex,
maxIndex: scriptureCount,
currentScripture: _scriptureDatabase.getScriptureForPhaseByIndex(
phase.name, initialIndex),
);
} else {
state = state.copyWith(
currentPhase: phase,
currentScripture: null,
currentIndex: 0,
maxIndex: 0,
);
}
}
}
void getNextScripture() {
if (state.currentPhase == null || state.maxIndex == null || state.maxIndex == 0) return;
final nextIndex = (state.currentIndex + 1) % state.maxIndex!;
_updateScripture(nextIndex);
}
void getPreviousScripture() {
if (state.currentPhase == null || state.maxIndex == null || state.maxIndex == 0) return;
final prevIndex = (state.currentIndex - 1 + state.maxIndex!) % state.maxIndex!;
_updateScripture(prevIndex);
}
void getRandomScripture() {
if (state.currentPhase == null || state.maxIndex == null || state.maxIndex == 0) return;
// Use a proper random number generator for better randomness
final randomIndex = DateTime.now().microsecondsSinceEpoch % state.maxIndex!; // Still using timestamp for simplicity
_updateScripture(randomIndex);
}
void _updateScripture(int newIndex) {
if (state.currentPhase == null) return;
final newScripture = _scriptureDatabase.getScriptureForPhaseByIndex(
state.currentPhase!.name, newIndex);
state = state.copyWith(
currentIndex: newIndex,
currentScripture: newScripture,
);
}
}
final scriptureDatabaseProvider = Provider((ref) => ScriptureDatabase());
final scriptureProvider =
StateNotifierProvider<ScriptureNotifier, ScriptureState>((ref) {
return ScriptureNotifier(ref.watch(scriptureDatabaseProvider), ref);
});