profile edit happens without requiring save button

This commit is contained in:
Christien Rioux 2024-08-01 14:30:06 -05:00
parent b6a812af87
commit 030f9d9651
19 changed files with 499 additions and 266 deletions

View file

@ -1,3 +1,4 @@
import 'package:awesome_extensions/awesome_extensions.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_translate/flutter_translate.dart';
@ -5,21 +6,27 @@ import 'package:flutter_translate/flutter_translate.dart';
import '../../proto/proto.dart' as proto;
class AvailabilityWidget extends StatelessWidget {
const AvailabilityWidget({required this.availability, super.key});
const AvailabilityWidget(
{required this.availability,
this.vertical = true,
this.iconSize = 32,
super.key});
static IconData availabilityIcon(proto.Availability availability) {
late final IconData iconData;
static Widget availabilityIcon(proto.Availability availability,
{double size = 32}) {
late final Widget iconData;
switch (availability) {
case proto.Availability.AVAILABILITY_AWAY:
iconData = Icons.hot_tub;
iconData =
ImageIcon(const AssetImage('assets/images/toilet.png'), size: size);
case proto.Availability.AVAILABILITY_BUSY:
iconData = Icons.event_busy;
iconData = Icon(Icons.event_busy, size: size);
case proto.Availability.AVAILABILITY_FREE:
iconData = Icons.event_available;
iconData = Icon(Icons.event_available, size: size);
case proto.Availability.AVAILABILITY_OFFLINE:
iconData = Icons.cloud_off;
iconData = Icon(Icons.cloud_off, size: size);
case proto.Availability.AVAILABILITY_UNSPECIFIED:
iconData = Icons.question_mark;
iconData = Icon(Icons.question_mark, size: size);
}
return iconData;
}
@ -49,20 +56,35 @@ class AvailabilityWidget extends StatelessWidget {
// final scaleConfig = theme.extension<ScaleConfig>()!;
final name = availabilityName(availability);
final iconData = availabilityIcon(availability);
final icon = availabilityIcon(availability, size: iconSize);
return Row(mainAxisSize: MainAxisSize.min, children: [
Icon(iconData, size: 32),
Text(name, style: textTheme.labelSmall)
]);
return vertical
? Column(
mainAxisSize: MainAxisSize.min,
//mainAxisAlignment: MainAxisAlignment.center,
children: [
icon,
Text(name, style: textTheme.labelSmall).paddingLTRB(0, 0, 0, 0)
])
: Row(mainAxisSize: MainAxisSize.min, children: [
icon,
Text(name, style: textTheme.labelSmall).paddingLTRB(8, 0, 0, 0)
]);
}
////////////////////////////////////////////////////////////////////////////
final proto.Availability availability;
final bool vertical;
final double iconSize;
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(
DiagnosticsProperty<proto.Availability>('availability', availability));
properties
..add(
DiagnosticsProperty<proto.Availability>('availability', availability))
..add(DiagnosticsProperty<bool>('vertical', vertical))
..add(DoubleProperty('iconSize', iconSize));
}
}

View file

