feat: add local-first private AI digest workflow

Migrate app code into canonical feature slices, add phone-only AI digest scheduling and review, wire local notification/background task support, and cover the flow with tests.
This commit is contained in:
Rijad Zuzo
2026-05-17 00:17:20 +02:00
parent dab50abf0e
commit f655adfbea
212 changed files with 24178 additions and 15895 deletions
+107
View File
@@ -0,0 +1,107 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
const String _llmApiKeyStorageKey = 'llm_api_key';
const String _llmProviderStorageKey = 'llm_provider';
enum LlmProvider { openai, anthropic, google }
class LlmConfigState {
const LlmConfigState({
this.apiKeyConfigured = false,
this.provider = LlmProvider.openai,
this.isConfigured = false,
});
final bool apiKeyConfigured;
final LlmProvider provider;
final bool isConfigured;
LlmConfigState copyWith({
bool? apiKeyConfigured,
LlmProvider? provider,
bool? isConfigured,
}) {
return LlmConfigState(
apiKeyConfigured: apiKeyConfigured ?? this.apiKeyConfigured,
provider: provider ?? this.provider,
isConfigured: isConfigured ?? this.isConfigured,
);
}
}
class LlmConfigNotifier extends Notifier<LlmConfigState> {
Future<void>? _initializeFuture;
@override
LlmConfigState build() {
return const LlmConfigState();
}
Future<void> initialize() async {
final Future<void>? inFlight = _initializeFuture;
if (inFlight != null) {
return inFlight;
}
_initializeFuture = _initializeFromStorage();
return _initializeFuture;
}
Future<void> _initializeFromStorage() async {
const FlutterSecureStorage secureStorage = FlutterSecureStorage();
final String? storedKey = await secureStorage.read(
key: _llmApiKeyStorageKey,
);
final String? storedProvider = await secureStorage.read(
key: _llmProviderStorageKey,
);
state = LlmConfigState(
apiKeyConfigured: storedKey != null && storedKey.isNotEmpty,
provider: storedProvider != null
? LlmProvider.values.firstWhere(
(LlmProvider p) => p.name == storedProvider,
orElse: () => LlmProvider.openai,
)
: LlmProvider.openai,
isConfigured: storedKey != null && storedKey.isNotEmpty,
);
}
Future<void> setApiKey(String apiKey) async {
const FlutterSecureStorage secureStorage = FlutterSecureStorage();
if (apiKey.isEmpty) {
await secureStorage.delete(key: _llmApiKeyStorageKey);
} else {
await secureStorage.write(key: _llmApiKeyStorageKey, value: apiKey);
}
state = state.copyWith(
apiKeyConfigured: apiKey.isNotEmpty,
isConfigured: apiKey.isNotEmpty,
);
}
Future<void> setProvider(LlmProvider provider) async {
const FlutterSecureStorage secureStorage = FlutterSecureStorage();
await secureStorage.write(
key: _llmProviderStorageKey,
value: provider.name,
);
state = state.copyWith(provider: provider);
}
Future<String?> getApiKey() async {
const FlutterSecureStorage secureStorage = FlutterSecureStorage();
return secureStorage.read(key: _llmApiKeyStorageKey);
}
Future<void> clearApiKey() async {
const FlutterSecureStorage secureStorage = FlutterSecureStorage();
await secureStorage.delete(key: _llmApiKeyStorageKey);
state = state.copyWith(apiKeyConfigured: false, isConfigured: false);
}
}
final llmConfigProvider = NotifierProvider<LlmConfigNotifier, LlmConfigState>(
LlmConfigNotifier.new,
);