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.
This commit is contained in:
113
lib/providers/scripture_provider.dart
Normal file
113
lib/providers/scripture_provider.dart
Normal file
@@ -0,0 +1,113 @@
|
||||
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);
|
||||
});
|
||||
@@ -55,6 +55,12 @@ class CycleEntriesNotifier extends StateNotifier<List<CycleEntry>> {
|
||||
_loadEntries();
|
||||
}
|
||||
|
||||
Future<void> updateEntry(CycleEntry entry) async {
|
||||
final box = Hive.box<CycleEntry>('cycle_entries');
|
||||
await box.put(entry.id, entry);
|
||||
_loadEntries();
|
||||
}
|
||||
|
||||
Future<void> deleteEntry(String id) async {
|
||||
final box = Hive.box<CycleEntry>('cycle_entries');
|
||||
await box.delete(id);
|
||||
|
||||
Reference in New Issue
Block a user