relatica/lib/screens/circle_editor_screen.dart

274 wiersze
10 KiB
Dart

import 'package:flutter/material.dart';
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';
import 'package:result_monad/result_monad.dart';
import '../controls/linear_status_indicator.dart';
import '../controls/padding.dart';
import '../controls/responsive_max_width.dart';
import '../controls/standard_appbar.dart';
import '../controls/status_and_refresh_button.dart';
import '../globals.dart';
import '../models/circle_data.dart';
import '../models/connection.dart';
import '../routes.dart';
import '../services/connections_manager.dart';
import '../services/network_status_service.dart';
import '../utils/active_profile_selector.dart';
import '../utils/snackbar_builder.dart';
class CircleEditorScreen extends StatefulWidget {
final String circleId;
const CircleEditorScreen({super.key, required this.circleId});
@override
State<CircleEditorScreen> createState() => _CircleEditorScreenState();
}
class _CircleEditorScreenState extends State<CircleEditorScreen> {
final circleTextController = TextEditingController();
var processingUpdate = false;
var allowNameEditing = false;
var filterText = '';
late CircleData circleData;
Future<void> updateCircleName(
BuildContext context, ConnectionsManager manager) async {
processingUpdate = true;
final updated = circleTextController.text;
if (circleTextController.text != circleData.name) {
final confirm = await showYesNoDialog(context,
'Change the circle name from ${circleData.name} to $updated?');
if (context.mounted && confirm == true) {
await manager.renameCircle(widget.circleId, updated).match(
onSuccess: (updatedCircleData) {
circleData = updatedCircleData;
setState(() {
allowNameEditing = false;
});
}, onError: (error) {
buildSnackbar(context, 'Error renaming circle: $error');
});
} else {
circleTextController.text = circleData.name;
}
}
processingUpdate = false;
}
Future<void> deleteCircle(ConnectionsManager manager) async {
final confirm = await showYesNoDialog(context,
"Permanently delete circle ${circleData.name}? This can't be undone.");
if (context.mounted && confirm == true) {
await manager.deleteCircle(circleData).match(
onSuccess: (_) => context.canPop() ? context.pop() : null,
onError: (error) =>
buildSnackbar(context, 'Error trying to delete circle: $error'),
);
}
}
Future<void> removeUserFromCircle(
ConnectionsManager manager,
Connection connection,
) async {
final messageBase = '${connection.name} from ${circleData.name}';
final confirm = await showYesNoDialog(context, 'Remove $messageBase?');
if (context.mounted && confirm == true) {
final message =
await manager.removeUserFromCircle(circleData, connection).fold(
onSuccess: (_) => 'Removed $messageBase',
onError: (error) => 'Error removing $messageBase: $error',
);
buildSnackbar(context, message);
}
}
@override
void initState() {
super.initState();
final manager =
getIt<ActiveProfileSelector<ConnectionsManager>>().activeEntry.value;
circleData = manager
.getMyCircles()
.where(
(g) => g.id == widget.circleId,
)
.first;
manager.refreshCircleMemberships(circleData);
circleTextController.text = circleData.name;
}
@override
Widget build(BuildContext context) {
final nss = getIt<NetworkStatusService>();
final manager = context
.watch<ActiveProfileSelector<ConnectionsManager>>()
.activeEntry
.value;
final filterTextLC = filterText.toLowerCase();
final members = manager
.getCircleMembers(circleData)
.transform((ms) => ms
.where((m) =>
filterText.isEmpty ||
m.name.toLowerCase().contains(filterTextLC) ||
m.handle.toLowerCase().contains(filterTextLC))
.toList())
.getValueOrElse(() => <Connection>[]);
return Scaffold(
appBar: StandardAppBar.build(
context,
'Circle Editor',
withHome: false,
actions: [
IconButton(
onPressed: () => deleteCircle(manager),
icon: const Icon(Icons.delete),
),
],
),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: RefreshIndicator(
onRefresh: () async {
manager.refreshCircles();
},
child: ResponsiveMaxWidth(
child: Column(
children: [
StandardLinearProgressIndicator(nss.connectionUpdateStatus),
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
children: [
Expanded(
child: TextFormField(
enabled: allowNameEditing,
readOnly: !allowNameEditing,
onEditingComplete: () async {
if (processingUpdate) {
return;
}
updateCircleName(context, manager);
},
onTapOutside: (_) async {
if (processingUpdate) {
return;
}
updateCircleName(context, manager);
},
controller: circleTextController,
textCapitalization: TextCapitalization.sentences,
decoration: InputDecoration(
labelText: 'Circle Name',
border: OutlineInputBorder(
borderSide: const BorderSide(),
borderRadius: BorderRadius.circular(5.0),
),
),
),
),
const HorizontalPadding(),
IconButton(
onPressed: () {
if (allowNameEditing) {
circleTextController.text = circleData.name;
}
setState(() {
allowNameEditing = !allowNameEditing;
});
},
icon: const Icon(Icons.edit),
),
],
),
),
const VerticalPadding(),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('Circle Members (${members.length}):',
style: Theme.of(context).textTheme.headlineSmall),
IconButton(
onPressed: () {
context.push(
'${ScreenPaths.circleManagement}/add_users/${widget.circleId}');
},
icon: const Icon(Icons.add)),
],
),
Row(
children: [
Expanded(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: TextField(
onChanged: (value) {
setState(() {
filterText = value.toLowerCase();
});
},
decoration: InputDecoration(
labelText: 'Filter By Name',
alignLabelWithHint: true,
border: OutlineInputBorder(
borderSide: BorderSide(
color: Theme.of(context).highlightColor,
),
borderRadius: BorderRadius.circular(5.0),
),
),
),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: StatusAndRefreshButton(
valueListenable: nss.connectionUpdateStatus,
refreshFunction: () async =>
manager.refreshCircleMemberships(circleData),
),
)
],
),
Expanded(
child: ListView.separated(
physics: const AlwaysScrollableScrollPhysics(),
itemBuilder: (context, index) {
final m = members[index];
return ListTile(
onTap: () {
context.pushNamed(ScreenPaths.userProfile,
pathParameters: {'id': m.id});
},
title: Text(
'${m.name} (${m.handle})',
softWrap: true,
),
subtitle: Text(
'Last Status: ${m.lastStatus?.toIso8601String() ?? "Unknown"}',
softWrap: true,
),
trailing: IconButton(
onPressed: () async =>
removeUserFromCircle(manager, m),
icon: const Icon(Icons.remove),
),
);
},
separatorBuilder: (_, __) => const Divider(),
itemCount: members.length,
),
),
],
),
),
),
));
}
}