add multiple accounts menu

This commit is contained in:
Christien Rioux 2024-06-11 21:27:20 -04:00
parent b0d4e35c6f
commit 87bb1657c7
25 changed files with 583 additions and 70 deletions

View File

@ -2,8 +2,9 @@
"app": { "app": {
"title": "VeilidChat" "title": "VeilidChat"
}, },
"app_bar": { "menu": {
"settings_tooltip": "Settings" "settings_tooltip": "Settings",
"add_account_tooltip": "Add Account"
}, },
"pager": { "pager": {
"chats": "Chats", "chats": "Chats",

View File

@ -4,7 +4,9 @@ import 'package:veilid_support/veilid_support.dart';
import '../../proto/proto.dart' as proto; import '../../proto/proto.dart' as proto;
class AccountRecordCubit extends DefaultDHTRecordCubit<proto.Account> { typedef AccountRecordState = proto.Account;
class AccountRecordCubit extends DefaultDHTRecordCubit<AccountRecordState> {
AccountRecordCubit({ AccountRecordCubit({
required super.open, required super.open,
}) : super(decodeState: proto.Account.fromBuffer); }) : super(decodeState: proto.Account.fromBuffer);

View File

@ -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;
}

View File

@ -3,7 +3,7 @@ import 'dart:async';
import 'package:bloc/bloc.dart'; import 'package:bloc/bloc.dart';
import 'package:veilid_support/veilid_support.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?> { class ActiveLocalAccountCubit extends Cubit<TypedKey?> {
ActiveLocalAccountCubit(AccountRepository accountRepository) ActiveLocalAccountCubit(AccountRepository accountRepository)

View File

@ -1,4 +1,5 @@
export 'account_record_cubit.dart'; export 'account_record_cubit.dart';
export 'account_records_bloc_map_cubit.dart';
export 'active_local_account_cubit.dart'; export 'active_local_account_cubit.dart';
export 'local_accounts_cubit.dart'; export 'local_accounts_cubit.dart';
export 'user_logins_cubit.dart'; export 'user_logins_cubit.dart';

View File

@ -4,7 +4,7 @@ import 'package:bloc/bloc.dart';
import 'package:fast_immutable_collections/fast_immutable_collections.dart'; import 'package:fast_immutable_collections/fast_immutable_collections.dart';
import '../models/models.dart'; import '../models/models.dart';
import '../repository/account_repository/account_repository.dart'; import '../repository/account_repository.dart';
class LocalAccountsCubit extends Cubit<IList<LocalAccount>> { class LocalAccountsCubit extends Cubit<IList<LocalAccount>> {
LocalAccountsCubit(AccountRepository accountRepository) LocalAccountsCubit(AccountRepository accountRepository)

View File

@ -1,12 +1,17 @@
import 'dart:async'; import 'dart:async';
import 'package:bloc/bloc.dart'; 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:fast_immutable_collections/fast_immutable_collections.dart';
import 'package:veilid_support/veilid_support.dart';
import '../models/models.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) UserLoginsCubit(AccountRepository accountRepository)
: _accountRepository = accountRepository, : _accountRepository = accountRepository,
super(accountRepository.getUserLogins()) { super(accountRepository.getUserLogins()) {
@ -30,6 +35,16 @@ class UserLoginsCubit extends Cubit<IList<UserLogin>> {
await _accountRepositorySubscription.cancel(); 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; final AccountRepository _accountRepository;
late final StreamSubscription<AccountRepositoryChange> late final StreamSubscription<AccountRepositoryChange>
_accountRepositorySubscription; _accountRepositorySubscription;

View File

@ -3,9 +3,9 @@ import 'dart:async';
import 'package:fast_immutable_collections/fast_immutable_collections.dart'; import 'package:fast_immutable_collections/fast_immutable_collections.dart';
import 'package:veilid_support/veilid_support.dart'; import 'package:veilid_support/veilid_support.dart';
import '../../../../proto/proto.dart' as proto; import '../../../proto/proto.dart' as proto;
import '../../../tools/tools.dart'; import '../../tools/tools.dart';
import '../../models/models.dart'; import '../models/models.dart';
const String veilidChatAccountKey = 'com.veilid.veilidchat'; const String veilidChatAccountKey = 'com.veilid.veilidchat';

View File

@ -1 +1 @@
export 'account_repository/account_repository.dart'; export 'account_repository.dart';

View File

@ -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: []),
);
}
}

View File

@ -129,7 +129,11 @@ class VeilidChatApp extends StatelessWidget {
BlocProvider<PreferencesCubit>( BlocProvider<PreferencesCubit>(
create: (context) => create: (context) =>
PreferencesCubit(PreferencesRepository.instance), PreferencesCubit(PreferencesRepository.instance),
) ),
BlocProvider<AccountRecordsBlocMapCubit>(
create: (context) =>
AccountRecordsBlocMapCubit(AccountRepository.instance)
..follow(context.read<UserLoginsCubit>())),
], ],
child: BackgroundTicker( child: BackgroundTicker(
child: _buildShortcuts( child: _buildShortcuts(

View File

@ -140,5 +140,7 @@ class ChatListCubit extends DHTShortArrayCubit<proto.Chat>
valueMapper: (e) => e.value); valueMapper: (e) => e.value);
} }
////////////////////////////////////////////////////////////////////////////
final ActiveChatCubit activeChatCubit; final ActiveChatCubit activeChatCubit;
} }

View 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),
);
}
}

