feat(settings): sound & vibration toggle; themed settings screen

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-06-13 18:01:23 +09:00
parent 3ca038ec65
commit 498fb6af83
3 changed files with 53 additions and 31 deletions
+2 -1
View File
@@ -59,5 +59,6 @@
"removeAdsDescription": "Removes banners and full-screen ads. Reward ads stay available.",
"restorePurchases": "Restore purchases",
"adsRemovedThanks": "Ads removed — thank you!",
"purchaseUnavailable": "Purchases are unavailable right now."
"purchaseUnavailable": "Purchases are unavailable right now.",
"soundAndVibration": "Sound & vibration"
}
+2 -1
View File
@@ -31,5 +31,6 @@
"removeAdsDescription": "배너와 전면 광고를 제거합니다. 보상형 광고는 계속 사용할 수 있습니다.",
"restorePurchases": "구매 복원",
"adsRemovedThanks": "광고가 제거되었습니다 — 감사합니다!",
"purchaseUnavailable": "지금은 구매를 사용할 수 없습니다."
"purchaseUnavailable": "지금은 구매를 사용할 수 없습니다.",
"soundAndVibration": "소리 및 진동"
}
+49 -29
View File
@@ -2,8 +2,10 @@
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../game/models/season.dart';
import '../../l10n/gen/app_localizations.dart';
import '../../state/providers.dart';
import '../widgets/season_background.dart';
class SettingsScreen extends ConsumerWidget {
const SettingsScreen({super.key});
@@ -12,6 +14,7 @@ class SettingsScreen extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) {
final l10n = AppLocalizations.of(context)!;
final adsRemoved = ref.watch(adsRemovedProvider);
final soundOn = ref.watch(soundEnabledProvider);
final iap = ref.read(iapServiceProvider);
ref.listen<bool>(adsRemovedProvider, (prev, next) {
@@ -22,37 +25,54 @@ class SettingsScreen extends ConsumerWidget {
}
});
return Scaffold(
appBar: AppBar(title: Text(l10n.settings)),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
ListTile(
title: Text(l10n.removeAds),
subtitle: Text(l10n.removeAdsDescription),
trailing: adsRemoved
? const Icon(Icons.check_circle, color: Colors.green)
: Text(iap.product?.price ?? ''),
onTap: adsRemoved
? null
: () async {
if (!iap.available || iap.product == null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(l10n.purchaseUnavailable)),
);
return;
}
await iap.buyRemoveAds();
},
return Stack(
fit: StackFit.expand,
children: [
const SeasonBackground(theme: SeasonTheme.fallback),
Scaffold(
backgroundColor: Colors.transparent,
appBar: AppBar(
backgroundColor: Colors.transparent,
title: Text(l10n.settings),
),
const Divider(),
ListTile(
leading: const Icon(Icons.restore),
title: Text(l10n.restorePurchases),
onTap: () => iap.restorePurchases(),
body: ListView(
padding: const EdgeInsets.all(16),
children: [
SwitchListTile(
title: Text(l10n.soundAndVibration),
value: soundOn,
onChanged: (v) =>
ref.read(soundEnabledProvider.notifier).set(v),
),
const Divider(),
ListTile(
title: Text(l10n.removeAds),
subtitle: Text(l10n.removeAdsDescription),
trailing: adsRemoved
? const Icon(Icons.check_circle, color: Colors.green)
: Text(iap.product?.price ?? ''),
onTap: adsRemoved
? null
: () async {
if (!iap.available || iap.product == null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(l10n.purchaseUnavailable)),
);
return;
}
await iap.buyRemoveAds();
},
),
const Divider(),
ListTile(
leading: const Icon(Icons.restore),
title: Text(l10n.restorePurchases),
onTap: () => iap.restorePurchases(),
),
],
),
],
),
),
],
);
}
}