/
opt
/
canhelp
/
apps
/
mobile
/
lib
/
services
/
/opt/canhelp/apps/mobile/lib/services
mkdir
upload
Name
Size
Mode
Actions
api_client.dart
59021
0644
edit
dl
rm
push_service.dart
4495
0644
edit
dl
rm
storage_service.dart
9090
0644
edit
dl
rm
Edit:
/opt/canhelp/apps/mobile/lib/services/storage_service.dart
(9090B)
import 'dart:convert'; import 'package:flutter/foundation.dart'; import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:shared_preferences/shared_preferences.dart'; class BlockedUserRecord { final String userId; final String? displayName; final String? avatarUrl; final DateTime blockedAt; const BlockedUserRecord({ required this.userId, required this.blockedAt, this.displayName, this.avatarUrl, }); factory BlockedUserRecord.fromJson(Map<String, dynamic> json) { final blockedAtRaw = json['blockedAt'] as String?; return BlockedUserRecord( userId: (json['userId'] as String? ?? '').trim(), displayName: (json['displayName'] as String?)?.trim(), avatarUrl: (json['avatarUrl'] as String?)?.trim(), blockedAt: blockedAtRaw != null ? DateTime.tryParse(blockedAtRaw) ?? DateTime.now() : DateTime.now(), ); } Map<String, dynamic> toJson() => { 'userId': userId, if (displayName != null && displayName!.isNotEmpty) 'displayName': displayName, if (avatarUrl != null && avatarUrl!.isNotEmpty) 'avatarUrl': avatarUrl, 'blockedAt': blockedAt.toIso8601String(), }; } class StorageService { static final StorageService _instance = StorageService._internal(); factory StorageService() => _instance; StorageService._internal(); final FlutterSecureStorage _secure = const FlutterSecureStorage( aOptions: AndroidOptions(encryptedSharedPreferences: true), ); // Backward-compatible reader for builds that stored values without // EncryptedSharedPreferences enabled. final FlutterSecureStorage _secureLegacy = const FlutterSecureStorage( aOptions: AndroidOptions(encryptedSharedPreferences: false), ); static final ValueNotifier<int> blockedUsersChanges = ValueNotifier<int>(0); static void _notifyBlockedUsersChanged() { blockedUsersChanges.value = blockedUsersChanges.value + 1; } static const _keyAccessToken = 'access_token'; static const _keyUserId = 'user_id'; static const _keyLocale = 'locale'; static const _keyOnboardingCompleted = 'onboarding_completed'; static const _keyPushToken = 'push_token'; static const _keyCachedUser = 'cached_user'; static const _keySelectedLocation = 'selected_location_slug'; static const _keyBlockedUserIds = 'blocked_user_ids'; static const _keyBlockedUsersMap = 'blocked_users_map_v1'; // --- Secure token storage --- Future<void> saveTokens({required String accessToken, String? userId}) async { await _secure.write(key: _keyAccessToken, value: accessToken); if (userId != null) { await _secure.write(key: _keyUserId, value: userId); } } Future<String?> getAccessToken() async { final token = await _secure.read(key: _keyAccessToken); if (token != null && token.isNotEmpty) return token; final legacyToken = await _secureLegacy.read(key: _keyAccessToken); if (legacyToken == null || legacyToken.isEmpty) return null; final legacyUserId = await _secureLegacy.read(key: _keyUserId); await saveTokens(accessToken: legacyToken, userId: legacyUserId); return legacyToken; } Future<String?> getUserId() async { final userId = await _secure.read(key: _keyUserId); if (userId != null && userId.isNotEmpty) return userId; return _secureLegacy.read(key: _keyUserId); } Future<void> deleteTokens() async { await _secure.delete(key: _keyAccessToken); await _secure.delete(key: _keyUserId); } Future<void> saveCachedUser(Map<String, dynamic> user) async { final prefs = await SharedPreferences.getInstance(); await prefs.setString(_keyCachedUser, jsonEncode(user)); } Future<String?> getCachedUser() async { final prefs = await SharedPreferences.getInstance(); return prefs.getString(_keyCachedUser); } Future<void> deleteCachedUser() async { final prefs = await SharedPreferences.getInstance(); await prefs.remove(_keyCachedUser); } Future<void> savePushToken(String token) async { await _secure.write(key: _keyPushToken, value: token); } Future<String?> getPushToken() async { final token = await _secure.read(key: _keyPushToken); if (token != null && token.isNotEmpty) return token; final legacyToken = await _secureLegacy.read(key: _keyPushToken); if (legacyToken == null || legacyToken.isEmpty) return null; await savePushToken(legacyToken); return legacyToken; } Future<void> deletePushToken() => _secure.delete(key: _keyPushToken); // --- SharedPreferences (locale & onboarding) --- Future<void> saveLocale(String locale) async { final prefs = await SharedPreferences.getInstance(); await prefs.setString(_keyLocale, locale); } Future<String?> getLocale() async { final prefs = await SharedPreferences.getInstance(); return prefs.getString(_keyLocale); } Future<void> setOnboardingCompleted(bool completed) async { final prefs = await SharedPreferences.getInstance(); await prefs.setBool(_keyOnboardingCompleted, completed); } Future<bool> isOnboardingCompleted() async { final prefs = await SharedPreferences.getInstance(); return prefs.getBool(_keyOnboardingCompleted) ?? false; } Future<void> saveSelectedLocation(String? slug) async { final prefs = await SharedPreferences.getInstance(); if (slug == null || slug.isEmpty) { await prefs.remove(_keySelectedLocation); } else { await prefs.setString(_keySelectedLocation, slug); } } Future<String?> getSelectedLocation() async { final prefs = await SharedPreferences.getInstance(); final slug = prefs.getString(_keySelectedLocation); return slug != null && slug.isNotEmpty ? slug : null; } Future<Map<String, BlockedUserRecord>> _getBlockedUsersMap() async { final prefs = await SharedPreferences.getInstance(); final raw = prefs.getString(_keyBlockedUsersMap); if (raw == null || raw.isEmpty) return <String, BlockedUserRecord>{}; try { final decoded = jsonDecode(raw); if (decoded is! Map<String, dynamic>) { return <String, BlockedUserRecord>{}; } final map = <String, BlockedUserRecord>{}; for (final entry in decoded.entries) { final key = entry.key.trim(); final value = entry.value; if (key.isEmpty || value is! Map<String, dynamic>) continue; final record = BlockedUserRecord.fromJson(value); if (record.userId.isNotEmpty) { map[record.userId] = record; } } return map; } catch (_) { return <String, BlockedUserRecord>{}; } } Future<void> _saveBlockedUsersMap(Map<String, BlockedUserRecord> map) async { final prefs = await SharedPreferences.getInstance(); final payload = <String, dynamic>{}; for (final entry in map.entries) { payload[entry.key] = entry.value.toJson(); } await prefs.setString(_keyBlockedUsersMap, jsonEncode(payload)); } Future<Set<String>> getBlockedUserIds() async { final prefs = await SharedPreferences.getInstance(); final idsLegacy = prefs.getStringList(_keyBlockedUserIds) ?? const <String>[]; final map = await _getBlockedUsersMap(); final merged = <String>{...map.keys}; merged.addAll( idsLegacy.where((id) => id.trim().isNotEmpty).map((id) => id.trim()), ); return merged; } Future<List<BlockedUserRecord>> getBlockedUsers() async { final ids = await getBlockedUserIds(); final map = await _getBlockedUsersMap(); final records = <BlockedUserRecord>[]; for (final id in ids) { records.add( map[id] ?? BlockedUserRecord( userId: id, blockedAt: DateTime.fromMillisecondsSinceEpoch(0), ), ); } records.sort((a, b) => b.blockedAt.compareTo(a.blockedAt)); return records; } Future<bool> isUserBlocked(String userId) async { final blocked = await getBlockedUserIds(); return blocked.contains(userId); } Future<void> blockUser( String userId, { String? displayName, String? avatarUrl, }) async { final id = userId.trim(); if (id.isEmpty) return; final prefs = await SharedPreferences.getInstance(); final blocked = await getBlockedUserIds(); blocked.add(id); await prefs.setStringList(_keyBlockedUserIds, blocked.toList()); final map = await _getBlockedUsersMap(); map[id] = BlockedUserRecord( userId: id, displayName: displayName, avatarUrl: avatarUrl, blockedAt: DateTime.now(), ); await _saveBlockedUsersMap(map); _notifyBlockedUsersChanged(); } Future<void> unblockUser(String userId) async { final prefs = await SharedPreferences.getInstance(); final blocked = await getBlockedUserIds(); final id = userId.trim(); blocked.remove(id); await prefs.setStringList(_keyBlockedUserIds, blocked.toList()); final map = await _getBlockedUsersMap(); map.remove(id); await _saveBlockedUsersMap(map); _notifyBlockedUsersChanged(); } }
Save
cmd:
run