ponshu-room-lite/lib/widgets/settings/backup_settings_section.dart

309 lines
11 KiB
Dart
Raw Normal View History

import 'package:flutter/material.dart';
import 'package:lucide_icons/lucide_icons.dart';
import '../../services/backup_service.dart';
import '../../theme/app_colors.dart';
class BackupSettingsSection extends StatefulWidget {
final String title;
const BackupSettingsSection({
super.key,
this.title = 'バックアップ・復元',
});
@override
State<BackupSettingsSection> createState() => _BackupSettingsSectionState();
}
enum _BackupState { idle, signingIn, signingOut, backingUp, restoring }
class _BackupSettingsSectionState extends State<BackupSettingsSection> {
final BackupService _backupService = BackupService();
_BackupState _state = _BackupState.idle;
@override
void initState() {
super.initState();
_initBackupService();
}
Future<void> _initBackupService() async {
await _backupService.init();
if (mounted) {
setState(() {});
}
}
Future<void> _signIn() async {
final messenger = ScaffoldMessenger.of(context);
setState(() => _state = _BackupState.signingIn);
final account = await _backupService.signIn();
if (mounted) {
setState(() => _state = _BackupState.idle);
if (account != null) {
messenger.showSnackBar(
SnackBar(content: Text('${account.email} で連携しました')),
);
} else {
messenger.showSnackBar(
const SnackBar(content: Text('連携がキャンセルされました')),
);
}
}
}
Future<void> _signOut() async {
final messenger = ScaffoldMessenger.of(context);
final confirmed = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: const Text('連携解除'),
content: const Text('Googleアカウントとの連携を解除しますか\n安全にログアウトできます。'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, false),
child: const Text('キャンセル'),
),
ElevatedButton(
onPressed: () => Navigator.pop(context, true),
child: const Text('解除'),
),
],
),
);
if (confirmed == true) {
setState(() => _state = _BackupState.signingOut);
await _backupService.signOut();
if (mounted) {
setState(() => _state = _BackupState.idle);
messenger.showSnackBar(
const SnackBar(content: Text('連携を解除しました')),
);
}
}
}
Future<void> _createBackup() async {
final messenger = ScaffoldMessenger.of(context);
setState(() => _state = _BackupState.backingUp);
final success = await _backupService.createBackup();
if (mounted) {
setState(() => _state = _BackupState.idle);
messenger.showSnackBar(
SnackBar(
content: Text(success ? 'バックアップが完了しました' : 'バックアップに失敗しました'),
// Snackbars can keep Green/Red for semantic clarity, or be neutral.
// User asked to remove Green/Red icons from the UI, but feedback (Snackbar) usually stays semantic.
// However, to be safe and "Washi", let's use Sumi (Black) for success?
// Or just leave snackbars as they are ephemeral. The request was likely about the visible static UI.
backgroundColor: success ? Colors.green : Colors.red,
),
);
}
}
Future<void> _confirmBackup() async {
final appColors = Theme.of(context).extension<AppColors>()!;
final confirmed = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: const Text('バックアップ'),
content: const Text('Google Driveに最新データのみ保存過去分は上書きされます。\n本当に続行しますか?'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, false),
child: const Text('キャンセル'),
),
ElevatedButton(
onPressed: () => Navigator.pop(context, true),
style: ElevatedButton.styleFrom(
backgroundColor: appColors.brandPrimary,
foregroundColor: appColors.surfaceSubtle,
),
child: const Text('バックアップ'),
),
],
),
);
if (confirmed == true && mounted) {
await _createBackup();
}
}
Future<void> _restoreBackup() async {
final messenger = ScaffoldMessenger.of(context);
final appColors = Theme.of(context).extension<AppColors>()!;
// Note: hasBackup check is async
final hasBackup = await _backupService.hasBackupOnDrive();
if (!hasBackup) {
if (mounted) {
messenger.showSnackBar(
const SnackBar(content: Text('バックアップファイルが見つかりません')),
);
}
return;
}
if (!mounted) return;
final confirmed = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: Row(
children: [
Icon(LucideIcons.alertTriangle, color: appColors.warning, size: 24),
const SizedBox(width: 8),
const Text('データ復元'),
],
),
content: const Text('現在のデータは上書きされます。\n削除されたデータは元に戻りません。\n\n本当に続行しますか?'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, false),
child: const Text('キャンセル'),
),
ElevatedButton(
onPressed: () => Navigator.pop(context, true),
style: ElevatedButton.styleFrom(
backgroundColor: appColors.warning,
foregroundColor: appColors.surfaceSubtle,
),
child: const Text('復元'),
),
],
),
);
if (confirmed == true && mounted) {
setState(() => _state = _BackupState.restoring);
final success = await _backupService.restoreBackup();
if (mounted) {
setState(() => _state = _BackupState.idle);
messenger.showSnackBar(
SnackBar(
content: Text(success ? '復元が完了しました' : '復元に失敗しました'),
backgroundColor: success ? Colors.green : Colors.red,
),
);
}
}
}
@override
Widget build(BuildContext context) {
final currentUser = _backupService.currentUser;
final isAnyProcessing = _state != _BackupState.idle;
final appColors = Theme.of(context).extension<AppColors>()!;
return Column(
children: [
_buildSectionHeader(context, widget.title, LucideIcons.cloud),
// Wi-Fi warning
Container(
margin: const EdgeInsets.only(bottom: 12),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: appColors.info.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: appColors.info.withValues(alpha: 0.3)),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(LucideIcons.wifi, color: appColors.info, size: 20),
const SizedBox(width: 12),
Expanded(
child: Text(
'Wi-Fi環境下での実行を推奨します\nデータ量が100MB以上になる可能性があります',
style: TextStyle(
fontSize: 12,
color: appColors.textSecondary,
fontWeight: FontWeight.w500,
),
),
),
],
),
),
Card(
color: appColors.surfaceSubtle,
elevation: 0,
margin: EdgeInsets.zero,
child: Column(
children: [
ListTile(
leading: Icon(
currentUser != null ? LucideIcons.checkCircle2 : LucideIcons.user,
color: currentUser != null ? appColors.success : appColors.iconSubtle,
),
title: Text(currentUser == null ? 'Googleアカウント連携' : currentUser.email,
style: TextStyle(color: appColors.textPrimary)),
subtitle: currentUser == null ? Text('Google Driveにバックアップ',
style: TextStyle(color: appColors.textSecondary)) : null,
trailing: (_state == _BackupState.signingIn || _state == _BackupState.signingOut)
? const SizedBox(width: 24, height: 24, child: CircularProgressIndicator(strokeWidth: 2))
: ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: currentUser == null ? appColors.brandPrimary : appColors.error,
foregroundColor: appColors.surfaceSubtle,
padding: const EdgeInsets.symmetric(horizontal: 16),
),
onPressed: isAnyProcessing ? null : (currentUser == null ? _signIn : _signOut),
child: Text(
currentUser == null ? '連携' : '解除',
style: const TextStyle(fontWeight: FontWeight.bold),
),
),
),
if (currentUser != null) ...[
Divider(height: 1, color: appColors.divider),
ListTile(
leading: Icon(LucideIcons.uploadCloud, color: appColors.iconDefault),
title: Text('バックアップ', style: TextStyle(color: appColors.textPrimary)),
trailing: _state == _BackupState.backingUp
? const SizedBox(width: 24, height: 24, child: CircularProgressIndicator(strokeWidth: 2))
: null,
onTap: isAnyProcessing ? null : _confirmBackup,
),
Divider(height: 1, color: appColors.divider),
ListTile(
leading: Icon(LucideIcons.downloadCloud, color: appColors.iconDefault),
title: Text('データ復元', style: TextStyle(color: appColors.textPrimary)),
trailing: _state == _BackupState.restoring
? const SizedBox(width: 24, height: 24, child: CircularProgressIndicator(strokeWidth: 2))
: null,
onTap: isAnyProcessing ? null : _restoreBackup,
),
],
],
),
),
],
);
}
Widget _buildSectionHeader(BuildContext context, String title, IconData icon) {
final appColors = Theme.of(context).extension<AppColors>()!;
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 4),
child: Row(
children: [
Icon(icon, size: 20, color: appColors.iconDefault),
const SizedBox(width: 8),
Text(
title,
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
color: appColors.textPrimary,
),
),
],
),
);
}
}