i8n과 광고 수정

This commit is contained in:
JiWoong Sul
2025-12-07 21:14:54 +09:00
parent 64da0c5fd3
commit bac4acf9a3
25 changed files with 640 additions and 382 deletions

View File

@@ -13,6 +13,7 @@ import '../widgets/analysis/subscription_pie_chart_card.dart';
import '../widgets/analysis/total_expense_summary_card.dart';
import '../widgets/analysis/monthly_expense_chart_card.dart';
import '../widgets/analysis/event_analysis_card.dart';
import '../theme/ui_constants.dart';
enum AnalysisCardFilterType { all, unassigned, card }
@@ -324,21 +325,11 @@ class _AnalysisScreenState extends State<AnalysisScreen>
controller: _scrollController,
physics: const BouncingScrollPhysics(),
slivers: <Widget>[
SliverToBoxAdapter(
child: SizedBox(
height: kToolbarHeight + MediaQuery.of(context).padding.top,
),
SliverPadding(
padding: const EdgeInsets.only(top: UIConstants.pageTopPadding),
sliver: _buildCardFilterSection(context, cardProvider),
),
// 네이티브 광고 위젯
SliverToBoxAdapter(
child: _buildAnimatedAd(),
),
const AnalysisScreenSpacer(),
_buildCardFilterSection(context, cardProvider),
const AnalysisScreenSpacer(),
// 1. 구독 비율 파이 차트
@@ -349,6 +340,13 @@ class _AnalysisScreenState extends State<AnalysisScreen>
const AnalysisScreenSpacer(),
// 네이티브 광고 위젯 (구독 비율 차트 하단)
SliverToBoxAdapter(
child: _buildAnimatedAd(),
),
const AnalysisScreenSpacer(),
// 2. 총 지출 요약 카드
TotalExpenseSummaryCard(
key: ValueKey('total_expense_$_lastDataHash'),

View File

@@ -1,5 +1,7 @@
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../l10n/app_localizations.dart';
import '../providers/app_lock_provider.dart';
// import '../theme/app_colors.dart';
@@ -8,6 +10,7 @@ class AppLockScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
final loc = AppLocalizations.of(context);
return Scaffold(
body: Center(
child: Column(
@@ -20,7 +23,7 @@ class AppLockScreen extends StatelessWidget {
),
const SizedBox(height: 24),
Text(
'앱이 잠겨 있습니다',
loc.appLocked,
style: TextStyle(
fontSize: 24,
fontWeight: FontWeight.bold,
@@ -29,7 +32,7 @@ class AppLockScreen extends StatelessWidget {
),
const SizedBox(height: 16),
Text(
'생체 인증으로 잠금을 해제하세요',
loc.appLockDesc,
style: TextStyle(
fontSize: 16,
color: Theme.of(context).colorScheme.onSurfaceVariant,
@@ -45,7 +48,7 @@ class AppLockScreen extends StatelessWidget {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
'인증에 실패했습니다. 다시 시도해주세요.',
loc.authenticationFailed,
style: TextStyle(
color: cs.onPrimary,
),
@@ -56,7 +59,7 @@ class AppLockScreen extends StatelessWidget {
}
},
icon: const Icon(Icons.fingerprint),
label: const Text('생체 인증으로 잠금 해제'),
label: Text(loc.unlockWithBiometric),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(
horizontal: 24,

View File

@@ -41,10 +41,11 @@ class _CategoryManagementScreenState extends State<CategoryManagementScreen> {
@override
Widget build(BuildContext context) {
final loc = AppLocalizations.of(context);
return Scaffold(
appBar: AppBar(
title: Text(
'카테고리 관리',
loc.categoryManagement,
style: Theme.of(context).textTheme.titleLarge?.copyWith(
color: Theme.of(context).colorScheme.onPrimary,
),
@@ -67,7 +68,7 @@ class _CategoryManagementScreenState extends State<CategoryManagementScreen> {
TextFormField(
controller: _nameController,
decoration: InputDecoration(
labelText: '카테고리 이름',
labelText: loc.categoryName,
labelStyle: TextStyle(
color: Theme.of(context)
.colorScheme
@@ -76,7 +77,7 @@ class _CategoryManagementScreenState extends State<CategoryManagementScreen> {
),
validator: (value) {
if (value == null || value.isEmpty) {
return '카테고리 이름을 입력하세요';
return loc.categoryNameRequired;
}
return null;
},
@@ -85,7 +86,7 @@ class _CategoryManagementScreenState extends State<CategoryManagementScreen> {
DropdownButtonFormField<String>(
initialValue: _selectedColor,
decoration: InputDecoration(
labelText: '색상 선택',
labelText: loc.selectColor,
labelStyle: TextStyle(
color: Theme.of(context)
.colorScheme
@@ -144,7 +145,7 @@ class _CategoryManagementScreenState extends State<CategoryManagementScreen> {
DropdownButtonFormField<String>(
initialValue: _selectedIcon,
decoration: InputDecoration(
labelText: '아이콘 선택',
labelText: loc.selectIcon,
labelStyle: TextStyle(
color: Theme.of(context)
.colorScheme
@@ -154,35 +155,35 @@ class _CategoryManagementScreenState extends State<CategoryManagementScreen> {
items: [
DropdownMenuItem(
value: 'subscriptions',
child: Text('구독',
child: Text(loc.subscription,
style: TextStyle(
color: Theme.of(context)
.colorScheme
.onSurface))),
DropdownMenuItem(
value: 'movie',
child: Text('영화',
child: Text(loc.movie,
style: TextStyle(
color: Theme.of(context)
.colorScheme
.onSurface))),
DropdownMenuItem(
value: 'music_note',
child: Text('음악',
child: Text(loc.music,
style: TextStyle(
color: Theme.of(context)
.colorScheme
.onSurface))),
DropdownMenuItem(
value: 'fitness_center',
child: Text('운동',
child: Text(loc.exercise,
style: TextStyle(
color: Theme.of(context)
.colorScheme
.onSurface))),
DropdownMenuItem(
value: 'shopping_cart',
child: Text('쇼핑',
child: Text(loc.shopping,
style: TextStyle(
color: Theme.of(context)
.colorScheme
@@ -197,7 +198,7 @@ class _CategoryManagementScreenState extends State<CategoryManagementScreen> {
const SizedBox(height: 16),
ElevatedButton(
onPressed: _addCategory,
child: const Text('카테고리 추가'),
child: Text(loc.addCategory),
),
],
),

View File

@@ -6,7 +6,6 @@ import 'dart:io';
import '../services/notification_service.dart';
// import '../widgets/glassmorphism_card.dart';
// import '../theme/app_colors.dart';
import '../widgets/native_ad_widget.dart';
import '../widgets/common/snackbar/app_snackbar.dart';
import '../l10n/app_localizations.dart';
import '../providers/locale_provider.dart';
@@ -17,6 +16,7 @@ import '../theme/adaptive_theme.dart';
import '../widgets/common/layout/page_container.dart';
import '../theme/color_scheme_ext.dart';
import '../widgets/app_navigator.dart';
import '../theme/ui_constants.dart';
class SettingsScreen extends StatelessWidget {
const SettingsScreen({super.key});
@@ -86,23 +86,16 @@ class SettingsScreen extends StatelessWidget {
child: PageContainer(
padding: EdgeInsets.zero,
child: ListView(
padding: const EdgeInsets.symmetric(horizontal: 16),
padding: const EdgeInsets.fromLTRB(
16,
UIConstants.pageTopPadding,
16,
0,
),
children: [
// toolbar 높이 추가
SizedBox(
height: kToolbarHeight + MediaQuery.of(context).padding.top,
),
// 광고 위젯 추가
const NativeAdWidget(
key: ValueKey('settings_ad'),
useOuterPadding: true,
),
const SizedBox(height: 16),
// 테마 모드 설정
Card(
margin:
const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
margin: const EdgeInsets.fromLTRB(16, 0, 16, 8),
elevation: 1,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
@@ -191,7 +184,7 @@ class SettingsScreen extends StatelessWidget {
leading: Icon(Icons.color_lens,
color: cs.onSurfaceVariant),
title: Text(
'테마',
loc.theme,
style: TextStyle(color: cs.onSurface),
),
),
@@ -360,14 +353,14 @@ class SettingsScreen extends StatelessWidget {
.colorScheme
.onSurfaceVariant),
title: Text(
'정확 알람 권한(알람 및 리마인더)',
loc.exactAlarmPermission,
style: TextStyle(
color: Theme.of(context)
.colorScheme
.onSurface),
),
subtitle: Text(
'정확한 시각에 알림을 보장하려면 권한이 필요합니다.',
loc.exactAlarmPermissionDesc,
style: TextStyle(
color: Theme.of(context)
.colorScheme
@@ -385,19 +378,19 @@ class SettingsScreen extends StatelessWidget {
if (ok || recheck) {
AppSnackBar.showSuccess(
context: context,
message: '권한이 허용되었습니다.',
message: loc.permissionGranted,
);
} else {
AppSnackBar.showInfo(
context: context,
message:
'설정에서 "알람 및 리마인더"를 허용해 주세요.',
loc.allowAlarmsInSettings,
);
}
(context as Element).markNeedsBuild();
}
},
child: const Text('허용 요청'),
child: Text(loc.requestPermission),
),
);
},
@@ -747,8 +740,8 @@ class SettingsScreen extends StatelessWidget {
child: OutlinedButton.icon(
icon: const Icon(Icons
.notifications_active),
label:
const Text('테스트 알림'),
label: Text(
loc.testNotification),
onPressed: () {
NotificationService
.showTestPaymentNotification();

View File

@@ -9,6 +9,7 @@ import '../l10n/app_localizations.dart';
import '../widgets/payment_card/payment_card_form_sheet.dart';
import '../routes/app_routes.dart';
import '../models/payment_card_suggestion.dart';
import '../theme/ui_constants.dart';
class SmsScanScreen extends StatefulWidget {
const SmsScanScreen({super.key});
@@ -56,7 +57,7 @@ class _SmsScanScreenState extends State<SmsScanScreen> {
if (_controller.scannedSubscriptions.isEmpty) {
return ScanInitialWidget(
onScanPressed: () => _controller.scanSms(context),
onScanPressed: () => _controller.startScan(context),
errorMessage: _controller.errorMessage,
);
}
@@ -75,7 +76,7 @@ class _SmsScanScreenState extends State<SmsScanScreen> {
}
});
return ScanInitialWidget(
onScanPressed: () => _controller.scanSms(context),
onScanPressed: () => _controller.startScan(context),
errorMessage: _controller.errorMessage,
);
}
@@ -104,7 +105,7 @@ class _SmsScanScreenState extends State<SmsScanScreen> {
onPaymentCardChanged: _controller.setSelectedPaymentCardId,
enableServiceNameEditing: _controller.isServiceNameEditable,
onServiceNameChanged: _controller.isServiceNameEditable
? _controller.updateCurrentServiceName
? (value) => _controller.updateCurrentServiceName(context, value)
: null,
onAddCard: () async {
final newCardId = await PaymentCardFormSheet.show(context);
@@ -160,22 +161,83 @@ class _SmsScanScreenState extends State<SmsScanScreen> {
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
controller: _scrollController,
padding: EdgeInsets.zero,
child: Column(
children: [
// toolbar 높이 추가
SizedBox(
height: kToolbarHeight + MediaQuery.of(context).padding.top,
return Stack(
children: [
SingleChildScrollView(
controller: _scrollController,
padding: EdgeInsets.zero,
child: Padding(
padding: const EdgeInsets.only(top: UIConstants.pageTopPadding),
child: Column(
children: [
_buildContent(),
// FloatingNavigationBar를 위한 충분한 하단 여백
SizedBox(
height: 120 + MediaQuery.of(context).padding.bottom,
),
],
),
),
_buildContent(),
// FloatingNavigationBar를 위한 충분한 하단 여백
SizedBox(
height: 120 + MediaQuery.of(context).padding.bottom,
),
if (_controller.isAdInProgress)
Positioned.fill(
child: IgnorePointer(
child: Stack(
children: [
Container(
color: Theme.of(context)
.colorScheme
.surface
.withValues(alpha: 0.4),
),
Center(
child: DecoratedBox(
decoration: BoxDecoration(
color: Theme.of(context)
.colorScheme
.surfaceContainerHighest
.withValues(alpha: 0.9),
borderRadius: BorderRadius.circular(16),
),
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 20, vertical: 16),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
width: 18,
height: 18,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(
Theme.of(context).colorScheme.primary,
),
),
),
const SizedBox(width: 12),
Text(
AppLocalizations.of(context).scanningMessages,
style: Theme.of(context)
.textTheme
.titleMedium
?.copyWith(
color:
Theme.of(context).colorScheme.onSurface,
fontWeight: FontWeight.w700,
),
textAlign: TextAlign.center,
),
],
),
),
),
),
],
),
),
),
],
),
],
);
}
}