View 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;
}

View File

@ -1,3 +1,4 @@
export 'drawer_menu/drawer_menu.dart';
export 'home_account_invalid.dart'; export 'home_account_invalid.dart';
export 'home_account_locked.dart'; export 'home_account_locked.dart';
export 'home_account_missing.dart'; export 'home_account_missing.dart';

View File

@ -2,7 +2,7 @@ import 'package:awesome_extensions/awesome_extensions.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_translate/flutter_translate.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 '../../../account_manager/account_manager.dart';
import '../../../chat/chat.dart'; import '../../../chat/chat.dart';
@ -36,7 +36,7 @@ class _HomeAccountReadyMainState extends State<HomeAccountReadyMain> {
return Column(children: <Widget>[ return Column(children: <Widget>[
Row(children: [ Row(children: [
IconButton( IconButton(
icon: const Icon(Icons.settings), icon: const Icon(Icons.menu),
color: scale.secondaryScale.borderText, color: scale.secondaryScale.borderText,
constraints: const BoxConstraints.expand(height: 64, width: 64), constraints: const BoxConstraints.expand(height: 64, width: 64),
style: ButtonStyle( style: ButtonStyle(
@ -46,7 +46,9 @@ class _HomeAccountReadyMainState extends State<HomeAccountReadyMain> {
borderRadius: BorderRadius.all(Radius.circular(16))))), borderRadius: BorderRadius.all(Radius.circular(16))))),
tooltip: translate('app_bar.settings_tooltip'), tooltip: translate('app_bar.settings_tooltip'),
onPressed: () async { 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), }).paddingLTRB(0, 0, 8, 0),
asyncValueBuilder(account, asyncValueBuilder(account,
(_, account) => ProfileWidget(profile: account.profile)) (_, account) => ProfileWidget(profile: account.profile))

View File

@ -1,9 +1,14 @@
import 'dart:math';
import 'package:awesome_extensions/awesome_extensions.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_zoom_drawer/flutter_zoom_drawer.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import '../../account_manager/account_manager.dart'; import '../../account_manager/account_manager.dart';
import '../../theme/theme.dart'; import '../../theme/theme.dart';
import 'drawer_menu/drawer_menu.dart';
import 'home_account_invalid.dart'; import 'home_account_invalid.dart';
import 'home_account_locked.dart'; import 'home_account_locked.dart';
import 'home_account_missing.dart'; import 'home_account_missing.dart';
@ -31,7 +36,7 @@ class HomeShellState extends State<HomeShell> {
Widget buildWithLogin(BuildContext context) { Widget buildWithLogin(BuildContext context) {
final activeLocalAccount = context.watch<ActiveLocalAccountCubit>().state; final activeLocalAccount = context.watch<ActiveLocalAccountCubit>().state;
final accountRecordsCubit = context.watch<AccountRecordsBlocMapCubit>();
if (activeLocalAccount == null) { if (activeLocalAccount == null) {
// If no logged in user is active, show the loading panel // If no logged in user is active, show the loading panel
return const HomeNoActive(); return const HomeNoActive();
@ -39,6 +44,11 @@ class HomeShellState extends State<HomeShell> {
final accountInfo = final accountInfo =
AccountRepository.instance.getAccountInfo(activeLocalAccount); AccountRepository.instance.getAccountInfo(activeLocalAccount);
final activeCubit =
accountRecordsCubit.tryOperate(activeLocalAccount, closure: (c) => c);
if (activeCubit == null) {
return waitingPage();
}
switch (accountInfo.status) { switch (accountInfo.status) {
case AccountInfoStatus.noAccount: case AccountInfoStatus.noAccount:
@ -48,14 +58,13 @@ class HomeShellState extends State<HomeShell> {
case AccountInfoStatus.accountLocked: case AccountInfoStatus.accountLocked:
return const HomeAccountLocked(); return const HomeAccountLocked();
case AccountInfoStatus.accountReady: case AccountInfoStatus.accountReady:
return Provider<ActiveAccountInfo>.value( return MultiProvider(providers: [
Provider<ActiveAccountInfo>.value(
value: accountInfo.activeAccountInfo!, value: accountInfo.activeAccountInfo!,
child: BlocProvider( ),
create: (context) => AccountRecordCubit( Provider<AccountRecordCubit>.value(value: activeCubit),
open: () async => AccountRepository.instance Provider<ZoomDrawerController>.value(value: _zoomDrawerController),
.openAccountRecord( ], child: widget.accountReadyBuilder);
accountInfo.activeAccountInfo!.userLogin)),
child: widget.accountReadyBuilder));
} }
} }
@ -64,11 +73,38 @@ class HomeShellState extends State<HomeShell> {
final theme = Theme.of(context); final theme = Theme.of(context);
final scale = theme.extension<ScaleScheme>()!; 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( return SafeArea(
child: DecoratedBox( child: DecoratedBox(
decoration: BoxDecoration( decoration: BoxDecoration(gradient: gradient),
color: scale.primaryScale.activeElementBackground), child: ZoomDrawer(
child: buildWithLogin(context))); 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();
} }

View File

@ -6,6 +6,7 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_translate/flutter_translate.dart'; import 'package:flutter_translate/flutter_translate.dart';
import 'package:intl/date_symbol_data_local.dart'; import 'package:intl/date_symbol_data_local.dart';
import 'package:stack_trace/stack_trace.dart'; import 'package:stack_trace/stack_trace.dart';
import 'app.dart'; import 'app.dart';
@ -45,6 +46,9 @@ void main() async {
fallbackLocale: 'en_US', supportedLocales: ['en_US']); fallbackLocale: 'en_US', supportedLocales: ['en_US']);
await initializeDateFormatting(); await initializeDateFormatting();
// Get package info
await initPackageInfo();
// Run the app // Run the app
// Hot reloads will only restart this part, not Veilid // Hot reloads will only restart this part, not Veilid
runApp(LocalizedApp(localizationDelegate, runApp(LocalizedApp(localizationDelegate,

View 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;
}

View File

@ -1,8 +1,10 @@
export 'animations.dart'; export 'animations.dart';
export 'enter_password.dart'; export 'enter_password.dart';
export 'enter_pin.dart'; export 'enter_pin.dart';
export 'loggy.dart'; export 'loggy.dart';
export 'misc.dart'; export 'misc.dart';
export 'package_info.dart';
export 'phono_byte.dart'; export 'phono_byte.dart';
export 'pop_control.dart'; export 'pop_control.dart';
export 'responsive.dart'; export 'responsive.dart';

View File

@ -6,6 +6,7 @@ import FlutterMacOS
import Foundation import Foundation
import mobile_scanner import mobile_scanner
import package_info_plus
import pasteboard import pasteboard
import path_provider_foundation import path_provider_foundation
import screen_retriever import screen_retriever
@ -19,6 +20,7 @@ import window_manager
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
MobileScannerPlugin.register(with: registry.registrar(forPlugin: "MobileScannerPlugin")) MobileScannerPlugin.register(with: registry.registrar(forPlugin: "MobileScannerPlugin"))
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
PasteboardPlugin.register(with: registry.registrar(forPlugin: "PasteboardPlugin")) PasteboardPlugin.register(with: registry.registrar(forPlugin: "PasteboardPlugin"))
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
ScreenRetrieverPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverPlugin")) ScreenRetrieverPlugin.register(with: registry.registrar(forPlugin: "ScreenRetrieverPlugin"))

View File

@ -2,6 +2,8 @@ PODS:
- FlutterMacOS (1.0.0) - FlutterMacOS (1.0.0)
- mobile_scanner (5.1.1): - mobile_scanner (5.1.1):
- FlutterMacOS - FlutterMacOS
- package_info_plus (0.0.1):
- FlutterMacOS
- pasteboard (0.0.1): - pasteboard (0.0.1):
- FlutterMacOS - FlutterMacOS
- path_provider_foundation (0.0.1): - path_provider_foundation (0.0.1):
@ -29,6 +31,7 @@ PODS:
DEPENDENCIES: DEPENDENCIES:
- FlutterMacOS (from `Flutter/ephemeral`) - FlutterMacOS (from `Flutter/ephemeral`)
- mobile_scanner (from `Flutter/ephemeral/.symlinks/plugins/mobile_scanner/macos`) - 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`) - pasteboard (from `Flutter/ephemeral/.symlinks/plugins/pasteboard/macos`)
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`) - path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
- screen_retriever (from `Flutter/ephemeral/.symlinks/plugins/screen_retriever/macos`) - screen_retriever (from `Flutter/ephemeral/.symlinks/plugins/screen_retriever/macos`)
@ -45,6 +48,8 @@ EXTERNAL SOURCES:
:path: Flutter/ephemeral :path: Flutter/ephemeral
mobile_scanner: mobile_scanner:
:path: Flutter/ephemeral/.symlinks/plugins/mobile_scanner/macos :path: Flutter/ephemeral/.symlinks/plugins/mobile_scanner/macos
package_info_plus:
:path: Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos
pasteboard: pasteboard:
:path: Flutter/ephemeral/.symlinks/plugins/pasteboard/macos :path: Flutter/ephemeral/.symlinks/plugins/pasteboard/macos
path_provider_foundation: path_provider_foundation:
@ -69,6 +74,7 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS: SPEC CHECKSUMS:
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
mobile_scanner: 1efac1e53c294b24e3bb55bcc7f4deee0233a86b mobile_scanner: 1efac1e53c294b24e3bb55bcc7f4deee0233a86b
package_info_plus: fa739dd842b393193c5ca93c26798dff6e3d0e0c
pasteboard: 9b69dba6fedbb04866be632205d532fe2f6b1d99 pasteboard: 9b69dba6fedbb04866be632205d532fe2f6b1d99
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
screen_retriever: 59634572a57080243dd1bf715e55b6c54f241a38 screen_retriever: 59634572a57080243dd1bf715e55b6c54f241a38

View File

@ -36,10 +36,11 @@ packages:
async_tools: async_tools:
dependency: "direct main" dependency: "direct main"
description: description:
path: "../../../dart_async_tools" name: async_tools
relative: true sha256: "72590010ed6c6f5cbd5d40e33834abc08a43da6a73ac3c3645517d53899b8684"
source: path url: "https://pub.dev"
version: "0.1.1" source: hosted
version: "0.1.2"
bloc: bloc:
dependency: "direct main" dependency: "direct main"
description: description:
@ -51,10 +52,11 @@ packages:
bloc_advanced_tools: bloc_advanced_tools:
dependency: "direct main" dependency: "direct main"
description: description:
path: "../../../bloc_advanced_tools" name: bloc_advanced_tools
relative: true sha256: "0cf9b3a73a67addfe22ec3f97a1ac240f6ad53870d6b21a980260f390d7901cd"
source: path url: "https://pub.dev"
version: "0.1.1" source: hosted
version: "0.1.2"
boolean_selector: boolean_selector:
dependency: transitive dependency: transitive
description: description:

View File

@ -585,6 +585,14 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" 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: form_builder_validators:
dependency: "direct main" dependency: "direct main"
description: description:
@ -857,6 +865,22 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.1.0" 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: pasteboard:
dependency: "direct main" dependency: "direct main"
description: description:

View File

@ -45,6 +45,7 @@ dependencies:
flutter_spinkit: ^5.2.1 flutter_spinkit: ^5.2.1
flutter_svg: ^2.0.10+1 flutter_svg: ^2.0.10+1
flutter_translate: ^4.1.0 flutter_translate: ^4.1.0
flutter_zoom_drawer: ^3.2.0
form_builder_validators: ^10.0.1 form_builder_validators: ^10.0.1
freezed_annotation: ^2.4.1 freezed_annotation: ^2.4.1
go_router: ^14.1.4 go_router: ^14.1.4
@ -56,6 +57,7 @@ dependencies:
meta: ^1.12.0 meta: ^1.12.0
mobile_scanner: ^5.1.1 mobile_scanner: ^5.1.1
motion_toast: ^2.10.0 motion_toast: ^2.10.0
package_info_plus: ^8.0.0
pasteboard: ^0.2.0 pasteboard: ^0.2.0
path: ^1.9.0 path: ^1.9.0
path_provider: ^2.1.3 path_provider: ^2.1.3