Initial commit: SubManager Flutter App

주요 구현 완료 기능:
- 구독 관리 (추가/편집/삭제/카테고리 분류)
- 이벤트 할인 시스템 (기본값 자동 설정)
- SMS 자동 스캔 및 구독 정보 추출
- 알림 시스템 (타임존 처리 안정화)
- 환율 변환 지원 (KRW/USD)
- 반응형 UI 및 애니메이션
- 다국어 지원 (한국어/영어)

버그 수정:
- NotificationService tz.local 초기화 오류 해결
- MainScreenSummaryCard 레이아웃 오버플로우 수정
- 구독 추가 시 LateInitializationError 완전 해결

🤖 Generated with Claude Code

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
JiWoong Sul
2025-07-09 14:29:53 +09:00
commit 8619e96739
177 changed files with 23085 additions and 0 deletions

View File

@@ -0,0 +1,171 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:local_auth/local_auth.dart';
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import '../services/notification_service.dart';
import '../providers/subscription_provider.dart';
import 'package:hive_flutter/hive_flutter.dart';
import 'package:flutter/foundation.dart' show kIsWeb;
class AppLockProvider extends ChangeNotifier {
final Box<bool> _appLockBox;
final LocalAuthentication _localAuth = LocalAuthentication();
final FlutterSecureStorage _secureStorage = const FlutterSecureStorage();
static const String _isEnabledKey = 'app_lock_enabled';
bool _isEnabled = false;
bool _isLocked = false;
bool _isBiometricEnabled = false;
bool _isBiometricAvailable = false;
AppLockProvider(this._appLockBox) {
_init();
}
bool get isEnabled => _isEnabled;
bool get isLocked => _isLocked;
bool get isBiometricEnabled => _isBiometricEnabled;
bool get isBiometricAvailable => _isBiometricAvailable;
Future<void> _init() async {
if (!kIsWeb) {
_isBiometricAvailable = await _localAuth.canCheckBiometrics;
} else {
_isBiometricAvailable = false;
}
_isLocked = _appLockBox.get('isLocked', defaultValue: false) ?? false;
await _loadState();
notifyListeners();
}
Future<void> _loadState() async {
final value = await _secureStorage.read(key: _isEnabledKey);
_isEnabled = value == 'true';
_isLocked = _appLockBox.get('isLocked', defaultValue: false) ?? false;
await _loadSettings();
notifyListeners();
}
Future<void> _loadSettings() async {
try {
final biometricEnabled =
await _secureStorage.read(key: 'biometric_enabled');
_isBiometricEnabled = biometricEnabled == 'true';
notifyListeners();
} catch (e) {
debugPrint('설정 로드 중 오류 발생: $e');
}
}
Future<bool> authenticate() async {
if (kIsWeb) {
_isLocked = false;
notifyListeners();
return true;
}
try {
final canCheck = await _checkBiometrics();
if (!canCheck) {
_isLocked = false;
notifyListeners();
return true;
}
final authenticated = await _localAuth.authenticate(
localizedReason: '생체 인증을 사용하여 앱 잠금을 해제하세요.',
options: const AuthenticationOptions(
stickyAuth: true,
biometricOnly: true,
),
);
if (authenticated) {
_isLocked = false;
await _appLockBox.put('isLocked', false);
notifyListeners();
}
return authenticated;
} catch (e) {
_isLocked = false;
notifyListeners();
return true;
}
}
Future<bool> _checkBiometrics() async {
if (kIsWeb) return false;
try {
return await _localAuth.canCheckBiometrics;
} catch (e) {
return false;
}
}
Future<void> toggleBiometricAuth() async {
if (kIsWeb) {
_isBiometricEnabled = false;
await _appLockBox.put('biometric_enabled', false);
notifyListeners();
return;
}
try {
final canCheck = await _checkBiometrics();
if (!canCheck) {
_isBiometricEnabled = false;
await _appLockBox.put('biometric_enabled', false);
notifyListeners();
return;
}
_isBiometricEnabled = !_isBiometricEnabled;
await _secureStorage.write(
key: 'biometric_enabled',
value: _isBiometricEnabled.toString(),
);
await _appLockBox.put('biometric_enabled', _isBiometricEnabled);
notifyListeners();
} catch (e) {
_isBiometricEnabled = false;
await _appLockBox.put('biometric_enabled', false);
notifyListeners();
}
}
void lock() {
if (_isBiometricEnabled) {
_isLocked = true;
notifyListeners();
}
}
Future<void> unlock() async {
_isLocked = false;
await _appLockBox.put('isLocked', false);
notifyListeners();
}
Future<void> enable() async {
_isEnabled = true;
await _secureStorage.write(key: _isEnabledKey, value: 'true');
notifyListeners();
}
Future<void> disable() async {
_isEnabled = false;
await _secureStorage.write(key: _isEnabledKey, value: 'false');
notifyListeners();
}
Future<void> refreshNotifications(BuildContext context) async {
final subscriptionProvider = Provider.of<SubscriptionProvider>(
context,
listen: false,
);
for (var subscription in subscriptionProvider.subscriptions) {
await NotificationService.scheduleSubscriptionNotification(subscription);
}
}
}