Implement three privacy features: 1. Consent gate at app startup - users must accept privacy terms before using OciDeck 2. License visibility - MIT license displayed in consent dialog 3. Consent revocation - privacy settings tab allows users to withdraw consent and return to consent screen Changes: - New ConsentProvider for managing consent state with SharedPreferences persistence - New ConsentDialog with privacy explanation and MIT license (expandable) - Added Privacy tab to settings dialog with revoke consent button - Updated localization strings for Dutch/English consent screens Consent flow: - On first launch or after revocation, consent screen blocks app access - Users can read privacy terms, view license, and accept to proceed - Consent can be revoked anytime from Settings → Privacy tab - After revocation, app returns to consent screen on next launch Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
67 lines
1.7 KiB
Dart
67 lines
1.7 KiB
Dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
|
|
|
const _consentKey = 'app_consent_accepted';
|
|
|
|
final consentProvider =
|
|
NotifierProvider<ConsentNotifier, ConsentState>(() {
|
|
return ConsentNotifier();
|
|
});
|
|
|
|
class ConsentState {
|
|
final bool hasAccepted;
|
|
final bool isLoading;
|
|
|
|
const ConsentState({
|
|
required this.hasAccepted,
|
|
this.isLoading = false,
|
|
});
|
|
|
|
ConsentState copyWith({
|
|
bool? hasAccepted,
|
|
bool? isLoading,
|
|
}) {
|
|
return ConsentState(
|
|
hasAccepted: hasAccepted ?? this.hasAccepted,
|
|
isLoading: isLoading ?? this.isLoading,
|
|
);
|
|
}
|
|
}
|
|
|
|
class ConsentNotifier extends Notifier<ConsentState> {
|
|
@override
|
|
ConsentState build() {
|
|
_initialize();
|
|
return const ConsentState(hasAccepted: false, isLoading: true);
|
|
}
|
|
|
|
Future<void> _initialize() async {
|
|
try {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
final hasAccepted = prefs.getBool(_consentKey) ?? false;
|
|
state = state.copyWith(hasAccepted: hasAccepted, isLoading: false);
|
|
} catch (e) {
|
|
state = state.copyWith(isLoading: false);
|
|
}
|
|
}
|
|
|
|
Future<void> acceptConsent() async {
|
|
try {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
await prefs.setBool(_consentKey, true);
|
|
state = state.copyWith(hasAccepted: true);
|
|
} catch (e) {
|
|
state = state.copyWith(hasAccepted: true);
|
|
}
|
|
}
|
|
|
|
Future<void> revokeConsent() async {
|
|
try {
|
|
final prefs = await SharedPreferences.getInstance();
|
|
await prefs.setBool(_consentKey, false);
|
|
state = state.copyWith(hasAccepted: false);
|
|
} catch (e) {
|
|
state = state.copyWith(hasAccepted: false);
|
|
}
|
|
}
|
|
}
|