style: apply dart format across project

This commit is contained in:
JiWoong Sul
2025-09-07 19:33:11 +09:00
parent f812d4b9fd
commit d1a6cb9fe3
101 changed files with 3123 additions and 2574 deletions

View File

@@ -4,42 +4,42 @@ import 'dart:io' show Platform;
/// 햅틱 피드백을 관리하는 헬퍼 클래스
class HapticFeedbackHelper {
static bool _isEnabled = true;
/// 햅틱 피드백 활성화 여부 설정
static void setEnabled(bool enabled) {
_isEnabled = enabled;
}
/// 가벼운 햅틱 피드백
static Future<void> lightImpact() async {
if (!_isEnabled || !_isPlatformSupported()) return;
await HapticFeedback.lightImpact();
}
/// 중간 강도 햅틱 피드백
static Future<void> mediumImpact() async {
if (!_isEnabled || !_isPlatformSupported()) return;
await HapticFeedback.mediumImpact();
}
/// 강한 햅틱 피드백
static Future<void> heavyImpact() async {
if (!_isEnabled || !_isPlatformSupported()) return;
await HapticFeedback.heavyImpact();
}
/// 선택 햅틱 피드백 (iOS의 경우 Taptic Engine)
static Future<void> selectionClick() async {
if (!_isEnabled || !_isPlatformSupported()) return;
await HapticFeedback.selectionClick();
}
/// 진동 패턴 (Android)
static Future<void> vibrate({int duration = 50}) async {
if (!_isEnabled || !_isPlatformSupported()) return;
await HapticFeedback.vibrate();
}
/// 성공 피드백 패턴
static Future<void> success() async {
if (!_isEnabled || !_isPlatformSupported()) return;
@@ -47,7 +47,7 @@ class HapticFeedbackHelper {
await Future.delayed(const Duration(milliseconds: 100));
await HapticFeedback.lightImpact();
}
/// 에러 피드백 패턴
static Future<void> error() async {
if (!_isEnabled || !_isPlatformSupported()) return;
@@ -55,13 +55,13 @@ class HapticFeedbackHelper {
await Future.delayed(const Duration(milliseconds: 100));
await HapticFeedback.heavyImpact();
}
/// 경고 피드백 패턴
static Future<void> warning() async {
if (!_isEnabled || !_isPlatformSupported()) return;
await HapticFeedback.mediumImpact();
}
/// 플랫폼이 햅틱 피드백을 지원하는지 확인
static bool _isPlatformSupported() {
try {
@@ -71,4 +71,4 @@ class HapticFeedbackHelper {
return false;
}
}
}
}

View File

@@ -7,19 +7,19 @@ class MemoryManager {
static final MemoryManager _instance = MemoryManager._internal();
factory MemoryManager() => _instance;
MemoryManager._internal();
// 캐시 관리
final Map<String, _CacheEntry> _cache = {};
final int _maxCacheSize = 100;
final Duration _defaultTTL = const Duration(minutes: 5);
// 이미지 캐시 관리
static const int maxImageCacheSize = 50 * 1024 * 1024; // 50MB
static const int maxImageCacheCount = 100;
// 위젯 참조 추적
final Map<String, WeakReference<State>> _widgetReferences = {};
/// 캐시에 데이터 저장
void cacheData<T>({
required String key,
@@ -27,32 +27,32 @@ class MemoryManager {
Duration? ttl,
}) {
_cleanupExpiredCache();
if (_cache.length >= _maxCacheSize) {
_evictOldestEntry();
}
_cache[key] = _CacheEntry(
data: data,
timestamp: DateTime.now(),
ttl: ttl ?? _defaultTTL,
);
}
/// 캐시에서 데이터 가져오기
T? getCachedData<T>(String key) {
final entry = _cache[key];
if (entry == null) return null;
if (entry.isExpired) {
_cache.remove(key);
return null;
}
entry.lastAccess = DateTime.now();
return entry.data as T?;
}
/// 캐시 비우기
void clearCache() {
_cache.clear();
@@ -60,53 +60,52 @@ class MemoryManager {
print('🧹 메모리 캐시가 비워졌습니다.');
}
}
/// 특정 패턴의 캐시 제거
void clearCacheByPattern(String pattern) {
final keysToRemove = _cache.keys
.where((key) => key.contains(pattern))
.toList();
final keysToRemove =
_cache.keys.where((key) => key.contains(pattern)).toList();
for (final key in keysToRemove) {
_cache.remove(key);
}
}
/// 만료된 캐시 정리
void _cleanupExpiredCache() {
final expiredKeys = _cache.entries
.where((entry) => entry.value.isExpired)
.map((entry) => entry.key)
.toList();
for (final key in expiredKeys) {
_cache.remove(key);
}
}
/// 가장 오래된 캐시 항목 제거
void _evictOldestEntry() {
if (_cache.isEmpty) return;
var oldestKey = _cache.keys.first;
var oldestTime = _cache[oldestKey]!.lastAccess;
for (final entry in _cache.entries) {
if (entry.value.lastAccess.isBefore(oldestTime)) {
oldestKey = entry.key;
oldestTime = entry.value.lastAccess;
}
}
_cache.remove(oldestKey);
}
/// 이미지 캐시 최적화
static void optimizeImageCache() {
PaintingBinding.instance.imageCache.maximumSize = maxImageCacheCount;
PaintingBinding.instance.imageCache.maximumSizeBytes = maxImageCacheSize;
}
/// 이미지 캐시 상태 확인
static ImageCacheStatus getImageCacheStatus() {
final cache = PaintingBinding.instance.imageCache;
@@ -117,7 +116,7 @@ class MemoryManager {
maximumSizeBytes: cache.maximumSizeBytes,
);
}
/// 이미지 캐시 비우기
static void clearImageCache() {
PaintingBinding.instance.imageCache.clear();
@@ -126,24 +125,22 @@ class MemoryManager {
print('🖼️ 이미지 캐시가 비워졌습니다.');
}
}
/// 위젯 참조 추적
void trackWidget(String key, State widget) {
_widgetReferences[key] = WeakReference(widget);
}
/// 위젯 참조 제거
void untrackWidget(String key) {
_widgetReferences.remove(key);
}
/// 살아있는 위젯 수 확인
int getAliveWidgetCount() {
return _widgetReferences.values
.where((ref) => ref.target != null)
.length;
return _widgetReferences.values.where((ref) => ref.target != null).length;
}
/// 메모리 압박 시 대응
void handleMemoryPressure() {
// 캐시 50% 제거
@@ -151,43 +148,43 @@ class MemoryManager {
for (final key in keysToRemove) {
_cache.remove(key);
}
// 이미지 캐시 축소
final imageCache = PaintingBinding.instance.imageCache;
imageCache.maximumSize = maxImageCacheCount ~/ 2;
imageCache.maximumSizeBytes = maxImageCacheSize ~/ 2;
if (kDebugMode) {
print('⚠️ 메모리 압박 대응: 캐시 크기 감소');
}
}
/// 자동 메모리 정리 시작
Timer? _cleanupTimer;
void startAutoCleanup({Duration interval = const Duration(minutes: 1)}) {
_cleanupTimer?.cancel();
_cleanupTimer = Timer.periodic(interval, (_) {
_cleanupExpiredCache();
// 죽은 위젯 참조 제거
final deadKeys = _widgetReferences.entries
.where((entry) => entry.value.target == null)
.map((entry) => entry.key)
.toList();
for (final key in deadKeys) {
_widgetReferences.remove(key);
}
});
}
/// 자동 메모리 정리 중지
void stopAutoCleanup() {
_cleanupTimer?.cancel();
_cleanupTimer = null;
}
/// 메모리 사용량 리포트
Map<String, dynamic> getMemoryReport() {
return {
@@ -206,13 +203,13 @@ class _CacheEntry {
final DateTime timestamp;
final Duration ttl;
DateTime lastAccess;
_CacheEntry({
required this.data,
required this.timestamp,
required this.ttl,
}) : lastAccess = timestamp;
bool get isExpired => DateTime.now().difference(timestamp) > ttl;
}
@@ -222,25 +219,26 @@ class ImageCacheStatus {
final int maximumSize;
final int currentSizeBytes;
final int maximumSizeBytes;
ImageCacheStatus({
required this.currentSize,
required this.maximumSize,
required this.currentSizeBytes,
required this.maximumSizeBytes,
});
double get sizeUsagePercentage => (currentSize / maximumSize) * 100;
double get bytesUsagePercentage => (currentSizeBytes / maximumSizeBytes) * 100;
double get bytesUsagePercentage =>
(currentSizeBytes / maximumSizeBytes) * 100;
Map<String, dynamic> toJson() => {
'currentSize': currentSize,
'maximumSize': maximumSize,
'currentSizeBytes': currentSizeBytes,
'maximumSizeBytes': maximumSizeBytes,
'sizeUsagePercentage': sizeUsagePercentage.toStringAsFixed(2),
'bytesUsagePercentage': bytesUsagePercentage.toStringAsFixed(2),
};
'currentSize': currentSize,
'maximumSize': maximumSize,
'currentSizeBytes': currentSizeBytes,
'maximumSizeBytes': maximumSizeBytes,
'sizeUsagePercentage': sizeUsagePercentage.toStringAsFixed(2),
'bytesUsagePercentage': bytesUsagePercentage.toStringAsFixed(2),
};
}
/// 메모리 효율적인 리스트 뷰
@@ -249,7 +247,7 @@ class MemoryEfficientListView<T> extends StatefulWidget {
final Widget Function(BuildContext, T) itemBuilder;
final int cacheExtent;
final ScrollPhysics? physics;
const MemoryEfficientListView({
super.key,
required this.items,
@@ -257,23 +255,21 @@ class MemoryEfficientListView<T> extends StatefulWidget {
this.cacheExtent = 250,
this.physics,
});
@override
State<MemoryEfficientListView<T>> createState() =>
State<MemoryEfficientListView<T>> createState() =>
_MemoryEfficientListViewState<T>();
}
class _MemoryEfficientListViewState<T>
extends State<MemoryEfficientListView<T>>
class _MemoryEfficientListViewState<T> extends State<MemoryEfficientListView<T>>
with AutomaticKeepAliveClientMixin {
@override
bool get wantKeepAlive => false;
@override
Widget build(BuildContext context) {
super.build(context);
return ListView.builder(
itemCount: widget.items.length,
cacheExtent: widget.cacheExtent.toDouble(),
@@ -283,4 +279,4 @@ class _MemoryEfficientListViewState<T>
},
);
}
}
}

View File

@@ -5,19 +5,20 @@ import 'dart:async';
/// 성능 최적화를 위한 유틸리티 클래스
class PerformanceOptimizer {
static final PerformanceOptimizer _instance = PerformanceOptimizer._internal();
static final PerformanceOptimizer _instance =
PerformanceOptimizer._internal();
factory PerformanceOptimizer() => _instance;
PerformanceOptimizer._internal();
// 프레임 타이밍 정보
final List<FrameTiming> _frameTimings = [];
bool _isMonitoring = false;
/// 프레임 성능 모니터링 시작
void startFrameMonitoring() {
if (_isMonitoring) return;
_isMonitoring = true;
SchedulerBinding.instance.addTimingsCallback((timings) {
_frameTimings.addAll(timings);
// 최근 100개 프레임만 유지
@@ -26,27 +27,27 @@ class PerformanceOptimizer {
}
});
}
/// 프레임 성능 모니터링 중지
void stopFrameMonitoring() {
if (!_isMonitoring) return;
_isMonitoring = false;
SchedulerBinding.instance.addTimingsCallback((_) {});
}
/// 평균 FPS 계산
double getAverageFPS() {
if (_frameTimings.isEmpty) return 0.0;
double totalDuration = 0;
for (final timing in _frameTimings) {
totalDuration += timing.totalSpan.inMicroseconds;
}
final averageDuration = totalDuration / _frameTimings.length;
return 1000000 / averageDuration; // microseconds to FPS
}
/// 메모리 사용량 모니터링
static Future<MemoryInfo> getMemoryInfo() async {
// Flutter에서는 직접적인 메모리 사용량 측정이 제한적이므로
@@ -57,7 +58,7 @@ class PerformanceOptimizer {
capacity: imageCache.maximumSizeBytes,
);
}
/// 위젯 재빌드 최적화를 위한 데바운서
static Timer? _debounceTimer;
static void debounce(
@@ -67,7 +68,7 @@ class PerformanceOptimizer {
_debounceTimer?.cancel();
_debounceTimer = Timer(delay, callback);
}
/// 스로틀링 - 지정된 시간 간격으로만 실행
static DateTime? _lastThrottleTime;
static void throttle(
@@ -81,7 +82,7 @@ class PerformanceOptimizer {
callback();
}
}
/// 무거운 연산을 별도 Isolate에서 실행
static Future<T> runInIsolate<T>(
ComputeCallback<dynamic, T> callback,
@@ -89,7 +90,7 @@ class PerformanceOptimizer {
) async {
return await compute(callback, parameter);
}
/// 레이지 로딩을 위한 페이지네이션 헬퍼
static List<T> paginate<T>({
required List<T> items,
@@ -98,13 +99,14 @@ class PerformanceOptimizer {
}) {
final startIndex = page * pageSize;
final endIndex = (startIndex + pageSize).clamp(0, items.length);
if (startIndex >= items.length) return [];
return items.sublist(startIndex, endIndex);
}
/// 이미지 최적화 - 메모리 효율적인 크기로 조정
static double getOptimalImageSize(BuildContext context, {
static double getOptimalImageSize(
BuildContext context, {
required double originalSize,
double maxSize = 1000,
}) {
@@ -113,29 +115,29 @@ class PerformanceOptimizer {
final maxDimension = screenSize.width > screenSize.height
? screenSize.width
: screenSize.height;
final optimalSize = (maxDimension * devicePixelRatio).clamp(100.0, maxSize);
return optimalSize < originalSize ? optimalSize : originalSize;
}
/// 위젯 키 최적화
static Key generateOptimizedKey(String prefix, dynamic identifier) {
return ValueKey('${prefix}_$identifier');
}
/// 애니메이션 최적화 - 보이지 않는 애니메이션 중지
static bool shouldAnimateWidget(BuildContext context) {
final mediaQuery = MediaQuery.of(context);
return !mediaQuery.disableAnimations && mediaQuery.accessibleNavigation;
}
/// 스크롤 성능 최적화
static ScrollPhysics getOptimizedScrollPhysics() {
return const BouncingScrollPhysics(
parent: AlwaysScrollableScrollPhysics(),
);
}
/// 빌드 최적화를 위한 const 위젯 권장사항 체크
static void checkConstOptimization() {
if (kDebugMode) {
@@ -147,16 +149,16 @@ class PerformanceOptimizer {
print('5. 애니메이션은 AnimatedBuilder 사용');
}
}
/// 메모리 누수 감지 헬퍼
static final Map<String, int> _widgetCounts = {};
static void trackWidget(String widgetName, bool isCreated) {
if (!kDebugMode) return;
_widgetCounts[widgetName] = (_widgetCounts[widgetName] ?? 0) +
(isCreated ? 1 : -1);
_widgetCounts[widgetName] =
(_widgetCounts[widgetName] ?? 0) + (isCreated ? 1 : -1);
// 위젯이 비정상적으로 많이 생성되면 경고
if ((_widgetCounts[widgetName] ?? 0) > 100) {
print('⚠️ 경고: $widgetName 위젯이 100개 이상 생성됨. 메모리 누수 가능성!');
@@ -168,16 +170,18 @@ class PerformanceOptimizer {
class MemoryInfo {
final int currentUsage;
final int capacity;
MemoryInfo({
required this.currentUsage,
required this.capacity,
});
double get usagePercentage => (currentUsage / capacity) * 100;
String get formattedUsage => '${(currentUsage / 1024 / 1024).toStringAsFixed(2)} MB';
String get formattedCapacity => '${(capacity / 1024 / 1024).toStringAsFixed(2)} MB';
String get formattedUsage =>
'${(currentUsage / 1024 / 1024).toStringAsFixed(2)} MB';
String get formattedCapacity =>
'${(capacity / 1024 / 1024).toStringAsFixed(2)} MB';
}
/// 성능 측정 데코레이터
@@ -187,7 +191,7 @@ class PerformanceMeasure {
required Future<T> Function() operation,
}) async {
if (!kDebugMode) return await operation();
final stopwatch = Stopwatch()..start();
try {
final result = await operation();
@@ -200,4 +204,4 @@ class PerformanceMeasure {
rethrow;
}
}
}
}

View File

@@ -2,23 +2,23 @@ import 'package:flutter/foundation.dart';
class PlatformHelper {
static bool get isWeb => kIsWeb;
static bool get isIOS {
if (kIsWeb) return false;
return defaultTargetPlatform == TargetPlatform.iOS;
}
static bool get isAndroid {
if (kIsWeb) return false;
return defaultTargetPlatform == TargetPlatform.android;
}
static bool get isMobile => isIOS || isAndroid;
static bool get isDesktop {
if (kIsWeb) return false;
return defaultTargetPlatform == TargetPlatform.linux ||
defaultTargetPlatform == TargetPlatform.macOS ||
defaultTargetPlatform == TargetPlatform.windows;
defaultTargetPlatform == TargetPlatform.macOS ||
defaultTargetPlatform == TargetPlatform.windows;
}
}
}

View File

@@ -50,4 +50,4 @@ class CategoryIconMapper {
return 16.0;
}
}
}
}

View File

@@ -26,7 +26,7 @@ class SmsDateFormatter {
) {
// 주기에 따라 다음 결제일 예측
DateTime? predictedDate = _predictNextBillingDate(date, billingCycle, now);
if (predictedDate != null) {
final daysUntil = predictedDate.difference(now).inDays;
return AppLocalizations.of(context).nextBillingDateEstimated(
@@ -34,7 +34,7 @@ class SmsDateFormatter {
daysUntil,
);
}
return '다음 결제일 확인 필요 (과거 날짜)';
}
@@ -78,7 +78,7 @@ class SmsDateFormatter {
// 다음 월간 결제일 계산
static DateTime _getNextMonthlyDate(DateTime lastDate, DateTime now) {
int day = lastDate.day;
// 현재 월의 마지막 날을 초과하는 경우 조정
final lastDay = DateTime(now.year, now.month + 1, 0).day;
if (day > lastDay) {
@@ -101,7 +101,7 @@ class SmsDateFormatter {
// 다음 연간 결제일 계산
static DateTime _getNextYearlyDate(DateTime lastDate, DateTime now) {
int day = lastDate.day;
// 해당 월의 마지막 날을 초과하는 경우 조정
final lastDay = DateTime(now.year, lastDate.month + 1, 0).day;
if (day > lastDay) {
@@ -162,4 +162,4 @@ class SmsDateFormatter {
static String getRepeatCountText(BuildContext context, int count) {
return AppLocalizations.of(context).repeatCountDetected(count);
}
}
}

View File

@@ -86,8 +86,8 @@ class SubscriptionCategoryHelper {
categorizedSubscriptions['shoppingEcommerce']!.add(subscription);
}
// 프로그래밍
else if (_isInCategory(subscription.serviceName,
LegacyServiceData.programmingServices)) {
else if (_isInCategory(
subscription.serviceName, LegacyServiceData.programmingServices)) {
if (!categorizedSubscriptions.containsKey('programming')) {
categorizedSubscriptions['programming'] = [];
}