Added class clearance fixes and improvements
Some checks failed
Release / meta (push) Successful in 18s
Release / android-build (push) Successful in 11m48s
Release / linux-build (push) Successful in 1m24s
Release / web-build (push) Successful in 6m15s
Release / release-dev (push) Has been cancelled
Release / release-master (push) Has been cancelled
Some checks failed
Release / meta (push) Successful in 18s
Release / android-build (push) Successful in 11m48s
Release / linux-build (push) Successful in 1m24s
Release / web-build (push) Successful in 6m15s
Release / release-dev (push) Has been cancelled
Release / release-master (push) Has been cancelled
This commit is contained in:
@@ -1,3 +1,5 @@
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
@@ -5,6 +7,7 @@ import 'package:mileograph_flutter/objects/objects.dart';
|
||||
import 'package:mileograph_flutter/services/api_service.dart';
|
||||
import 'package:mileograph_flutter/services/authservice.dart';
|
||||
import 'package:mileograph_flutter/services/data_service.dart';
|
||||
import 'package:mileograph_flutter/utils/download_helper.dart';
|
||||
|
||||
class ProfilePage extends StatefulWidget {
|
||||
const ProfilePage({super.key});
|
||||
@@ -31,6 +34,10 @@ class _ProfilePageState extends State<ProfilePage> {
|
||||
bool _showAccountSettings = false;
|
||||
bool _changingPassword = false;
|
||||
static const List<String> _visibilityOptions = ['private', 'friends', 'public'];
|
||||
static const List<String> _exportFormats = ['xlsx', 'json', 'csv'];
|
||||
bool _exporting = false;
|
||||
String _exportFormat = 'xlsx';
|
||||
String? _exportError;
|
||||
|
||||
UserSummary? _selectedUser;
|
||||
Friendship? _status;
|
||||
@@ -71,6 +78,54 @@ class _ProfilePageState extends State<ProfilePage> {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _exportLogEntries() async {
|
||||
if (_exporting) return;
|
||||
setState(() {
|
||||
_exporting = true;
|
||||
_exportError = null;
|
||||
});
|
||||
try {
|
||||
final data = context.read<DataService>();
|
||||
final response = await data.api.getBytes(
|
||||
'/legs/export?export_format=$_exportFormat',
|
||||
headers: const {'accept': '*/*'},
|
||||
);
|
||||
final timestamp = DateTime.now()
|
||||
.toIso8601String()
|
||||
.replaceAll(':', '')
|
||||
.replaceAll('.', '');
|
||||
final fallbackName = 'log_entries_$timestamp.$_exportFormat';
|
||||
final filename = response.filename ?? fallbackName;
|
||||
final saveResult = await saveBytes(
|
||||
Uint8List.fromList(response.bytes),
|
||||
filename,
|
||||
mimeType: response.contentType,
|
||||
);
|
||||
if (!mounted) return;
|
||||
if (saveResult.canceled) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(content: Text('Export canceled.')),
|
||||
);
|
||||
} else {
|
||||
final path = saveResult.path;
|
||||
final message = path == null || path.isEmpty
|
||||
? 'Download started.'
|
||||
: 'Export saved to $path';
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(message)),
|
||||
);
|
||||
}
|
||||
} on ApiException catch (e) {
|
||||
if (!mounted) return;
|
||||
setState(() => _exportError = e.message);
|
||||
} catch (e) {
|
||||
if (!mounted) return;
|
||||
setState(() => _exportError = e.toString());
|
||||
} finally {
|
||||
if (mounted) setState(() => _exporting = false);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _searchUsers() async {
|
||||
final query = _searchController.text.trim();
|
||||
if (query.isEmpty) {
|
||||
@@ -829,6 +884,8 @@ class _ProfilePageState extends State<ProfilePage> {
|
||||
firstChild: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
_buildExportSection(theme),
|
||||
const SizedBox(height: 12),
|
||||
if (showPrivacySpinner)
|
||||
const Padding(
|
||||
padding: EdgeInsets.symmetric(vertical: 8.0),
|
||||
@@ -1023,6 +1080,60 @@ class _ProfilePageState extends State<ProfilePage> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildExportSection(ThemeData theme) {
|
||||
return ExpansionTile(
|
||||
tilePadding: EdgeInsets.zero,
|
||||
childrenPadding: const EdgeInsets.only(top: 8),
|
||||
title: Text(
|
||||
'Export log entries',
|
||||
style: theme.textTheme.titleMedium?.copyWith(fontWeight: FontWeight.w700),
|
||||
),
|
||||
subtitle: const Text('Download your log as a file.'),
|
||||
children: [
|
||||
DropdownButtonFormField<String>(
|
||||
value: _exportFormat,
|
||||
decoration: const InputDecoration(
|
||||
labelText: 'Export format',
|
||||
border: OutlineInputBorder(),
|
||||
),
|
||||
items: _exportFormats
|
||||
.map(
|
||||
(format) => DropdownMenuItem(
|
||||
value: format,
|
||||
child: Text(format.toUpperCase()),
|
||||
),
|
||||
)
|
||||
.toList(),
|
||||
onChanged: _exporting
|
||||
? null
|
||||
: (value) {
|
||||
if (value == null) return;
|
||||
setState(() => _exportFormat = value);
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
FilledButton.icon(
|
||||
onPressed: _exporting ? null : _exportLogEntries,
|
||||
icon: _exporting
|
||||
? const SizedBox(
|
||||
width: 18,
|
||||
height: 18,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
)
|
||||
: const Icon(Icons.download),
|
||||
label: Text(_exporting ? 'Exporting...' : 'Export'),
|
||||
),
|
||||
if (_exportError != null) ...[
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
_exportError!,
|
||||
style: TextStyle(color: theme.colorScheme.error),
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
UserSummary? _otherUser(Friendship friendship, String? currentUserId) {
|
||||
final selfId = currentUserId ?? '';
|
||||
if (friendship.requester?.userId == selfId) return friendship.addressee;
|
||||
|
||||
Reference in New Issue
Block a user