mirror of
https://github.com/FoggedLens/deflock-app.git
synced 2026-07-18 18:17:22 +02:00
377 lines
15 KiB
Dart
377 lines
15 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:provider/provider.dart';
|
|
import 'package:url_launcher/url_launcher.dart';
|
|
import '../app_state.dart';
|
|
import '../services/localization_service.dart';
|
|
import '../dev_config.dart';
|
|
import '../screens/settings/sections/upload_mode_section.dart';
|
|
|
|
class OSMAccountScreen extends StatefulWidget {
|
|
const OSMAccountScreen({super.key});
|
|
|
|
@override
|
|
State<OSMAccountScreen> createState() => _OSMAccountScreenState();
|
|
}
|
|
|
|
class _OSMAccountScreenState extends State<OSMAccountScreen> {
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
// Check for messages when screen loads
|
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
|
final appState = context.read<AppState>();
|
|
if (appState.isLoggedIn) {
|
|
appState.checkMessages();
|
|
}
|
|
});
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return AnimatedBuilder(
|
|
animation: LocalizationService.instance,
|
|
builder: (context, child) {
|
|
final locService = LocalizationService.instance;
|
|
final appState = context.watch<AppState>();
|
|
|
|
return Scaffold(
|
|
appBar: AppBar(
|
|
title: Text(locService.t('auth.osmAccountTitle')),
|
|
),
|
|
body: ListView(
|
|
padding: EdgeInsets.fromLTRB(
|
|
16,
|
|
16,
|
|
16,
|
|
16 + MediaQuery.of(context).padding.bottom,
|
|
),
|
|
children: [
|
|
// Login/Account Status Section
|
|
Card(
|
|
child: Column(
|
|
children: [
|
|
ListTile(
|
|
leading: Icon(
|
|
appState.isLoggedIn ? Icons.person : Icons.login,
|
|
color: appState.isLoggedIn ? Colors.green : null,
|
|
),
|
|
title: Text(appState.isLoggedIn
|
|
? locService.t('auth.loggedInAs', params: [appState.username])
|
|
: locService.t('auth.loginToOSM')),
|
|
subtitle: appState.isLoggedIn
|
|
? Text(locService.t('auth.tapToLogout'))
|
|
: Text(locService.t('auth.requiredToSubmit')),
|
|
onTap: () async {
|
|
if (appState.isLoggedIn) {
|
|
await appState.logout();
|
|
if (context.mounted) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(
|
|
content: Text(locService.t('auth.loggedOut')),
|
|
backgroundColor: Colors.grey,
|
|
),
|
|
);
|
|
}
|
|
} else {
|
|
// Start login flow - the user will be redirected to browser
|
|
await appState.forceLogin();
|
|
|
|
// Don't show immediate feedback - the UI will update automatically
|
|
// when the OAuth callback completes and notifyListeners() is called
|
|
}
|
|
},
|
|
),
|
|
|
|
if (appState.isLoggedIn) ...[
|
|
const Divider(),
|
|
ListTile(
|
|
leading: const Icon(Icons.wifi_protected_setup),
|
|
title: Text(locService.t('auth.testConnection')),
|
|
subtitle: Text(locService.t('auth.testConnectionSubtitle')),
|
|
onTap: () async {
|
|
final isValid = await appState.validateToken();
|
|
if (context.mounted) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(
|
|
content: Text(isValid
|
|
? locService.t('auth.connectionOK')
|
|
: locService.t('auth.connectionFailed')),
|
|
backgroundColor: isValid ? Colors.green : Colors.red,
|
|
),
|
|
);
|
|
}
|
|
if (!isValid) {
|
|
await appState.logout();
|
|
}
|
|
},
|
|
),
|
|
|
|
// Only show OSM website buttons when not in simulate mode
|
|
if (appState.uploadMode != UploadMode.simulate) ...[
|
|
const Divider(),
|
|
ListTile(
|
|
leading: const Icon(Icons.history),
|
|
title: Text(locService.t('auth.viewMyEdits')),
|
|
subtitle: Text(locService.t('auth.viewMyEditsSubtitle')),
|
|
trailing: const Icon(Icons.open_in_new),
|
|
onTap: () async {
|
|
final url = Uri.parse('https://openstreetmap.org/user/${Uri.encodeComponent(appState.username)}/history');
|
|
if (await canLaunchUrl(url)) {
|
|
await launchUrl(url, mode: LaunchMode.externalApplication);
|
|
} else {
|
|
if (context.mounted) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(content: Text(locService.t('advancedEdit.couldNotOpenOSMWebsite'))),
|
|
);
|
|
}
|
|
}
|
|
},
|
|
),
|
|
|
|
// Messages button - only show when not in simulate mode
|
|
const Divider(),
|
|
ListTile(
|
|
leading: Stack(
|
|
children: [
|
|
const Icon(Icons.message),
|
|
if (appState.hasUnreadMessages)
|
|
Positioned(
|
|
right: 0,
|
|
top: 0,
|
|
child: Container(
|
|
padding: const EdgeInsets.all(2),
|
|
decoration: BoxDecoration(
|
|
color: Theme.of(context).colorScheme.error,
|
|
shape: BoxShape.circle,
|
|
),
|
|
constraints: const BoxConstraints(
|
|
minWidth: 12,
|
|
minHeight: 12,
|
|
),
|
|
child: Text(
|
|
'${appState.unreadMessageCount}',
|
|
style: TextStyle(
|
|
color: Theme.of(context).colorScheme.onError,
|
|
fontSize: 10,
|
|
fontWeight: FontWeight.bold,
|
|
),
|
|
textAlign: TextAlign.center,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
title: Text(locService.t('auth.viewMessages')),
|
|
subtitle: Text(appState.hasUnreadMessages
|
|
? locService.t('auth.unreadMessagesCount', params: [appState.unreadMessageCount.toString()])
|
|
: locService.t('auth.noUnreadMessages')),
|
|
trailing: const Icon(Icons.open_in_new),
|
|
onTap: () async {
|
|
final url = Uri.parse(appState.getMessagesUrl());
|
|
if (await canLaunchUrl(url)) {
|
|
await launchUrl(url, mode: LaunchMode.externalApplication);
|
|
} else {
|
|
if (context.mounted) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(content: Text(locService.t('advancedEdit.couldNotOpenOSMWebsite'))),
|
|
);
|
|
}
|
|
}
|
|
},
|
|
),
|
|
],
|
|
],
|
|
],
|
|
),
|
|
),
|
|
|
|
const SizedBox(height: 16),
|
|
|
|
// Upload Mode Section (only show in development builds)
|
|
if (kEnableDevelopmentModes) ...[
|
|
Card(
|
|
child: const Padding(
|
|
padding: EdgeInsets.all(16.0),
|
|
child: UploadModeSection(),
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
],
|
|
|
|
// Account deletion section - only show when logged in and not in simulate mode
|
|
if (appState.isLoggedIn && appState.uploadMode != UploadMode.simulate) ...[
|
|
const SizedBox(height: 16),
|
|
_buildAccountDeletionSection(context, appState),
|
|
],
|
|
],
|
|
),
|
|
);
|
|
},
|
|
);
|
|
}
|
|
|
|
Widget _buildAccountDeletionSection(BuildContext context, AppState appState) {
|
|
final locService = LocalizationService.instance;
|
|
|
|
return Card(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(16.0),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
Text(
|
|
locService.t('auth.accountManagement'),
|
|
style: Theme.of(context).textTheme.titleMedium,
|
|
),
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
locService.t('auth.accountManagementDescription'),
|
|
style: Theme.of(context).textTheme.bodyMedium,
|
|
),
|
|
const SizedBox(height: 16),
|
|
|
|
// Show current upload destination
|
|
Container(
|
|
padding: const EdgeInsets.all(12.0),
|
|
decoration: BoxDecoration(
|
|
color: Theme.of(context).colorScheme.surfaceContainerHighest.withValues(alpha: 0.5),
|
|
borderRadius: BorderRadius.circular(8.0),
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Icon(
|
|
Icons.info_outline,
|
|
size: 16,
|
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
|
),
|
|
const SizedBox(width: 8),
|
|
Expanded(
|
|
child: Text(
|
|
_getCurrentDestinationText(locService, appState.uploadMode),
|
|
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
|
color: Theme.of(context).colorScheme.onSurfaceVariant,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(height: 16),
|
|
|
|
// Delete account button
|
|
SizedBox(
|
|
width: double.infinity,
|
|
child: OutlinedButton.icon(
|
|
onPressed: () => _showDeleteAccountDialog(context, locService, appState.uploadMode),
|
|
icon: const Icon(Icons.delete_outline),
|
|
label: Text(locService.t('auth.deleteAccount')),
|
|
style: OutlinedButton.styleFrom(
|
|
foregroundColor: Theme.of(context).colorScheme.error,
|
|
side: BorderSide(color: Theme.of(context).colorScheme.error.withValues(alpha: 0.5)),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
String _getCurrentDestinationText(LocalizationService locService, UploadMode uploadMode) {
|
|
switch (uploadMode) {
|
|
case UploadMode.production:
|
|
return locService.t('auth.currentDestinationProduction');
|
|
case UploadMode.sandbox:
|
|
return locService.t('auth.currentDestinationSandbox');
|
|
case UploadMode.simulate:
|
|
return locService.t('auth.currentDestinationSimulate');
|
|
}
|
|
}
|
|
|
|
void _showDeleteAccountDialog(BuildContext context, LocalizationService locService, UploadMode uploadMode) {
|
|
final deleteUrl = _getDeleteAccountUrl(uploadMode);
|
|
|
|
showDialog(
|
|
context: context,
|
|
builder: (context) => AlertDialog(
|
|
title: Text(locService.t('auth.deleteAccount')),
|
|
content: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(locService.t('auth.deleteAccountExplanation')),
|
|
const SizedBox(height: 12),
|
|
Text(
|
|
locService.t('auth.deleteAccountWarning'),
|
|
style: TextStyle(
|
|
fontWeight: FontWeight.bold,
|
|
color: Theme.of(context).colorScheme.error,
|
|
),
|
|
),
|
|
const SizedBox(height: 12),
|
|
// Show which account will be deleted
|
|
Container(
|
|
padding: const EdgeInsets.all(12.0),
|
|
decoration: BoxDecoration(
|
|
color: Theme.of(context).colorScheme.errorContainer.withValues(alpha: 0.1),
|
|
borderRadius: BorderRadius.circular(8.0),
|
|
border: Border.all(
|
|
color: Theme.of(context).colorScheme.error.withValues(alpha: 0.3),
|
|
),
|
|
),
|
|
child: Text(
|
|
_getCurrentDestinationText(locService, uploadMode),
|
|
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
|
color: Theme.of(context).colorScheme.error,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Navigator.of(context).pop(),
|
|
child: Text(locService.t('actions.cancel')),
|
|
),
|
|
TextButton(
|
|
onPressed: () {
|
|
Navigator.of(context).pop();
|
|
_launchDeleteAccountUrl(deleteUrl, context, locService);
|
|
},
|
|
style: TextButton.styleFrom(
|
|
foregroundColor: Theme.of(context).colorScheme.error,
|
|
),
|
|
child: Text(locService.t('auth.goToOSM')),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
String _getDeleteAccountUrl(UploadMode uploadMode) {
|
|
switch (uploadMode) {
|
|
case UploadMode.production:
|
|
return 'https://www.openstreetmap.org/account/deletion';
|
|
case UploadMode.sandbox:
|
|
return 'https://master.apis.dev.openstreetmap.org/account/deletion';
|
|
case UploadMode.simulate:
|
|
// For simulate mode, just go to production since it's not a real account
|
|
return 'https://www.openstreetmap.org/account/deletion';
|
|
}
|
|
}
|
|
|
|
Future<void> _launchDeleteAccountUrl(String url, BuildContext context, LocalizationService locService) async {
|
|
final uri = Uri.parse(url);
|
|
if (await canLaunchUrl(uri)) {
|
|
await launchUrl(uri, mode: LaunchMode.externalApplication);
|
|
} else {
|
|
if (context.mounted) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(
|
|
content: Text(locService.t('advancedEdit.couldNotOpenOSMWebsite')),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
}
|
|
} |