veilidchat/lib/contacts/views/contacts_dialog.dart
2025-03-17 22:00:26 -04:00

196 lines
7.6 KiB
Dart

import 'package:async_tools/async_tools.dart';
import 'package:awesome_extensions/awesome_extensions.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_translate/flutter_translate.dart';
import 'package:provider/provider.dart';
import '../../chat/chat.dart';
import '../../chat_list/chat_list.dart';
import '../../layout/layout.dart';
import '../../proto/proto.dart' as proto;
import '../../theme/theme.dart';
import '../contacts.dart';
const _kDoBackArrow = 'doBackArrow';
class ContactsDialog extends StatefulWidget {
const ContactsDialog._({required this.modalContext});
@override
State<ContactsDialog> createState() => _ContactsDialogState();
static Future<void> show(BuildContext modalContext) async {
await showDialog<void>(
context: modalContext,
barrierDismissible: false,
useRootNavigator: false,
builder: (context) => ContactsDialog._(modalContext: modalContext));
}
final BuildContext modalContext;
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties
.add(DiagnosticsProperty<BuildContext>('modalContext', modalContext));
}
}
class _ContactsDialogState extends State<ContactsDialog> {
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final scale = theme.extension<ScaleScheme>()!;
final appBarIconColor = scale.primaryScale.borderText;
final enableSplit = !isMobileWidth(context);
final enableLeft = enableSplit || _selectedContact == null;
final enableRight = enableSplit || _selectedContact != null;
return SizedBox(
width: MediaQuery.of(context).size.width,
child: StyledScaffold(
appBar: DefaultAppBar(
title: Text(!enableSplit && enableRight
? translate('contacts_dialog.edit_contact')
: translate('contacts_dialog.contacts')),
leading: IconButton(
icon: const Icon(Icons.arrow_back),
onPressed: () {
singleFuture((this, _kDoBackArrow), () async {
final confirmed = await _onContactSelected(null);
if (!enableSplit && enableRight) {
} else {
if (confirmed) {
if (context.mounted) {
Navigator.pop(context);
}
}
}
});
},
),
actions: [
if (_selectedContact != null)
FittedBox(
fit: BoxFit.scaleDown,
child:
Column(mainAxisSize: MainAxisSize.min, children: [
IconButton(
icon: const Icon(Icons.chat_bubble),
color: appBarIconColor,
tooltip: translate('contacts_dialog.new_chat'),
onPressed: () async {
await _onChatStarted(_selectedContact!);
}),
Text(translate('contacts_dialog.new_chat'),
style: theme.textTheme.labelSmall!
.copyWith(color: appBarIconColor)),
])).paddingLTRB(8, 0, 8, 0),
if (enableSplit && _selectedContact != null)
FittedBox(
fit: BoxFit.scaleDown,
child:
Column(mainAxisSize: MainAxisSize.min, children: [
IconButton(
icon: const Icon(Icons.close),
color: appBarIconColor,
tooltip:
translate('contacts_dialog.close_contact'),
onPressed: () async {
await _onContactSelected(null);
}),
Text(translate('contacts_dialog.close_contact'),
style: theme.textTheme.labelSmall!
.copyWith(color: appBarIconColor)),
])).paddingLTRB(8, 0, 8, 0),
]),
body: LayoutBuilder(builder: (context, constraint) {
final maxWidth = constraint.maxWidth;
return ColoredBox(
color: scale.primaryScale.appBackground,
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Offstage(
offstage: !enableLeft,
child: SizedBox(
width: enableLeft && !enableRight
? maxWidth
: (maxWidth / 3).clamp(200, 500),
child: DecoratedBox(
decoration: BoxDecoration(
color: scale
.primaryScale.subtleBackground),
child: ContactsBrowser(
selectedContactRecordKey: _selectedContact
?.localConversationRecordKey
.toVeilid(),
onContactSelected: _onContactSelected,
onStartChat: _onChatStarted,
).paddingLTRB(8, 0, 8, 8)))),
if (enableRight && enableLeft)
Container(
constraints: const BoxConstraints(
minWidth: 1, maxWidth: 1),
color: scale.primaryScale.subtleBorder),
if (enableRight)
if (_selectedContact == null)
const NoContactWidget().expanded()
else
ContactDetailsWidget(
contact: _selectedContact!,
onModifiedState: _onModifiedState)
.paddingLTRB(16, 16, 16, 16)
.expanded(),
]));
})));
}
void _onModifiedState(bool isModified) {
setState(() {
_isModified = isModified;
});
}
Future<bool> _onContactSelected(proto.Contact? contact) async {
if (contact != _selectedContact && _isModified) {
final ok = await showConfirmModal(
context: context,
title: translate('confirmation.discard_changes'),
text: translate('confirmation.are_you_sure_discard'));
if (!ok) {
return false;
}
}
setState(() {
_selectedContact = contact;
_isModified = false;
});
return true;
}
Future<void> _onChatStarted(proto.Contact contact) async {
final chatListCubit = context.read<ChatListCubit>();
await chatListCubit.getOrCreateChatSingleContact(contact: contact);
if (mounted) {
context
.read<ActiveChatCubit>()
.setActiveChat(contact.localConversationRecordKey.toVeilid());
Navigator.pop(context);
}
}
proto.Contact? _selectedContact;
bool _isModified = false;
}