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

286 lines
10 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'package:flutter/material.dart';
import 'package:lucide_icons/lucide_icons.dart';
import '../../services/backup_service.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 {
setState(() => _state = _BackupState.signingIn);
final account = await _backupService.signIn();
if (mounted) {
setState(() => _state = _BackupState.idle);
if (account != null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('${account.email} で連携しました')),
);
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('連携がキャンセルされました')),
);
}
}
}
Future<void> _signOut() async {
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);
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('連携を解除しました')),
);
}
}
}
Future<void> _createBackup() async {
setState(() => _state = _BackupState.backingUp);
final success = await _backupService.createBackup();
if (mounted) {
setState(() => _state = _BackupState.idle);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(success ? 'バックアップが完了しました' : 'バックアップに失敗しました'),
backgroundColor: success ? Colors.green : Colors.red,
),
);
}
}
Future<void> _confirmBackup() async {
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: Theme.of(context).primaryColor,
foregroundColor: Colors.white,
),
child: const Text('バックアップ'),
),
],
),
);
if (confirmed == true) {
await _createBackup();
}
}
Future<void> _restoreBackup() async {
final hasBackup = await _backupService.hasBackupOnDrive();
if (!hasBackup && mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('バックアップファイルが見つかりません')),
);
return;
}
final confirmed = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: Row(
children: [
Icon(LucideIcons.alertTriangle, color: Colors.orange, 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: Theme.of(context).primaryColor,
foregroundColor: Colors.white,
),
child: const Text('復元'),
),
],
),
);
if (confirmed == true) {
setState(() => _state = _BackupState.restoring);
final success = await _backupService.restoreBackup();
if (mounted) {
setState(() => _state = _BackupState.idle);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(success ? '復元が完了しました' : '復元に失敗しました'),
backgroundColor: success ? Colors.green : Colors.red,
),
);
}
}
}
@override
Widget build(BuildContext context) {
final currentUser = _backupService.currentUser;
final isDark = Theme.of(context).brightness == Brightness.dark;
final isAnyProcessing = _state != _BackupState.idle;
return Column(
children: [
_buildSectionHeader(context, widget.title, LucideIcons.cloud),
// Wi-Fi推奨の注意書き (v1.2)
Container(
margin: const EdgeInsets.only(bottom: 12),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.blue.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.blue.withValues(alpha: 0.3)),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(LucideIcons.wifi, color: Colors.blue, size: 20),
const SizedBox(width: 12),
Expanded(
child: Text(
'バックアップやデータ復元はWi-Fi環境下を推奨します\nデータ量が数100MB1GB以上になる可能性があります',
style: TextStyle(
fontSize: 12,
color: isDark ? Colors.blue[300] : Colors.blue[900],
),
),
),
],
),
),
Card(
color: isDark ? const Color(0xFF1E1E1E) : null,
child: Column(
children: [
// Google Sign In Status
ListTile(
leading: Icon(
currentUser != null ? LucideIcons.checkCircle2 : LucideIcons.user,
color: currentUser != null ? Colors.green : (isDark ? Colors.orange[300] : Theme.of(context).primaryColor),
),
title: Text(currentUser == null ? 'Googleアカウント連携' : currentUser.email),
subtitle: currentUser == null ? const Text('Google Driveにバックアップ') : 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 ? Theme.of(context).primaryColor : Colors.grey[700],
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(horizontal: 16),
),
onPressed: isAnyProcessing ? null : (currentUser == null ? _signIn : _signOut),
child: Text(currentUser == null ? '連携' : '解除'),
),
),
if (currentUser != null) ...[
const Divider(height: 1),
ListTile(
leading: Icon(LucideIcons.uploadCloud, color: isDark ? Colors.blue[300] : Colors.blue),
title: const Text('バックアップ'),
trailing: _state == _BackupState.backingUp
? const SizedBox(width: 24, height: 24, child: CircularProgressIndicator(strokeWidth: 2))
: null,
onTap: isAnyProcessing ? null : _confirmBackup,
),
const Divider(height: 1),
ListTile(
leading: Icon(LucideIcons.downloadCloud, color: isDark ? Colors.red[300] : Colors.red),
title: const Text('データ復元'),
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 isDark = Theme.of(context).brightness == Brightness.dark;
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 4),
child: Row(
children: [
Icon(icon, size: 20, color: isDark ? Colors.orange[300] : Theme.of(context).primaryColor),
const SizedBox(width: 8),
Text(
title,
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
color: isDark ? Colors.grey[300] : Theme.of(context).primaryColor,
),
),
],
),
);
}
}