veilidchat/lib/layout/home/drawer_menu/drawer_menu.dart

434 lines
16 KiB
Dart
Raw Normal View History

import 'package:async_tools/async_tools.dart';
2024-06-11 21:27:20 -04:00
import 'package:awesome_extensions/awesome_extensions.dart';
2024-06-16 22:12:24 -04:00
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
2024-06-11 21:27:20 -04:00
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:flutter_translate/flutter_translate.dart';
import 'package:go_router/go_router.dart';
import 'package:veilid_support/veilid_support.dart';
import '../../../account_manager/account_manager.dart';
2024-06-15 00:01:08 -04:00
import '../../../proto/proto.dart' as proto;
2024-06-11 21:27:20 -04:00
import '../../../theme/theme.dart';
import '../../../tools/tools.dart';
import '../../../veilid_processor/veilid_processor.dart';
2024-06-11 21:27:20 -04:00
import 'menu_item_widget.dart';
class DrawerMenu extends StatefulWidget {
const DrawerMenu({super.key});
@override
2024-07-08 21:29:52 -04:00
State<DrawerMenu> createState() => _DrawerMenuState();
2024-06-11 21:27:20 -04:00
}
class _DrawerMenuState extends State<DrawerMenu> {
@override
void initState() {
super.initState();
}
@override
void dispose() {
super.dispose();
}
void _doSwitchClick(TypedKey superIdentityRecordKey) {
singleFuture(this, () async {
await AccountRepository.instance.switchToAccount(superIdentityRecordKey);
});
2024-06-11 21:27:20 -04:00
}
2024-07-03 20:59:54 -04:00
void _doEditClick(TypedKey superIdentityRecordKey,
proto.Profile existingProfile, OwnedDHTRecordPointer accountRecord) {
2024-06-15 00:01:08 -04:00
singleFuture(this, () async {
await GoRouterHelper(context).push('/edit_account',
2024-07-03 20:59:54 -04:00
extra: [superIdentityRecordKey, existingProfile, accountRecord]);
2024-06-15 00:01:08 -04:00
});
2024-06-11 21:27:20 -04:00
}
2024-07-06 20:09:18 -04:00
Widget _wrapInBox(
{required Widget child,
required Color color,
required double borderRadius}) =>
2024-06-11 21:27:20 -04:00
DecoratedBox(
decoration: ShapeDecoration(
color: color,
shape: RoundedRectangleBorder(
2024-07-06 20:09:18 -04:00
borderRadius: BorderRadius.circular(borderRadius))),
2024-06-11 21:27:20 -04:00
child: child);
Widget _makeAccountWidget(
{required String name,
required bool selected,
required ScaleColor scale,
2024-07-06 20:09:18 -04:00
required ScaleConfig scaleConfig,
2024-06-11 21:27:20 -04:00
required bool loggedIn,
required void Function()? callback,
required void Function()? footerCallback}) {
2024-06-11 21:27:20 -04:00
final theme = Theme.of(context);
final abbrev = name.split(' ').map((s) => s.isEmpty ? '' : s[0]).join();
late final String shortname;
if (abbrev.length >= 3) {
shortname = abbrev[0] + abbrev[1] + abbrev[abbrev.length - 1];
} else {
shortname = abbrev;
}
2024-07-06 20:09:18 -04:00
late final Color background;
late final Color hoverBackground;
late final Color activeBackground;
late final Color border;
late final Color hoverBorder;
late final Color activeBorder;
if (scaleConfig.useVisualIndicators && !scaleConfig.preferBorders) {
background = loggedIn ? scale.border : scale.subtleBorder;
hoverBackground = background;
activeBackground = background;
border =
selected ? scale.activeElementBackground : scale.elementBackground;
hoverBorder = border;
activeBorder = border;
} else {
background =
selected ? scale.activeElementBackground : scale.elementBackground;
hoverBackground = scale.hoverElementBackground;
activeBackground = scale.activeElementBackground;
border = loggedIn ? scale.border : scale.subtleBorder;
hoverBorder = scale.hoverBorder;
activeBorder = scale.primary;
}
2024-06-21 22:44:35 -04:00
final avatar = Container(
height: 34,
width: 34,
decoration: BoxDecoration(
shape: BoxShape.circle,
2024-07-08 21:29:52 -04:00
border: scaleConfig.preferBorders
? Border.all(
color: border,
width: 2,
strokeAlign: BorderSide.strokeAlignOutside)
: null,
2024-06-21 22:44:35 -04:00
color: Colors.blue,
),
child: AvatarImage(
//size: 32,
backgroundColor: loggedIn ? scale.primary : scale.elementBackground,
foregroundColor: loggedIn ? scale.primaryText : scale.subtleText,
child: Text(shortname, style: theme.textTheme.titleLarge)));
2024-06-11 21:27:20 -04:00
return AnimatedPadding(
2024-07-06 20:09:18 -04:00
padding: EdgeInsets.fromLTRB(selected ? 0 : 8, selected ? 0 : 2,
selected ? 0 : 8, selected ? 0 : 2),
duration: const Duration(milliseconds: 50),
child: MenuItemWidget(
title: name,
headerWidget: avatar,
2024-07-06 20:09:18 -04:00
titleStyle: theme.textTheme.titleSmall!
.copyWith(color: scaleConfig.useVisualIndicators ? border : null),
foregroundColor: scale.primary,
2024-07-06 20:09:18 -04:00
backgroundColor: background,
backgroundHoverColor: hoverBackground,
backgroundFocusColor: activeBackground,
2024-07-08 21:29:52 -04:00
borderColor:
(scaleConfig.preferBorders || scaleConfig.useVisualIndicators)
? border
: null,
borderHoverColor:
(scaleConfig.preferBorders || scaleConfig.useVisualIndicators)
? hoverBorder
: null,
borderFocusColor:
(scaleConfig.preferBorders || scaleConfig.useVisualIndicators)
? activeBorder
: null,
2024-07-11 23:04:08 -04:00
borderRadius: 12 * scaleConfig.borderRadiusScale,
callback: callback,
footerButtonIcon: loggedIn ? Icons.edit_outlined : null,
footerCallback: footerCallback,
2024-07-06 20:09:18 -04:00
footerButtonIconColor: border,
footerButtonIconHoverColor: hoverBackground,
footerButtonIconFocusColor: activeBackground,
));
2024-06-11 21:27:20 -04:00
}
2024-07-08 21:29:52 -04:00
List<Widget> _getAccountList(
2024-06-16 22:12:24 -04:00
{required IList<LocalAccount> localAccounts,
required TypedKey? activeLocalAccount,
2024-06-19 11:35:51 -04:00
required PerAccountCollectionBlocMapState
perAccountCollectionBlocMapState}) {
2024-06-11 21:27:20 -04:00
final theme = Theme.of(context);
final scaleScheme = theme.extension<ScaleScheme>()!;
2024-07-06 20:09:18 -04:00
final scaleConfig = theme.extension<ScaleConfig>()!;
2024-06-11 21:27:20 -04:00
final loggedInAccounts = <Widget>[];
final loggedOutAccounts = <Widget>[];
for (final la in localAccounts) {
final superIdentityRecordKey = la.superIdentity.recordKey;
// See if this account is logged in
2024-07-03 20:59:54 -04:00
final perAccountState =
perAccountCollectionBlocMapState.get(superIdentityRecordKey);
final avAccountRecordState = perAccountState?.avAccountRecordState;
if (perAccountState != null && avAccountRecordState != null) {
2024-06-11 21:27:20 -04:00
// Account is logged in
2024-07-08 21:29:52 -04:00
final scale = scaleConfig.useVisualIndicators
? theme.extension<ScaleScheme>()!.primaryScale
: theme.extension<ScaleScheme>()!.tertiaryScale;
2024-06-19 11:35:51 -04:00
final loggedInAccount = avAccountRecordState.when(
2024-06-11 21:27:20 -04:00
data: (value) => _makeAccountWidget(
name: value.profile.name,
scale: scale,
2024-07-06 20:09:18 -04:00
scaleConfig: scaleConfig,
selected: superIdentityRecordKey == activeLocalAccount,
2024-06-11 21:27:20 -04:00
loggedIn: true,
callback: () {
_doSwitchClick(superIdentityRecordKey);
},
footerCallback: () {
2024-07-03 20:59:54 -04:00
_doEditClick(
superIdentityRecordKey,
value.profile,
perAccountState.accountInfo.userLogin!.accountRecordInfo
.accountRecord);
2024-06-11 21:27:20 -04:00
}),
loading: () => _wrapInBox(
child: buildProgressIndicator(),
2024-07-06 20:09:18 -04:00
color: scaleScheme.grayScale.subtleBorder,
2024-07-11 23:04:08 -04:00
borderRadius: 12 * scaleConfig.borderRadiusScale),
2024-06-11 21:27:20 -04:00
error: (err, st) => _wrapInBox(
child: errorPage(err, st),
2024-07-06 20:09:18 -04:00
color: scaleScheme.errorScale.subtleBorder,
2024-07-11 23:04:08 -04:00
borderRadius: 12 * scaleConfig.borderRadiusScale),
2024-06-11 21:27:20 -04:00
);
loggedInAccounts.add(loggedInAccount.paddingLTRB(0, 0, 0, 8));
2024-06-11 21:27:20 -04:00
} else {
// Account is not logged in
final scale = theme.extension<ScaleScheme>()!.grayScale;
2024-06-11 21:27:20 -04:00
final loggedOutAccount = _makeAccountWidget(
name: la.name,
scale: scale,
2024-07-06 20:09:18 -04:00
scaleConfig: scaleConfig,
selected: superIdentityRecordKey == activeLocalAccount,
loggedIn: false,
callback: () => {_doSwitchClick(superIdentityRecordKey)},
footerCallback: null,
);
2024-06-11 21:27:20 -04:00
loggedOutAccounts.add(loggedOutAccount);
}
}
// Assemble main menu
2024-07-08 21:29:52 -04:00
return <Widget>[...loggedInAccounts, ...loggedOutAccounts];
2024-06-11 21:27:20 -04:00
}
Widget _getButton(
2024-07-06 20:09:18 -04:00
{required Icon icon,
required ScaleColor scale,
required ScaleConfig scaleConfig,
required String tooltip,
required void Function()? onPressed}) {
late final Color background;
late final Color hoverBackground;
late final Color activeBackground;
late final Color border;
late final Color hoverBorder;
late final Color activeBorder;
if (scaleConfig.useVisualIndicators && !scaleConfig.preferBorders) {
background = scale.border;
hoverBackground = scale.hoverBorder;
activeBackground = scale.primary;
border = scale.elementBackground;
hoverBorder = scale.hoverElementBackground;
activeBorder = scale.activeElementBackground;
} else {
background = scale.elementBackground;
hoverBackground = scale.hoverElementBackground;
activeBackground = scale.activeElementBackground;
border = scale.border;
hoverBorder = scale.hoverBorder;
activeBorder = scale.primary;
}
return IconButton(
icon: icon,
color: border,
2024-07-11 23:04:08 -04:00
constraints: const BoxConstraints.expand(height: 48, width: 48),
2024-07-06 20:09:18 -04:00
style: ButtonStyle(
backgroundColor: WidgetStateProperty.resolveWith((states) {
if (states.contains(WidgetState.hovered)) {
return hoverBackground;
}
if (states.contains(WidgetState.focused)) {
return activeBackground;
}
return background;
}), shape: WidgetStateProperty.resolveWith((states) {
if (states.contains(WidgetState.hovered)) {
return RoundedRectangleBorder(
2024-07-08 21:29:52 -04:00
side: BorderSide(color: hoverBorder, width: 2),
2024-07-06 20:09:18 -04:00
borderRadius: BorderRadius.all(
2024-07-11 23:04:08 -04:00
Radius.circular(12 * scaleConfig.borderRadiusScale)));
2024-07-06 20:09:18 -04:00
}
if (states.contains(WidgetState.focused)) {
2024-06-11 21:27:20 -04:00
return RoundedRectangleBorder(
2024-07-08 21:29:52 -04:00
side: BorderSide(color: activeBorder, width: 2),
2024-07-06 20:09:18 -04:00
borderRadius: BorderRadius.all(
2024-07-11 23:04:08 -04:00
Radius.circular(12 * scaleConfig.borderRadiusScale)));
2024-07-06 20:09:18 -04:00
}
return RoundedRectangleBorder(
2024-07-08 21:29:52 -04:00
side: BorderSide(color: border, width: 2),
2024-07-06 20:09:18 -04:00
borderRadius: BorderRadius.all(
2024-07-11 23:04:08 -04:00
Radius.circular(12 * scaleConfig.borderRadiusScale)));
2024-07-06 20:09:18 -04:00
})),
tooltip: tooltip,
onPressed: onPressed);
}
2024-06-11 21:27:20 -04:00
Widget _getBottomButtons() {
final theme = Theme.of(context);
final scale = theme.extension<ScaleScheme>()!;
2024-07-06 20:09:18 -04:00
final scaleConfig = theme.extension<ScaleConfig>()!;
2024-06-11 21:27:20 -04:00
final settingsButton = _getButton(
icon: const Icon(Icons.settings),
tooltip: translate('menu.settings_tooltip'),
scale: scale.tertiaryScale,
2024-07-06 20:09:18 -04:00
scaleConfig: scaleConfig,
2024-06-11 21:27:20 -04:00
onPressed: () async {
await GoRouterHelper(context).push('/settings');
}).paddingLTRB(0, 0, 16, 0);
final addButton = _getButton(
icon: const Icon(Icons.add),
tooltip: translate('menu.add_account_tooltip'),
scale: scale.tertiaryScale,
2024-07-06 20:09:18 -04:00
scaleConfig: scaleConfig,
2024-06-11 21:27:20 -04:00
onPressed: () async {
await GoRouterHelper(context).push('/new_account');
}).paddingLTRB(0, 0, 16, 0);
return Row(
mainAxisAlignment: MainAxisAlignment.center,
2024-07-08 21:29:52 -04:00
children: [settingsButton, addButton]).paddingLTRB(0, 16, 0, 16);
2024-06-11 21:27:20 -04:00
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final scale = theme.extension<ScaleScheme>()!;
2024-06-21 22:44:35 -04:00
final scaleConfig = theme.extension<ScaleConfig>()!;
2024-06-11 21:27:20 -04:00
//final textTheme = theme.textTheme;
2024-06-16 22:12:24 -04:00
final localAccounts = context.watch<LocalAccountsCubit>().state;
2024-06-19 11:35:51 -04:00
final perAccountCollectionBlocMapState =
2024-06-18 21:20:06 -04:00
context.watch<PerAccountCollectionBlocMapCubit>().state;
2024-06-16 22:12:24 -04:00
final activeLocalAccount = context.watch<ActiveLocalAccountCubit>().state;
2024-06-11 21:27:20 -04:00
final gradient = LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
2024-07-08 21:29:52 -04:00
scale.tertiaryScale.border,
2024-07-06 20:09:18 -04:00
scale.tertiaryScale.subtleBorder,
2024-06-11 21:27:20 -04:00
]);
return DecoratedBox(
decoration: ShapeDecoration(
shadows: [
2024-07-06 20:09:18 -04:00
if (scaleConfig.useVisualIndicators && !scaleConfig.preferBorders)
BoxShadow(
color: scale.tertiaryScale.primary.darken(80),
spreadRadius: 2,
)
else if (scaleConfig.useVisualIndicators &&
scaleConfig.preferBorders)
BoxShadow(
color: scale.tertiaryScale.border,
spreadRadius: 2,
)
else
BoxShadow(
color: scale.tertiaryScale.primary.darken(40),
blurRadius: 6,
offset: const Offset(
0,
4,
),
2024-06-11 21:27:20 -04:00
),
],
2024-07-06 20:09:18 -04:00
gradient: scaleConfig.useVisualIndicators ? null : gradient,
color: scaleConfig.useVisualIndicators
? (scaleConfig.preferBorders
? scale.tertiaryScale.appBackground
: scale.tertiaryScale.subtleBorder)
: null,
shape: RoundedRectangleBorder(
side: scaleConfig.preferBorders
? BorderSide(color: scale.tertiaryScale.primary, width: 2)
: BorderSide.none,
2024-06-11 21:27:20 -04:00
borderRadius: BorderRadius.only(
2024-07-06 20:09:18 -04:00
topRight: Radius.circular(16 * scaleConfig.borderRadiusScale),
bottomRight:
Radius.circular(16 * scaleConfig.borderRadiusScale)))),
2024-06-11 21:27:20 -04:00
child: Column(children: [
FittedBox(
fit: BoxFit.scaleDown,
2024-07-06 20:09:18 -04:00
child: ColorFiltered(
colorFilter: ColorFilter.mode(
theme.brightness == Brightness.light
? scale.tertiaryScale.primary
: scale.tertiaryScale.border,
scaleConfig.preferBorders
? BlendMode.modulate
: BlendMode.dst),
child: Row(children: [
SvgPicture.asset(
height: 48,
'assets/images/icon.svg',
colorFilter: scaleConfig.useVisualIndicators
? grayColorFilter
: null)
.paddingLTRB(0, 0, 16, 0),
SvgPicture.asset(
2024-06-21 22:44:35 -04:00
height: 48,
2024-07-06 20:09:18 -04:00
'assets/images/title.svg',
2024-06-21 22:44:35 -04:00
colorFilter: scaleConfig.useVisualIndicators
? grayColorFilter
2024-07-06 20:09:18 -04:00
: null),
]))),
2024-07-08 21:29:52 -04:00
Text(translate('menu.accounts'),
style: theme.textTheme.titleMedium!.copyWith(
color: scaleConfig.preferBorders
? scale.tertiaryScale.border
: scale.tertiaryScale.borderText))
.paddingLTRB(0, 16, 0, 16),
ListView(
shrinkWrap: true,
children: _getAccountList(
localAccounts: localAccounts,
activeLocalAccount: activeLocalAccount,
perAccountCollectionBlocMapState:
perAccountCollectionBlocMapState))
.expanded(),
2024-06-11 21:27:20 -04:00
_getBottomButtons(),
Row(children: [
2024-07-06 20:09:18 -04:00
Text('${translate('menu.version')} $packageInfoVersion',
2024-07-11 23:04:08 -04:00
style: theme.textTheme.labelMedium!.copyWith(
color: scaleConfig.preferBorders
? scale.tertiaryScale.hoverBorder
: scale.tertiaryScale.subtleBackground)),
const Spacer(),
SignalStrengthMeterWidget(
2024-07-11 23:04:08 -04:00
color: scaleConfig.preferBorders
? scale.tertiaryScale.hoverBorder
: scale.tertiaryScale.subtleBackground,
inactiveColor: scaleConfig.preferBorders
? scale.tertiaryScale.border
: scale.tertiaryScale.elementBackground,
),
])
2024-06-11 21:27:20 -04:00
]).paddingAll(16),
2024-07-06 20:09:18 -04:00
).paddingLTRB(0, 2, 2, 2);
2024-06-11 21:27:20 -04:00
}
}