mirror of
https://gitlab.com/veilid/veilidchat.git
synced 2024-12-28 09:09:27 -05:00
add multiple accounts menu
This commit is contained in:
parent
b0d4e35c6f
commit
87bb1657c7
@ -2,8 +2,9 @@
|
||||
"app": {
|
||||
"title": "VeilidChat"
|
||||
},
|
||||
"app_bar": {
|
||||
"settings_tooltip": "Settings"
|
||||
"menu": {
|
||||
"settings_tooltip": "Settings",
|
||||
"add_account_tooltip": "Add Account"
|
||||
},
|
||||
"pager": {
|
||||
"chats": "Chats",
|
||||
|
@ -4,7 +4,9 @@ import 'package:veilid_support/veilid_support.dart';
|
||||
|
||||
import '../../proto/proto.dart' as proto;
|
||||
|
||||
class AccountRecordCubit extends DefaultDHTRecordCubit<proto.Account> {
|
||||
typedef AccountRecordState = proto.Account;
|
||||
|
||||
class AccountRecordCubit extends DefaultDHTRecordCubit<AccountRecordState> {
|
||||
AccountRecordCubit({
|
||||
required super.open,
|
||||
}) : super(decodeState: proto.Account.fromBuffer);
|
||||
|
@ -0,0 +1,36 @@
|
||||
import 'package:async_tools/async_tools.dart';
|
||||
import 'package:bloc_advanced_tools/bloc_advanced_tools.dart';
|
||||
import 'package:veilid_support/veilid_support.dart';
|
||||
|
||||
import '../../account_manager/account_manager.dart';
|
||||
|
||||
typedef AccountRecordsBlocMapState
|
||||
= BlocMapState<TypedKey, AsyncValue<AccountRecordState>>;
|
||||
|
||||
// Map of the logged in user accounts to their account information
|
||||
class AccountRecordsBlocMapCubit extends BlocMapCubit<TypedKey,
|
||||
AsyncValue<AccountRecordState>, AccountRecordCubit>
|
||||
with StateMapFollower<UserLoginsState, TypedKey, UserLogin> {
|
||||
AccountRecordsBlocMapCubit(AccountRepository accountRepository)
|
||||
: _accountRepository = accountRepository;
|
||||
|
||||
// Add account record cubit
|
||||
Future<void> _addAccountRecordCubit({required UserLogin userLogin}) async =>
|
||||
add(() => MapEntry(
|
||||
userLogin.superIdentityRecordKey,
|
||||
AccountRecordCubit(
|
||||
open: () => _accountRepository.openAccountRecord(userLogin))));
|
||||
|
||||
/// StateFollower /////////////////////////
|
||||
|
||||
@override
|
||||
Future<void> removeFromState(TypedKey key) => remove(key);
|
||||
|
||||
@override
|
||||
Future<void> updateState(TypedKey key, UserLogin value) async {
|
||||
await _addAccountRecordCubit(userLogin: value);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
final AccountRepository _accountRepository;
|
||||
}
|
@ -3,7 +3,7 @@ import 'dart:async';
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:veilid_support/veilid_support.dart';
|
||||
|
||||
import '../repository/account_repository/account_repository.dart';
|
||||
import '../repository/account_repository.dart';
|
||||
|
||||
class ActiveLocalAccountCubit extends Cubit<TypedKey?> {
|
||||
ActiveLocalAccountCubit(AccountRepository accountRepository)
|
||||
|
@ -1,4 +1,5 @@
|
||||
export 'account_record_cubit.dart';
|
||||
export 'account_records_bloc_map_cubit.dart';
|
||||
export 'active_local_account_cubit.dart';
|
||||
export 'local_accounts_cubit.dart';
|
||||
export 'user_logins_cubit.dart';
|
||||
|
@ -4,7 +4,7 @@ import 'package:bloc/bloc.dart';
|
||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
|
||||
import '../models/models.dart';
|
||||
import '../repository/account_repository/account_repository.dart';
|
||||
import '../repository/account_repository.dart';
|
||||
|
||||
class LocalAccountsCubit extends Cubit<IList<LocalAccount>> {
|
||||
LocalAccountsCubit(AccountRepository accountRepository)
|
||||
|
@ -1,12 +1,17 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:bloc/bloc.dart';
|
||||
import 'package:bloc_advanced_tools/bloc_advanced_tools.dart';
|
||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
import 'package:veilid_support/veilid_support.dart';
|
||||
|
||||
import '../models/models.dart';
|
||||
import '../repository/account_repository/account_repository.dart';
|
||||
import '../repository/account_repository.dart';
|
||||
|
||||
class UserLoginsCubit extends Cubit<IList<UserLogin>> {
|
||||
typedef UserLoginsState = IList<UserLogin>;
|
||||
|
||||
class UserLoginsCubit extends Cubit<UserLoginsState>
|
||||
with StateMapFollowable<UserLoginsState, TypedKey, UserLogin> {
|
||||
UserLoginsCubit(AccountRepository accountRepository)
|
||||
: _accountRepository = accountRepository,
|
||||
super(accountRepository.getUserLogins()) {
|
||||
@ -30,6 +35,16 @@ class UserLoginsCubit extends Cubit<IList<UserLogin>> {
|
||||
await _accountRepositorySubscription.cancel();
|
||||
}
|
||||
|
||||
/// StateMapFollowable /////////////////////////
|
||||
@override
|
||||
IMap<TypedKey, UserLogin> getStateMap(UserLoginsState state) {
|
||||
final stateValue = state;
|
||||
return IMap.fromIterable(stateValue,
|
||||
keyMapper: (e) => e.superIdentityRecordKey, valueMapper: (e) => e);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
final AccountRepository _accountRepository;
|
||||
late final StreamSubscription<AccountRepositoryChange>
|
||||
_accountRepositorySubscription;
|
||||
|
@ -3,9 +3,9 @@ import 'dart:async';
|
||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
import 'package:veilid_support/veilid_support.dart';
|
||||
|
||||
import '../../../../proto/proto.dart' as proto;
|
||||
import '../../../tools/tools.dart';
|
||||
import '../../models/models.dart';
|
||||
import '../../../proto/proto.dart' as proto;
|
||||
import '../../tools/tools.dart';
|
||||
import '../models/models.dart';
|
||||
|
||||
const String veilidChatAccountKey = 'com.veilid.veilidchat';
|
||||
|
@ -1 +1 @@
|
||||
export 'account_repository/account_repository.dart';
|
||||
export 'account_repository.dart';
|
||||
|
@ -1,35 +0,0 @@
|
||||
import 'package:awesome_extensions/awesome_extensions.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import '../../proto/proto.dart' as proto;
|
||||
import '../../theme/theme.dart';
|
||||
import '../account_manager.dart';
|
||||
|
||||
class SwitchAccountWidget extends StatelessWidget {
|
||||
const SwitchAccountWidget({
|
||||
super.key,
|
||||
});
|
||||
//
|
||||
|
||||
@override
|
||||
// ignore: prefer_expression_function_bodies
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final scale = theme.extension<ScaleScheme>()!;
|
||||
final textTheme = theme.textTheme;
|
||||
|
||||
final accountRepo = AccountRepository.instance;
|
||||
final localAccounts = accountRepo.getLocalAccounts();
|
||||
for (final la in localAccounts) {
|
||||
//
|
||||
}
|
||||
|
||||
return DecoratedBox(
|
||||
decoration: ShapeDecoration(
|
||||
color: scale.primaryScale.border,
|
||||
shape:
|
||||
RoundedRectangleBorder(borderRadius: BorderRadius.circular(16))),
|
||||
child: Column(children: []),
|
||||
);
|
||||
}
|
||||
}
|
@ -129,7 +129,11 @@ class VeilidChatApp extends StatelessWidget {
|
||||
BlocProvider<PreferencesCubit>(
|
||||
create: (context) =>
|
||||
PreferencesCubit(PreferencesRepository.instance),
|
||||
)
|
||||
),
|
||||
BlocProvider<AccountRecordsBlocMapCubit>(
|
||||
create: (context) =>
|
||||
AccountRecordsBlocMapCubit(AccountRepository.instance)
|
||||
..follow(context.read<UserLoginsCubit>())),
|
||||
],
|
||||
child: BackgroundTicker(
|
||||
child: _buildShortcuts(
|
||||
|
@ -140,5 +140,7 @@ class ChatListCubit extends DHTShortArrayCubit<proto.Chat>
|
||||
valueMapper: (e) => e.value);
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
final ActiveChatCubit activeChatCubit;
|
||||
}
|
||||
|
262
lib/layout/home/drawer_menu/drawer_menu.dart
Normal file
262
lib/layout/home/drawer_menu/drawer_menu.dart
Normal file
@ -0,0 +1,262 @@
|
||||
import 'package:awesome_extensions/awesome_extensions.dart';
|
||||
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';
|
||||
import '../../../theme/theme.dart';
|
||||
import '../../../tools/tools.dart';
|
||||
import 'menu_item_widget.dart';
|
||||
|
||||
class DrawerMenu extends StatefulWidget {
|
||||
const DrawerMenu({super.key});
|
||||
|
||||
@override
|
||||
State createState() => _DrawerMenuState();
|
||||
}
|
||||
|
||||
class _DrawerMenuState extends State<DrawerMenu> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void _doLoginClick(TypedKey superIdentityRecordKey) {
|
||||
//
|
||||
}
|
||||
|
||||
void _doEditClick(TypedKey superIdentityRecordKey) {
|
||||
//
|
||||
}
|
||||
|
||||
Widget _wrapInBox({required Widget child, required Color color}) =>
|
||||
DecoratedBox(
|
||||
decoration: ShapeDecoration(
|
||||
color: color,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(16))),
|
||||
child: child);
|
||||
|
||||
Widget _makeAccountWidget(
|
||||
{required String name,
|
||||
required bool loggedIn,
|
||||
required void Function() clickHandler}) {
|
||||
final theme = Theme.of(context);
|
||||
final scale = theme.extension<ScaleScheme>()!.tertiaryScale;
|
||||
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;
|
||||
}
|
||||
|
||||
final avatar = AvatarImage(
|
||||
size: 32,
|
||||
backgroundColor: loggedIn ? scale.primary : scale.elementBackground,
|
||||
foregroundColor: loggedIn ? scale.primaryText : scale.subtleText,
|
||||
child: Text(shortname, style: theme.textTheme.titleLarge));
|
||||
|
||||
return MenuItemWidget(
|
||||
title: name,
|
||||
headerWidget: avatar,
|
||||
titleStyle: theme.textTheme.titleLarge!,
|
||||
foregroundColor: scale.primary,
|
||||
backgroundColor: scale.elementBackground,
|
||||
backgroundHoverColor: scale.hoverElementBackground,
|
||||
backgroundFocusColor: scale.activeElementBackground,
|
||||
borderColor: scale.border,
|
||||
borderHoverColor: scale.hoverBorder,
|
||||
borderFocusColor: scale.primary,
|
||||
footerButtonIcon: loggedIn ? Icons.edit_outlined : Icons.login_outlined,
|
||||
footerCallback: clickHandler,
|
||||
footerButtonIconColor: scale.border,
|
||||
footerButtonIconHoverColor: scale.hoverElementBackground,
|
||||
footerButtonIconFocusColor: scale.activeElementBackground,
|
||||
);
|
||||
}
|
||||
|
||||
Widget _getAccountList(
|
||||
{required TypedKey? activeLocalAccount,
|
||||
required AccountRecordsBlocMapState accountRecords}) {
|
||||
final theme = Theme.of(context);
|
||||
final scale = theme.extension<ScaleScheme>()!;
|
||||
|
||||
final accountRepo = AccountRepository.instance;
|
||||
final localAccounts = accountRepo.getLocalAccounts();
|
||||
//final userLogins = accountRepo.getUserLogins();
|
||||
|
||||
final loggedInAccounts = <Widget>[];
|
||||
final loggedOutAccounts = <Widget>[];
|
||||
|
||||
for (final la in localAccounts) {
|
||||
final superIdentityRecordKey = la.superIdentity.recordKey;
|
||||
|
||||
// See if this account is logged in
|
||||
final acctRecord = accountRecords.get(superIdentityRecordKey);
|
||||
if (acctRecord != null) {
|
||||
// Account is logged in
|
||||
final loggedInAccount = acctRecord.when(
|
||||
data: (value) => _makeAccountWidget(
|
||||
name: value.profile.name,
|
||||
loggedIn: true,
|
||||
clickHandler: () {
|
||||
_doEditClick(superIdentityRecordKey);
|
||||
}),
|
||||
loading: () => _wrapInBox(
|
||||
child: buildProgressIndicator(),
|
||||
color: scale.grayScale.subtleBorder),
|
||||
error: (err, st) => _wrapInBox(
|
||||
child: errorPage(err, st), color: scale.errorScale.subtleBorder),
|
||||
);
|
||||
loggedInAccounts.add(loggedInAccount);
|
||||
} else {
|
||||
// Account is not logged in
|
||||
final loggedOutAccount = _makeAccountWidget(
|
||||
name: la.name,
|
||||
loggedIn: false,
|
||||
clickHandler: () {
|
||||
_doLoginClick(superIdentityRecordKey);
|
||||
});
|
||||
loggedOutAccounts.add(loggedOutAccount);
|
||||
}
|
||||
}
|
||||
|
||||
// Assemble main menu
|
||||
final mainMenu = <Widget>[...loggedInAccounts, ...loggedOutAccounts];
|
||||
|
||||
// Return main menu widgets
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[...mainMenu],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _getButton(
|
||||
{required Icon icon,
|
||||
required ScaleColor scale,
|
||||
required String tooltip,
|
||||
required void Function()? onPressed}) =>
|
||||
IconButton(
|
||||
icon: icon,
|
||||
color: scale.hoverBorder,
|
||||
constraints: const BoxConstraints.expand(height: 64, width: 64),
|
||||
style: ButtonStyle(
|
||||
backgroundColor: WidgetStateProperty.resolveWith((states) {
|
||||
if (states.contains(WidgetState.hovered)) {
|
||||
return scale.hoverElementBackground;
|
||||
}
|
||||
if (states.contains(WidgetState.focused)) {
|
||||
return scale.activeElementBackground;
|
||||
}
|
||||
return scale.elementBackground;
|
||||
}), shape: WidgetStateProperty.resolveWith((states) {
|
||||
if (states.contains(WidgetState.hovered)) {
|
||||
return RoundedRectangleBorder(
|
||||
side: BorderSide(color: scale.hoverBorder),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(16)));
|
||||
}
|
||||
if (states.contains(WidgetState.focused)) {
|
||||
return RoundedRectangleBorder(
|
||||
side: BorderSide(color: scale.primary),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(16)));
|
||||
}
|
||||
return RoundedRectangleBorder(
|
||||
side: BorderSide(color: scale.border),
|
||||
borderRadius: const BorderRadius.all(Radius.circular(16)));
|
||||
})),
|
||||
tooltip: tooltip,
|
||||
onPressed: onPressed);
|
||||
|
||||
Widget _getBottomButtons() {
|
||||
final theme = Theme.of(context);
|
||||
final scale = theme.extension<ScaleScheme>()!;
|
||||
|
||||
final settingsButton = _getButton(
|
||||
icon: const Icon(Icons.settings),
|
||||
tooltip: translate('menu.settings_tooltip'),
|
||||
scale: scale.tertiaryScale,
|
||||
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,
|
||||
onPressed: () async {
|
||||
await GoRouterHelper(context).push('/new_account');
|
||||
}).paddingLTRB(0, 0, 16, 0);
|
||||
|
||||
return Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [settingsButton, addButton]).paddingLTRB(0, 16, 0, 0);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final scale = theme.extension<ScaleScheme>()!;
|
||||
//final textTheme = theme.textTheme;
|
||||
final accountRecords = context.watch<AccountRecordsBlocMapCubit>().state;
|
||||
final activeLocalAccount = context.watch<ActiveLocalAccountCubit>().state;
|
||||
final gradient = LinearGradient(
|
||||
begin: Alignment.topLeft,
|
||||
end: Alignment.bottomRight,
|
||||
colors: [
|
||||
scale.tertiaryScale.hoverElementBackground,
|
||||
scale.tertiaryScale.subtleBackground,
|
||||
]);
|
||||
|
||||
return DecoratedBox(
|
||||
decoration: ShapeDecoration(
|
||||
shadows: [
|
||||
BoxShadow(
|
||||
color: scale.tertiaryScale.appBackground,
|
||||
blurRadius: 6,
|
||||
offset: const Offset(
|
||||
0,
|
||||
3,
|
||||
),
|
||||
),
|
||||
],
|
||||
gradient: gradient,
|
||||
shape: const RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.only(
|
||||
topRight: Radius.circular(16),
|
||||
bottomRight: Radius.circular(16)))),
|
||||
child: Column(children: [
|
||||
FittedBox(
|
||||
fit: BoxFit.scaleDown,
|
||||
child: Row(children: [
|
||||
SvgPicture.asset(
|
||||
height: 48,
|
||||
'assets/images/icon.svg',
|
||||
).paddingLTRB(0, 0, 16, 0),
|
||||
SvgPicture.asset(
|
||||
height: 48,
|
||||
'assets/images/title.svg',
|
||||
),
|
||||
])),
|
||||
const Spacer(),
|
||||
_getAccountList(
|
||||
activeLocalAccount: activeLocalAccount,
|
||||
accountRecords: accountRecords),
|
||||
_getBottomButtons(),
|
||||
const Spacer(),
|
||||
Text('Version $packageInfoVersion',
|
||||
style: theme.textTheme.labelMedium!
|
||||
.copyWith(color: scale.tertiaryScale.hoverBorder))
|
||||
]).paddingAll(16),
|
||||
);
|
||||
}
|
||||
}
|
130
lib/layout/home/drawer_menu/menu_item_widget.dart
Normal file
130
lib/layout/home/drawer_menu/menu_item_widget.dart
Normal file
@ -0,0 +1,130 @@
|
||||
import 'package:awesome_extensions/awesome_extensions.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
class MenuItemWidget extends StatelessWidget {
|
||||
const MenuItemWidget({
|
||||
required this.title,
|
||||
required this.titleStyle,
|
||||
required this.foregroundColor,
|
||||
this.headerWidget,
|
||||
this.widthBox,
|
||||
this.callback,
|
||||
this.backgroundColor,
|
||||
this.backgroundHoverColor,
|
||||
this.backgroundFocusColor,
|
||||
this.borderColor,
|
||||
this.borderHoverColor,
|
||||
this.borderFocusColor,
|
||||
this.footerButtonIcon,
|
||||
this.footerButtonIconColor,
|
||||
this.footerButtonIconHoverColor,
|
||||
this.footerButtonIconFocusColor,
|
||||
this.footerCallback,
|
||||
super.key,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => TextButton(
|
||||
onPressed: () => callback,
|
||||
style: TextButton.styleFrom(foregroundColor: foregroundColor).copyWith(
|
||||
backgroundColor: WidgetStateProperty.resolveWith((states) {
|
||||
if (states.contains(WidgetState.hovered)) {
|
||||
return backgroundHoverColor;
|
||||
}
|
||||
if (states.contains(WidgetState.focused)) {
|
||||
return backgroundFocusColor;
|
||||
}
|
||||
return backgroundColor;
|
||||
}),
|
||||
side: WidgetStateBorderSide.resolveWith((states) {
|
||||
if (states.contains(WidgetState.hovered)) {
|
||||
return borderColor != null
|
||||
? BorderSide(color: borderHoverColor!)
|
||||
: null;
|
||||
}
|
||||
if (states.contains(WidgetState.focused)) {
|
||||
return borderColor != null
|
||||
? BorderSide(color: borderFocusColor!)
|
||||
: null;
|
||||
}
|
||||
return borderColor != null ? BorderSide(color: borderColor!) : null;
|
||||
}),
|
||||
shape: WidgetStateProperty.all(
|
||||
RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)))),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
if (headerWidget != null) headerWidget!,
|
||||
if (widthBox != null) widthBox!,
|
||||
Expanded(
|
||||
child: FittedBox(
|
||||
alignment: Alignment.centerLeft,
|
||||
fit: BoxFit.scaleDown,
|
||||
child: Text(
|
||||
title,
|
||||
style: titleStyle,
|
||||
).paddingAll(8)),
|
||||
),
|
||||
if (footerButtonIcon != null)
|
||||
IconButton.outlined(
|
||||
color: footerButtonIconColor,
|
||||
focusColor: footerButtonIconFocusColor,
|
||||
hoverColor: footerButtonIconHoverColor,
|
||||
icon: Icon(
|
||||
footerButtonIcon,
|
||||
size: 24,
|
||||
),
|
||||
onPressed: footerCallback),
|
||||
],
|
||||
),
|
||||
));
|
||||
|
||||
@override
|
||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||
super.debugFillProperties(properties);
|
||||
properties
|
||||
..add(DiagnosticsProperty<TextStyle?>('textStyle', titleStyle))
|
||||
..add(ObjectFlagProperty<void Function()?>.has('callback', callback))
|
||||
..add(DiagnosticsProperty<Color>('foregroundColor', foregroundColor))
|
||||
..add(StringProperty('title', title))
|
||||
..add(
|
||||
DiagnosticsProperty<IconData?>('footerButtonIcon', footerButtonIcon))
|
||||
..add(ObjectFlagProperty<void Function()?>.has(
|
||||
'footerCallback', footerCallback))
|
||||
..add(ColorProperty('footerButtonIconColor', footerButtonIconColor))
|
||||
..add(ColorProperty(
|
||||
'footerButtonIconHoverColor', footerButtonIconHoverColor))
|
||||
..add(ColorProperty(
|
||||
'footerButtonIconFocusColor', footerButtonIconFocusColor))
|
||||
..add(ColorProperty('backgroundColor', backgroundColor))
|
||||
..add(ColorProperty('backgroundHoverColor', backgroundHoverColor))
|
||||
..add(ColorProperty('backgroundFocusColor', backgroundFocusColor))
|
||||
..add(ColorProperty('borderColor', borderColor))
|
||||
..add(ColorProperty('borderHoverColor', borderHoverColor))
|
||||
..add(ColorProperty('borderFocusColor', borderFocusColor));
|
||||
}
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
final String title;
|
||||
final Widget? headerWidget;
|
||||
final Widget? widthBox;
|
||||
final TextStyle titleStyle;
|
||||
final Color foregroundColor;
|
||||
final void Function()? callback;
|
||||
final IconData? footerButtonIcon;
|
||||
final void Function()? footerCallback;
|
||||
final Color? backgroundColor;
|
||||
final Color? backgroundHoverColor;
|
||||
final Color? backgroundFocusColor;
|
||||
final Color? borderColor;
|
||||
final Color? borderHoverColor;
|
||||
final Color? borderFocusColor;
|
||||
final Color? footerButtonIconColor;
|
||||
final Color? footerButtonIconHoverColor;
|
||||
final Color? footerButtonIconFocusColor;
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
export 'drawer_menu/drawer_menu.dart';
|
||||
export 'home_account_invalid.dart';
|
||||
export 'home_account_locked.dart';
|
||||
export 'home_account_missing.dart';
|
||||
|
@ -2,7 +2,7 @@ import 'package:awesome_extensions/awesome_extensions.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_translate/flutter_translate.dart';
|
||||
import 'package:go_router/go_router.dart';
|
||||
import 'package:flutter_zoom_drawer/flutter_zoom_drawer.dart';
|
||||
|
||||
import '../../../account_manager/account_manager.dart';
|
||||
import '../../../chat/chat.dart';
|
||||
@ -36,7 +36,7 @@ class _HomeAccountReadyMainState extends State<HomeAccountReadyMain> {
|
||||
return Column(children: <Widget>[
|
||||
Row(children: [
|
||||
IconButton(
|
||||
icon: const Icon(Icons.settings),
|
||||
icon: const Icon(Icons.menu),
|
||||
color: scale.secondaryScale.borderText,
|
||||
constraints: const BoxConstraints.expand(height: 64, width: 64),
|
||||
style: ButtonStyle(
|
||||
@ -46,7 +46,9 @@ class _HomeAccountReadyMainState extends State<HomeAccountReadyMain> {
|
||||
borderRadius: BorderRadius.all(Radius.circular(16))))),
|
||||
tooltip: translate('app_bar.settings_tooltip'),
|
||||
onPressed: () async {
|
||||
await GoRouterHelper(context).push('/settings');
|
||||
final ctrl = context.read<ZoomDrawerController>();
|
||||
await ctrl.toggle?.call();
|
||||
//await GoRouterHelper(context).push('/settings');
|
||||
}).paddingLTRB(0, 0, 8, 0),
|
||||
asyncValueBuilder(account,
|
||||
(_, account) => ProfileWidget(profile: account.profile))
|
||||
|
@ -1,9 +1,14 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:awesome_extensions/awesome_extensions.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_zoom_drawer/flutter_zoom_drawer.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../../account_manager/account_manager.dart';
|
||||
import '../../theme/theme.dart';
|
||||
import 'drawer_menu/drawer_menu.dart';
|
||||
import 'home_account_invalid.dart';
|
||||
import 'home_account_locked.dart';
|
||||
import 'home_account_missing.dart';
|
||||
@ -31,7 +36,7 @@ class HomeShellState extends State<HomeShell> {
|
||||
|
||||
Widget buildWithLogin(BuildContext context) {
|
||||
final activeLocalAccount = context.watch<ActiveLocalAccountCubit>().state;
|
||||
|
||||
final accountRecordsCubit = context.watch<AccountRecordsBlocMapCubit>();
|
||||
if (activeLocalAccount == null) {
|
||||
// If no logged in user is active, show the loading panel
|
||||
return const HomeNoActive();
|
||||
@ -39,6 +44,11 @@ class HomeShellState extends State<HomeShell> {
|
||||
|
||||
final accountInfo =
|
||||
AccountRepository.instance.getAccountInfo(activeLocalAccount);
|
||||
final activeCubit =
|
||||
accountRecordsCubit.tryOperate(activeLocalAccount, closure: (c) => c);
|
||||
if (activeCubit == null) {
|
||||
return waitingPage();
|
||||
}
|
||||
|
||||
switch (accountInfo.status) {
|
||||
case AccountInfoStatus.noAccount:
|
||||
@ -48,14 +58,13 @@ class HomeShellState extends State<HomeShell> {
|
||||
case AccountInfoStatus.accountLocked:
|
||||
return const HomeAccountLocked();
|
||||
case AccountInfoStatus.accountReady:
|
||||
return Provider<ActiveAccountInfo>.value(
|
||||
return MultiProvider(providers: [
|
||||
Provider<ActiveAccountInfo>.value(
|
||||
value: accountInfo.activeAccountInfo!,
|
||||
child: BlocProvider(
|
||||
create: (context) => AccountRecordCubit(
|
||||
open: () async => AccountRepository.instance
|
||||
.openAccountRecord(
|
||||
accountInfo.activeAccountInfo!.userLogin)),
|
||||
child: widget.accountReadyBuilder));
|
||||
),
|
||||
Provider<AccountRecordCubit>.value(value: activeCubit),
|
||||
Provider<ZoomDrawerController>.value(value: _zoomDrawerController),
|
||||
], child: widget.accountReadyBuilder);
|
||||
}
|
||||
}
|
||||
|
||||
@ -64,11 +73,38 @@ class HomeShellState extends State<HomeShell> {
|
||||
final theme = Theme.of(context);
|
||||
final scale = theme.extension<ScaleScheme>()!;
|
||||
|
||||
// XXX: eventually write account switcher here
|
||||
final gradient = LinearGradient(
|
||||
begin: Alignment.topCenter,
|
||||
end: Alignment.bottomCenter,
|
||||
colors: [
|
||||
scale.tertiaryScale.subtleBackground,
|
||||
scale.tertiaryScale.appBackground,
|
||||
]);
|
||||
|
||||
return SafeArea(
|
||||
child: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: scale.primaryScale.activeElementBackground),
|
||||
child: buildWithLogin(context)));
|
||||
decoration: BoxDecoration(gradient: gradient),
|
||||
child: ZoomDrawer(
|
||||
controller: _zoomDrawerController,
|
||||
//menuBackgroundColor: Colors.transparent,
|
||||
menuScreen: const DrawerMenu(),
|
||||
mainScreen: DecoratedBox(
|
||||
decoration: BoxDecoration(
|
||||
color: scale.primaryScale.activeElementBackground),
|
||||
child: buildWithLogin(context)),
|
||||
borderRadius: 24,
|
||||
showShadow: true,
|
||||
angle: 0,
|
||||
drawerShadowsBackgroundColor: theme.shadowColor,
|
||||
mainScreenOverlayColor: theme.shadowColor.withAlpha(0x3F),
|
||||
openCurve: Curves.fastEaseInToSlowEaseOut,
|
||||
// duration: const Duration(milliseconds: 250),
|
||||
// reverseDuration: const Duration(milliseconds: 250),
|
||||
menuScreenTapClose: true,
|
||||
mainScreenScale: .25,
|
||||
slideWidth: min(360, MediaQuery.of(context).size.width * 0.9),
|
||||
)));
|
||||
}
|
||||
|
||||
final ZoomDrawerController _zoomDrawerController = ZoomDrawerController();
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_translate/flutter_translate.dart';
|
||||
import 'package:intl/date_symbol_data_local.dart';
|
||||
|
||||
import 'package:stack_trace/stack_trace.dart';
|
||||
|
||||
import 'app.dart';
|
||||
@ -45,6 +46,9 @@ void main() async {
|
||||
fallbackLocale: 'en_US', supportedLocales: ['en_US']);
|
||||
await initializeDateFormatting();
|
||||
|
||||
// Get package info
|
||||
await initPackageInfo();
|
||||
|
||||
// Run the app
|
||||
// Hot reloads will only restart this part, not Veilid
|
||||
runApp(LocalizedApp(localizationDelegate,
|
||||
|
14
lib/tools/package_info.dart
Normal file
14
lib/tools/package_info.dart
Normal file
@ -0,0 +1,14 @@
|
||||
import 'package:package_info_plus/package_info_plus.dart';
|
||||
|
||||
String packageInfoAppName = '';
|
||||
String packageInfoPackageName = '';
|
||||
String packageInfoVersion = '';
|
||||
String packageInfoBuildNumber = '';
|
||||
|
||||
Future<void> initPackageInfo() async {
|
||||
final packageInfo = await PackageInfo.fromPlatform();
|
||||
packageInfoAppName = packageInfo.appName;
|
||||
packageInfoPackageName = packageInfo.packageName;
|
||||
packageInfoVersion = packageInfo.version;
|
||||
packageInfoBuildNumber = packageInfo.buildNumber;
|
||||
}
|
@ -1,8 +1,10 @@
|
||||
|
||||
export 'animations.dart';
|
||||
export 'enter_password.dart';
|
||||
export 'enter_pin.dart';
|
||||
export 'loggy.dart';
|
||||
export 'misc.dart';
|
||||
export 'package_info.dart';
|
||||
export 'phono_byte.dart';
|
||||
export 'pop_control.dart';
|
||||
export 'responsive.dart';
|
||||
|
@ -6,6 +6,7 @@ import FlutterMacOS
|
||||
import Foundation
|
||||
|
||||
import mobile_scanner
|
||||
import package_info_plus
|
||||
import pasteboard
|
||||
import path_provider_foundation
|
||||
import screen_retriever
|
||||
@ -19,6 +20,7 @@ import window_manager
|
||||
|
||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
MobileScannerPlugin.register(with: registry.registrar(forPlugin: "MobileScannerPlugin"))
|
||||
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
|
||||
PasteboardPlugin.register(with: registry.registrar(forPlugin: "PasteboardPlugin"))
|
||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||
ScreenRetrieverPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverPlugin"))
|
||||
|
@ -2,6 +2,8 @@ PODS:
|
||||
- FlutterMacOS (1.0.0)
|
||||
- mobile_scanner (5.1.1):
|
||||
- FlutterMacOS
|
||||
- package_info_plus (0.0.1):
|
||||
- FlutterMacOS
|
||||
- pasteboard (0.0.1):
|
||||
- FlutterMacOS
|
||||
- path_provider_foundation (0.0.1):
|
||||
@ -29,6 +31,7 @@ PODS:
|
||||
DEPENDENCIES:
|
||||
- FlutterMacOS (from `Flutter/ephemeral`)
|
||||
- mobile_scanner (from `Flutter/ephemeral/.symlinks/plugins/mobile_scanner/macos`)
|
||||
- package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`)
|
||||
- pasteboard (from `Flutter/ephemeral/.symlinks/plugins/pasteboard/macos`)
|
||||
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
|
||||
- screen_retriever (from `Flutter/ephemeral/.symlinks/plugins/screen_retriever/macos`)
|
||||
@ -45,6 +48,8 @@ EXTERNAL SOURCES:
|
||||
:path: Flutter/ephemeral
|
||||
mobile_scanner:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/mobile_scanner/macos
|
||||
package_info_plus:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos
|
||||
pasteboard:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/pasteboard/macos
|
||||
path_provider_foundation:
|
||||
@ -69,6 +74,7 @@ EXTERNAL SOURCES:
|
||||
SPEC CHECKSUMS:
|
||||
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
|
||||
mobile_scanner: 1efac1e53c294b24e3bb55bcc7f4deee0233a86b
|
||||
package_info_plus: fa739dd842b393193c5ca93c26798dff6e3d0e0c
|
||||
pasteboard: 9b69dba6fedbb04866be632205d532fe2f6b1d99
|
||||
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
|
||||
screen_retriever: 59634572a57080243dd1bf715e55b6c54f241a38
|
||||
|
@ -36,10 +36,11 @@ packages:
|
||||
async_tools:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "../../../dart_async_tools"
|
||||
relative: true
|
||||
source: path
|
||||
version: "0.1.1"
|
||||
name: async_tools
|
||||
sha256: "72590010ed6c6f5cbd5d40e33834abc08a43da6a73ac3c3645517d53899b8684"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.2"
|
||||
bloc:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -51,10 +52,11 @@ packages:
|
||||
bloc_advanced_tools:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
path: "../../../bloc_advanced_tools"
|
||||
relative: true
|
||||
source: path
|
||||
version: "0.1.1"
|
||||
name: bloc_advanced_tools
|
||||
sha256: "0cf9b3a73a67addfe22ec3f97a1ac240f6ad53870d6b21a980260f390d7901cd"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.2"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
24
pubspec.lock
24
pubspec.lock
@ -585,6 +585,14 @@ packages:
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_zoom_drawer:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_zoom_drawer
|
||||
sha256: "5a3708548868463fb36e0e3171761ab7cb513df88d2f14053802812d2e855060"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.2.0"
|
||||
form_builder_validators:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@ -857,6 +865,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.0"
|
||||
package_info_plus:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: package_info_plus
|
||||
sha256: b93d8b4d624b4ea19b0a5a208b2d6eff06004bc3ce74c06040b120eeadd00ce0
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.0.0"
|
||||
package_info_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: package_info_plus_platform_interface
|
||||
sha256: f49918f3433a3146047372f9d4f1f847511f2acd5cd030e1f44fe5a50036b70e
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.0"
|
||||
pasteboard:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
@ -45,6 +45,7 @@ dependencies:
|
||||
flutter_spinkit: ^5.2.1
|
||||
flutter_svg: ^2.0.10+1
|
||||
flutter_translate: ^4.1.0
|
||||
flutter_zoom_drawer: ^3.2.0
|
||||
form_builder_validators: ^10.0.1
|
||||
freezed_annotation: ^2.4.1
|
||||
go_router: ^14.1.4
|
||||
@ -56,6 +57,7 @@ dependencies:
|
||||
meta: ^1.12.0
|
||||
mobile_scanner: ^5.1.1
|
||||
motion_toast: ^2.10.0
|
||||
package_info_plus: ^8.0.0
|
||||
pasteboard: ^0.2.0
|
||||
path: ^1.9.0
|
||||
path_provider: ^2.1.3
|
||||
|
Loading…
Reference in New Issue
Block a user