@ -28,24 +28,28 @@ class ContactItemWidget extends StatelessWidget {
Widget build(
BuildContext context,
) {
late final String title;
late final String subtitle;
final theme = Theme.of(context);
final scale = theme.extension<ScaleScheme>()!;
final scaleConfig = theme.extension<ScaleConfig>()!;
if (_contact.nickname.isNotEmpty) {
title = _contact.nickname;
if (_contact.profile.pronouns.isNotEmpty) {
subtitle = '${_contact.profile.name} (${_contact.profile.pronouns})';
} else {
subtitle = _contact.profile.name;
}
} else {
title = _contact.profile.name;
if (_contact.profile.pronouns.isNotEmpty) {
subtitle = '(${_contact.profile.pronouns})';
} else {
subtitle = '';
}
}
final name = _contact.nameOrNickname;
final title = _contact.displayName;
final subtitle = _contact.profile.status;
final avatar = AvatarWidget(
name: name,
size: 34,
borderColor: _disabled
? scale.grayScale.primaryText
: scale.primaryScale.primaryText,
foregroundColor: _disabled
? scale.grayScale.primaryText
: scale.primaryScale.primaryText,
backgroundColor:
_disabled ? scale.grayScale.primary : scale.primaryScale.primary,
scaleConfig: scaleConfig,
textStyle: theme.textTheme.titleLarge!,
);
return SliderTile(
key: ObjectKey(_contact),
@ -54,7 +58,7 @@ class ContactItemWidget extends StatelessWidget {
tileScale: ScaleKind.primary,
title: title,
subtitle: subtitle,
icon: Icons.person,
leading: avatar,
onDoubleTap: _onDoubleTap == null
? null
: () => singleFuture<void>((this, _kOnTap), () async {

View file

@ -5,6 +5,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_translate/flutter_translate.dart';
import 'package:searchable_listview/searchable_listview.dart';
import 'package:star_menu/star_menu.dart';
import 'package:veilid_support/veilid_support.dart';
import '../../chat_list/chat_list.dart';
@ -71,6 +72,75 @@ class _ContactsBrowserState extends State<ContactsBrowser>
final scale = theme.extension<ScaleScheme>()!;
final scaleConfig = theme.extension<ScaleConfig>()!;
final menuIconColor = scaleConfig.preferBorders
? scale.primaryScale.hoverBorder
: scale.primaryScale.borderText;
final menuBackgroundColor = scaleConfig.preferBorders
? scale.primaryScale.elementBackground
: scale.primaryScale.border;
// final menuHoverColor = scaleConfig.preferBorders
// ? scale.primaryScale.hoverElementBackground
// : scale.primaryScale.hoverBorder;
final menuBorderColor = scale.primaryScale.hoverBorder;
final menuParams = StarMenuParameters(
shape: MenuShape.grid,
checkItemsScreenBoundaries: true,
centerOffset: const Offset(0, 64),
backgroundParams:
BackgroundParams(backgroundColor: theme.shadowColor.withAlpha(128)),
boundaryBackground: BoundaryBackground(
color: menuBackgroundColor,
decoration: ShapeDecoration(
color: menuBackgroundColor,
shape: RoundedRectangleBorder(
side: scaleConfig.useVisualIndicators
? BorderSide(
width: 2, color: menuBorderColor, strokeAlign: 0)
: BorderSide.none,
borderRadius: BorderRadius.circular(
8 * scaleConfig.borderRadiusScale)))));
final receiveInviteMenuItems = [
Column(mainAxisSize: MainAxisSize.min, children: [
IconButton(
onPressed: () async {
_receiveInviteMenuController.closeMenu!();
await ScanInvitationDialog.show(context);
},
iconSize: 32,
icon: Icon(
Icons.qr_code_scanner,
size: 32,
color: menuIconColor,
),
),
Text(translate('add_contact_sheet.scan_invite'),
maxLines: 2,
textAlign: TextAlign.center,
style: textTheme.labelSmall!.copyWith(color: menuIconColor))
]).paddingAll(4),
Column(mainAxisSize: MainAxisSize.min, children: [
IconButton(
onPressed: () async {
_receiveInviteMenuController.closeMenu!();
await PasteInvitationDialog.show(context);
},
iconSize: 32,
icon: Icon(
Icons.paste,
size: 32,
color: menuIconColor,
),
),
Text(translate('add_contact_sheet.paste_invite'),
maxLines: 2,
textAlign: TextAlign.center,
style: textTheme.labelSmall!.copyWith(color: menuIconColor))
]).paddingAll(4)
];
return Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [
Column(mainAxisSize: MainAxisSize.min, children: [
IconButton(
@ -80,30 +150,36 @@ class _ContactsBrowserState extends State<ContactsBrowser>
iconSize: 32,
icon: const Icon(Icons.contact_page),
color: scale.primaryScale.hoverBorder,
tooltip: translate('add_contact_sheet.create_invite'),
)
]),
Column(mainAxisSize: MainAxisSize.min, children: [
IconButton(
onPressed: () async {
await ScanInvitationDialog.show(context);
},
iconSize: 32,
icon: const Icon(Icons.qr_code_scanner),
color: scale.primaryScale.hoverBorder,
tooltip: translate('add_contact_sheet.scan_invite')),
]),
Column(mainAxisSize: MainAxisSize.min, children: [
IconButton(
onPressed: () async {
await PasteInvitationDialog.show(context);
},
iconSize: 32,
icon: const Icon(Icons.paste),
color: scale.primaryScale.hoverBorder,
tooltip: translate('add_contact_sheet.paste_invite'),
),
])
Text(translate('add_contact_sheet.create_invite'),
maxLines: 2,
textAlign: TextAlign.center,
style: textTheme.labelSmall!
.copyWith(color: scale.primaryScale.hoverBorder))
]),
StarMenu(
items: receiveInviteMenuItems,
onItemTapped: (_index, controller) {
controller.closeMenu!();
},
controller: _receiveInviteMenuController,
params: menuParams,
child: Column(mainAxisSize: MainAxisSize.min, children: [
IconButton(
onPressed: () {},
iconSize: 32,
icon: ImageIcon(
const AssetImage('assets/images/handshake.png'),
size: 32,
color: scale.primaryScale.hoverBorder,
)),
Text(translate('add_contact_sheet.receive_invite'),
maxLines: 2,
textAlign: TextAlign.center,
style: textTheme.labelSmall!
.copyWith(color: scale.primaryScale.hoverBorder))
]),
),
]).paddingAll(16);
}
@ -112,7 +188,7 @@ class _ContactsBrowserState extends State<ContactsBrowser>
final theme = Theme.of(context);
final textTheme = theme.textTheme;
final scale = theme.extension<ScaleScheme>()!;
final scaleConfig = theme.extension<ScaleConfig>()!;
//final scaleConfig = theme.extension<ScaleConfig>()!;
final cilState = context.watch<ContactInvitationListCubit>().state;
final cilBusy = cilState.busy;
@ -244,4 +320,7 @@ class _ContactsBrowserState extends State<ContactsBrowser>
await chatListCubit.deleteChat(
localConversationRecordKey: localConversationRecordKey);
}
////////////////////////////////////////////////////////////////////////////
final _receiveInviteMenuController = StarMenuController();
}

