i8n과 광고 수정
This commit is contained in:
@@ -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'),
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user