View file

@ -1,18 +1,14 @@
import 'package:awesome_extensions/awesome_extensions.dart';
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_translate/flutter_translate.dart';
import 'package:go_router/go_router.dart';
import 'package:provider/provider.dart';
import '../../chat/chat.dart';
import '../../chat_list/chat_list.dart';
import '../../proto/proto.dart' as proto;
import '../../contact_invitation/contact_invitation.dart';
import '../../layout/layout.dart';
import '../../proto/proto.dart' as proto;
import '../../theme/theme.dart';
import '../../veilid_processor/veilid_processor.dart';
import '../contacts.dart';
class ContactsDialog extends StatefulWidget {
@ -48,9 +44,9 @@ class _ContactsDialogState extends State<ContactsDialog> {
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final textTheme = theme.textTheme;
// final textTheme = theme.textTheme;
final scale = theme.extension<ScaleScheme>()!;
final scaleConfig = theme.extension<ScaleConfig>()!;
// final scaleConfig = theme.extension<ScaleConfig>()!;
final enableSplit = !isMobileWidth(context);
final enableLeft = enableSplit || _selectedContact == null;
@ -105,7 +101,7 @@ class _ContactsDialogState extends State<ContactsDialog> {
.toVeilid(),
onContactSelected: onContactSelected,
onChatStarted: onChatStarted,
).paddingAll(8)))),
).paddingLTRB(8, 0, 8, 8)))),
if (enableRight)
if (_selectedContact == null)
const NoContactWidget().expanded()