mirror of
https://gitlab.com/veilid/veilidchat.git
synced 2025-06-07 14:12:41 -04:00
theming work, revamp contact invitation
This commit is contained in:
parent
3c95c9d1a3
commit
ae841ec42a
26 changed files with 504 additions and 507 deletions
|
@ -158,9 +158,8 @@
|
||||||
"away": "Away"
|
"away": "Away"
|
||||||
},
|
},
|
||||||
"add_contact_sheet": {
|
"add_contact_sheet": {
|
||||||
"new_contact": "New Contact",
|
"add_contact": "Add Contact",
|
||||||
"create_invite": "Create\nInvitation",
|
"create_invite": "Create\nInvitation",
|
||||||
"receive_invite": "Receive\nInvitation",
|
|
||||||
"scan_invite": "Scan\nInvitation",
|
"scan_invite": "Scan\nInvitation",
|
||||||
"paste_invite": "Paste\nInvitation"
|
"paste_invite": "Paste\nInvitation"
|
||||||
},
|
},
|
||||||
|
|
|
@ -14,7 +14,6 @@ class ActiveLocalAccountCubit extends Cubit<TypedKey?> {
|
||||||
switch (change) {
|
switch (change) {
|
||||||
case AccountRepositoryChange.activeLocalAccount:
|
case AccountRepositoryChange.activeLocalAccount:
|
||||||
emit(_accountRepository.getActiveLocalAccount());
|
emit(_accountRepository.getActiveLocalAccount());
|
||||||
break;
|
|
||||||
// Ignore these
|
// Ignore these
|
||||||
case AccountRepositoryChange.localAccounts:
|
case AccountRepositoryChange.localAccounts:
|
||||||
case AccountRepositoryChange.userLogins:
|
case AccountRepositoryChange.userLogins:
|
||||||
|
|
|
@ -32,6 +32,7 @@ class PerAccountCollectionState with _$PerAccountCollectionState {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension PerAccountCollectionStateExt on PerAccountCollectionState {
|
extension PerAccountCollectionStateExt on PerAccountCollectionState {
|
||||||
|
// Returns if the account is ready and logged in
|
||||||
bool get isReady =>
|
bool get isReady =>
|
||||||
avAccountRecordState != null &&
|
avAccountRecordState != null &&
|
||||||
avAccountRecordState!.isData &&
|
avAccountRecordState!.isData &&
|
||||||
|
@ -45,7 +46,11 @@ extension PerAccountCollectionStateExt on PerAccountCollectionState {
|
||||||
activeConversationsBlocMapCubit != null &&
|
activeConversationsBlocMapCubit != null &&
|
||||||
activeSingleContactChatBlocMapCubit != null;
|
activeSingleContactChatBlocMapCubit != null;
|
||||||
|
|
||||||
Widget provide({required Widget child}) => MultiBlocProvider(providers: [
|
/// If we have a selected account and it is ready and not locked,
|
||||||
|
/// this will provide the unlocked account's cubits to the context
|
||||||
|
Widget provideReady({required Widget child}) {
|
||||||
|
if (isReady) {
|
||||||
|
return MultiBlocProvider(providers: [
|
||||||
BlocProvider.value(value: accountInfoCubit!),
|
BlocProvider.value(value: accountInfoCubit!),
|
||||||
BlocProvider.value(value: accountRecordCubit!),
|
BlocProvider.value(value: accountRecordCubit!),
|
||||||
BlocProvider.value(value: contactInvitationListCubit!),
|
BlocProvider.value(value: contactInvitationListCubit!),
|
||||||
|
@ -56,4 +61,9 @@ extension PerAccountCollectionStateExt on PerAccountCollectionState {
|
||||||
BlocProvider.value(value: activeConversationsBlocMapCubit!),
|
BlocProvider.value(value: activeConversationsBlocMapCubit!),
|
||||||
BlocProvider.value(value: activeSingleContactChatBlocMapCubit!),
|
BlocProvider.value(value: activeSingleContactChatBlocMapCubit!),
|
||||||
], child: child);
|
], child: child);
|
||||||
|
} else {
|
||||||
|
// Otherwise we just provide the child
|
||||||
|
return child;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,11 +33,6 @@ class ChatComponentWidget extends StatelessWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
// final theme = Theme.of(context);
|
|
||||||
// final scale = theme.extension<ScaleScheme>()!;
|
|
||||||
// final scaleConfig = theme.extension<ScaleConfig>()!;
|
|
||||||
// final textTheme = theme.textTheme;
|
|
||||||
|
|
||||||
// Get the account info
|
// Get the account info
|
||||||
final accountInfo = context.watch<AccountInfoCubit>().state;
|
final accountInfo = context.watch<AccountInfoCubit>().state;
|
||||||
|
|
||||||
|
@ -125,7 +120,7 @@ class ChatComponentWidget extends StatelessWidget {
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
Container(
|
Container(
|
||||||
height: 48,
|
height: 40,
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: scale.border,
|
color: scale.border,
|
||||||
),
|
),
|
||||||
|
@ -141,9 +136,10 @@ class ChatComponentWidget extends StatelessWidget {
|
||||||
)),
|
)),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
IconButton(
|
IconButton(
|
||||||
|
iconSize: 24,
|
||||||
icon: Icon(Icons.close, color: scale.borderText),
|
icon: Icon(Icons.close, color: scale.borderText),
|
||||||
onPressed: _onClose)
|
onPressed: _onClose)
|
||||||
.paddingLTRB(16, 0, 16, 0)
|
.paddingLTRB(0, 0, 8, 0)
|
||||||
]),
|
]),
|
||||||
),
|
),
|
||||||
DecoratedBox(
|
DecoratedBox(
|
||||||
|
|
|
@ -1,31 +0,0 @@
|
||||||
import 'package:awesome_extensions/awesome_extensions.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:flutter_translate/flutter_translate.dart';
|
|
||||||
|
|
||||||
import '../../theme/theme.dart';
|
|
||||||
|
|
||||||
Widget newChatBottomSheetBuilder(
|
|
||||||
BuildContext sheetContext, BuildContext context) {
|
|
||||||
//final theme = Theme.of(sheetContext);
|
|
||||||
//final scale = theme.extension<ScaleScheme>()!;
|
|
||||||
|
|
||||||
return KeyboardListener(
|
|
||||||
focusNode: FocusNode(),
|
|
||||||
onKeyEvent: (ke) {
|
|
||||||
if (ke.logicalKey == LogicalKeyboardKey.escape) {
|
|
||||||
Navigator.pop(sheetContext);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: styledBottomSheet(
|
|
||||||
context: context,
|
|
||||||
title: translate('add_chat_sheet.new_chat'),
|
|
||||||
child: SizedBox(
|
|
||||||
height: 160,
|
|
||||||
child: const Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
|
||||||
children: [
|
|
||||||
Text(
|
|
||||||
'Group and custom chat functionality is not available yet')
|
|
||||||
]).paddingAll(16))));
|
|
||||||
}
|
|
|
@ -1,8 +1,7 @@
|
||||||
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 '../../theme/models/scale_theme/scale_scheme.dart';
|
import '../../theme/theme.dart';
|
||||||
import '../../theme/views/views.dart';
|
|
||||||
|
|
||||||
class NoConversationWidget extends StatelessWidget {
|
class NoConversationWidget extends StatelessWidget {
|
||||||
const NoConversationWidget({super.key});
|
const NoConversationWidget({super.key});
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
export 'chat_component_widget.dart';
|
export 'chat_component_widget.dart';
|
||||||
export 'empty_chat_widget.dart';
|
export 'empty_chat_widget.dart';
|
||||||
export 'new_chat_bottom_sheet.dart';
|
|
||||||
export 'no_conversation_widget.dart';
|
export 'no_conversation_widget.dart';
|
||||||
|
|
|
@ -48,7 +48,7 @@ class ContactInvitationItemWidget extends StatelessWidget {
|
||||||
key: ObjectKey(contactInvitationRecord),
|
key: ObjectKey(contactInvitationRecord),
|
||||||
disabled: tileDisabled,
|
disabled: tileDisabled,
|
||||||
selected: selected,
|
selected: selected,
|
||||||
tileScale: ScaleKind.primary,
|
tileScale: ScaleKind.secondary,
|
||||||
title: title,
|
title: title,
|
||||||
leading: const Icon(Icons.person_add),
|
leading: const Icon(Icons.person_add),
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
|
|
|
@ -17,19 +17,25 @@ import 'contact_item_widget.dart';
|
||||||
import 'empty_contact_list_widget.dart';
|
import 'empty_contact_list_widget.dart';
|
||||||
|
|
||||||
enum ContactsBrowserElementKind {
|
enum ContactsBrowserElementKind {
|
||||||
invitation,
|
|
||||||
contact,
|
contact,
|
||||||
|
invitation,
|
||||||
}
|
}
|
||||||
|
|
||||||
class ContactsBrowserElement {
|
class ContactsBrowserElement {
|
||||||
ContactsBrowserElement.invitation(proto.ContactInvitationRecord i)
|
|
||||||
: kind = ContactsBrowserElementKind.invitation,
|
|
||||||
contact = null,
|
|
||||||
invitation = i;
|
|
||||||
ContactsBrowserElement.contact(proto.Contact c)
|
ContactsBrowserElement.contact(proto.Contact c)
|
||||||
: kind = ContactsBrowserElementKind.contact,
|
: kind = ContactsBrowserElementKind.contact,
|
||||||
invitation = null,
|
invitation = null,
|
||||||
contact = c;
|
contact = c;
|
||||||
|
ContactsBrowserElement.invitation(proto.ContactInvitationRecord i)
|
||||||
|
: kind = ContactsBrowserElementKind.invitation,
|
||||||
|
contact = null,
|
||||||
|
invitation = i;
|
||||||
|
|
||||||
|
String get sortKey => switch (kind) {
|
||||||
|
ContactsBrowserElementKind.contact => contact!.displayName,
|
||||||
|
ContactsBrowserElementKind.invitation =>
|
||||||
|
invitation!.recipient + invitation!.message
|
||||||
|
};
|
||||||
|
|
||||||
final ContactsBrowserElementKind kind;
|
final ContactsBrowserElementKind kind;
|
||||||
final proto.ContactInvitationRecord? invitation;
|
final proto.ContactInvitationRecord? invitation;
|
||||||
|
@ -66,27 +72,25 @@ class ContactsBrowser extends StatefulWidget {
|
||||||
|
|
||||||
class _ContactsBrowserState extends State<ContactsBrowser>
|
class _ContactsBrowserState extends State<ContactsBrowser>
|
||||||
with SingleTickerProviderStateMixin {
|
with SingleTickerProviderStateMixin {
|
||||||
Widget buildInvitationBar(BuildContext context) {
|
Widget buildInvitationButton(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final textTheme = theme.textTheme;
|
final scaleScheme = theme.extension<ScaleScheme>()!;
|
||||||
final scale = theme.extension<ScaleScheme>()!;
|
|
||||||
final scaleConfig = theme.extension<ScaleConfig>()!;
|
final scaleConfig = theme.extension<ScaleConfig>()!;
|
||||||
|
|
||||||
final menuIconColor = scaleConfig.preferBorders
|
final menuIconColor = scaleConfig.preferBorders
|
||||||
? scale.primaryScale.hoverBorder
|
? scaleScheme.primaryScale.hoverBorder
|
||||||
: scale.primaryScale.hoverBorder;
|
: scaleScheme.primaryScale.hoverBorder;
|
||||||
final menuBackgroundColor = scaleConfig.preferBorders
|
final menuBackgroundColor = scaleConfig.preferBorders
|
||||||
? scale.primaryScale.elementBackground
|
? scaleScheme.primaryScale.activeElementBackground
|
||||||
: scale.primaryScale.elementBackground;
|
: scaleScheme.primaryScale.activeElementBackground;
|
||||||
|
|
||||||
final menuBorderColor = scale.primaryScale.hoverBorder;
|
final menuBorderColor = scaleScheme.primaryScale.hoverBorder;
|
||||||
|
|
||||||
final menuParams = StarMenuParameters(
|
final menuParams = StarMenuParameters(
|
||||||
shape: MenuShape.grid,
|
shape: MenuShape.linear,
|
||||||
checkItemsScreenBoundaries: true,
|
|
||||||
centerOffset: const Offset(0, 64),
|
centerOffset: const Offset(0, 64),
|
||||||
backgroundParams:
|
// backgroundParams:
|
||||||
BackgroundParams(backgroundColor: theme.shadowColor.withAlpha(128)),
|
// BackgroundParams(backgroundColor: theme.shadowColor.withAlpha(128)),
|
||||||
boundaryBackground: BoundaryBackground(
|
boundaryBackground: BoundaryBackground(
|
||||||
color: menuBackgroundColor,
|
color: menuBackgroundColor,
|
||||||
decoration: ShapeDecoration(
|
decoration: ShapeDecoration(
|
||||||
|
@ -99,89 +103,64 @@ class _ContactsBrowserState extends State<ContactsBrowser>
|
||||||
borderRadius: BorderRadius.circular(
|
borderRadius: BorderRadius.circular(
|
||||||
8 * scaleConfig.borderRadiusScale)))));
|
8 * scaleConfig.borderRadiusScale)))));
|
||||||
|
|
||||||
final receiveInviteMenuItems = [
|
ElevatedButton makeMenuButton(
|
||||||
Column(mainAxisSize: MainAxisSize.min, children: [
|
{required IconData iconData,
|
||||||
IconButton(
|
required String text,
|
||||||
onPressed: () async {
|
required void Function()? onPressed}) =>
|
||||||
_receiveInviteMenuController.closeMenu!();
|
ElevatedButton.icon(
|
||||||
await ScanInvitationDialog.show(context);
|
onPressed: onPressed,
|
||||||
},
|
|
||||||
iconSize: 32,
|
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
Icons.qr_code_scanner,
|
iconData,
|
||||||
size: 32,
|
size: 32,
|
||||||
color: menuIconColor,
|
).paddingSTEB(0, 8, 0, 8),
|
||||||
),
|
label: Text(
|
||||||
),
|
text,
|
||||||
Text(translate('add_contact_sheet.scan_invite'),
|
|
||||||
maxLines: 2,
|
maxLines: 2,
|
||||||
textAlign: TextAlign.center,
|
textAlign: TextAlign.center,
|
||||||
style: textTheme.labelSmall!.copyWith(color: menuIconColor))
|
).paddingSTEB(0, 8, 0, 8));
|
||||||
]).paddingAll(4),
|
|
||||||
Column(mainAxisSize: MainAxisSize.min, children: [
|
final inviteMenuItems = [
|
||||||
IconButton(
|
makeMenuButton(
|
||||||
|
iconData: Icons.paste,
|
||||||
|
text: translate('add_contact_sheet.paste_invite'),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
_receiveInviteMenuController.closeMenu!();
|
_invitationMenuController.closeMenu!();
|
||||||
await PasteInvitationDialog.show(context);
|
await PasteInvitationDialog.show(context);
|
||||||
},
|
}),
|
||||||
iconSize: 32,
|
makeMenuButton(
|
||||||
icon: Icon(
|
iconData: Icons.qr_code_scanner,
|
||||||
Icons.paste,
|
text: translate('add_contact_sheet.scan_invite'),
|
||||||
size: 32,
|
onPressed: () async {
|
||||||
color: menuIconColor,
|
_invitationMenuController.closeMenu!();
|
||||||
),
|
await ScanInvitationDialog.show(context);
|
||||||
),
|
}).paddingLTRB(0, 0, 0, 8),
|
||||||
Text(translate('add_contact_sheet.paste_invite'),
|
makeMenuButton(
|
||||||
maxLines: 2,
|
iconData: Icons.contact_page,
|
||||||
textAlign: TextAlign.center,
|
text: translate('add_contact_sheet.create_invite'),
|
||||||
style: textTheme.labelSmall!.copyWith(color: menuIconColor))
|
onPressed: () async {
|
||||||
]).paddingAll(4)
|
_invitationMenuController.closeMenu!();
|
||||||
|
await CreateInvitationDialog.show(context);
|
||||||
|
}).paddingLTRB(0, 0, 0, 8),
|
||||||
];
|
];
|
||||||
|
|
||||||
return Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [
|
return StarMenu(
|
||||||
Column(mainAxisSize: MainAxisSize.min, children: [
|
items: inviteMenuItems,
|
||||||
IconButton(
|
|
||||||
onPressed: () async {
|
|
||||||
await CreateInvitationDialog.show(context);
|
|
||||||
},
|
|
||||||
iconSize: 32,
|
|
||||||
icon: const Icon(Icons.contact_page),
|
|
||||||
color: menuIconColor,
|
|
||||||
),
|
|
||||||
Text(translate('add_contact_sheet.create_invite'),
|
|
||||||
maxLines: 2,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: textTheme.labelSmall!.copyWith(color: menuIconColor))
|
|
||||||
]),
|
|
||||||
StarMenu(
|
|
||||||
items: receiveInviteMenuItems,
|
|
||||||
onItemTapped: (_index, controller) {
|
onItemTapped: (_index, controller) {
|
||||||
controller.closeMenu!();
|
controller.closeMenu!();
|
||||||
},
|
},
|
||||||
controller: _receiveInviteMenuController,
|
controller: _invitationMenuController,
|
||||||
params: menuParams,
|
params: menuParams,
|
||||||
child: Column(mainAxisSize: MainAxisSize.min, children: [
|
child: IconButton(
|
||||||
IconButton(
|
|
||||||
onPressed: () {},
|
onPressed: () {},
|
||||||
iconSize: 32,
|
iconSize: 24,
|
||||||
icon: ImageIcon(
|
icon: Icon(Icons.person_add, color: menuIconColor),
|
||||||
const AssetImage('assets/images/handshake.png'),
|
tooltip: translate('add_contact_sheet.add_contact')),
|
||||||
size: 32,
|
);
|
||||||
color: menuIconColor,
|
|
||||||
)),
|
|
||||||
Text(translate('add_contact_sheet.receive_invite'),
|
|
||||||
maxLines: 2,
|
|
||||||
textAlign: TextAlign.center,
|
|
||||||
style: textTheme.labelSmall!.copyWith(color: menuIconColor))
|
|
||||||
]),
|
|
||||||
),
|
|
||||||
]).paddingAll(16);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final textTheme = theme.textTheme;
|
|
||||||
final scale = theme.extension<ScaleScheme>()!;
|
final scale = theme.extension<ScaleScheme>()!;
|
||||||
//final scaleConfig = theme.extension<ScaleConfig>()!;
|
//final scaleConfig = theme.extension<ScaleConfig>()!;
|
||||||
|
|
||||||
|
@ -196,40 +175,23 @@ class _ContactsBrowserState extends State<ContactsBrowser>
|
||||||
final contactList =
|
final contactList =
|
||||||
ciState.state.asData?.value.map((x) => x.value).toIList();
|
ciState.state.asData?.value.map((x) => x.value).toIList();
|
||||||
|
|
||||||
final expansionListData =
|
final initialList = <ContactsBrowserElement>[];
|
||||||
<ContactsBrowserElementKind, List<ContactsBrowserElement>>{};
|
|
||||||
if (contactInvitationRecordList.isNotEmpty) {
|
|
||||||
expansionListData[ContactsBrowserElementKind.invitation] =
|
|
||||||
contactInvitationRecordList
|
|
||||||
.toList()
|
|
||||||
.map(ContactsBrowserElement.invitation)
|
|
||||||
.toList();
|
|
||||||
}
|
|
||||||
if (contactList != null) {
|
if (contactList != null) {
|
||||||
expansionListData[ContactsBrowserElementKind.contact] =
|
initialList
|
||||||
contactList.toList().map(ContactsBrowserElement.contact).toList();
|
.addAll(contactList.toList().map(ContactsBrowserElement.contact));
|
||||||
}
|
}
|
||||||
|
if (contactInvitationRecordList.isNotEmpty) {
|
||||||
|
initialList.addAll(contactInvitationRecordList
|
||||||
|
.toList()
|
||||||
|
.map(ContactsBrowserElement.invitation));
|
||||||
|
}
|
||||||
|
|
||||||
|
initialList.sort((a, b) => a.sortKey.compareTo(b.sortKey));
|
||||||
|
|
||||||
return Column(children: [
|
return Column(children: [
|
||||||
buildInvitationBar(context),
|
SearchableList<ContactsBrowserElement>(
|
||||||
SearchableList<ContactsBrowserElement>.expansion(
|
initialList: initialList,
|
||||||
expansionListData: expansionListData,
|
itemBuilder: (element) {
|
||||||
expansionTitleBuilder: (k) {
|
|
||||||
final kind = k as ContactsBrowserElementKind;
|
|
||||||
late final String title;
|
|
||||||
switch (kind) {
|
|
||||||
case ContactsBrowserElementKind.contact:
|
|
||||||
title = translate('contacts_dialog.contacts');
|
|
||||||
case ContactsBrowserElementKind.invitation:
|
|
||||||
title = translate('contacts_dialog.invitations');
|
|
||||||
}
|
|
||||||
|
|
||||||
return Center(
|
|
||||||
child: Text(title, style: textTheme.titleSmall),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
expansionInitiallyExpanded: (k) => true,
|
|
||||||
expansionListBuilder: (_index, element) {
|
|
||||||
switch (element.kind) {
|
switch (element.kind) {
|
||||||
case ContactsBrowserElementKind.contact:
|
case ContactsBrowserElementKind.contact:
|
||||||
final contact = element.contact!;
|
final contact = element.contact!;
|
||||||
|
@ -245,51 +207,57 @@ class _ContactsBrowserState extends State<ContactsBrowser>
|
||||||
case ContactsBrowserElementKind.invitation:
|
case ContactsBrowserElementKind.invitation:
|
||||||
final invitation = element.invitation!;
|
final invitation = element.invitation!;
|
||||||
return ContactInvitationItemWidget(
|
return ContactInvitationItemWidget(
|
||||||
contactInvitationRecord: invitation, disabled: false)
|
contactInvitationRecord: invitation,
|
||||||
|
disabled: false)
|
||||||
.paddingLTRB(0, 4, 0, 0);
|
.paddingLTRB(0, 4, 0, 0);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
filterExpansionData: (value) {
|
filter: (value) {
|
||||||
final lowerValue = value.toLowerCase();
|
final lowerValue = value.toLowerCase();
|
||||||
final filteredMap = {
|
|
||||||
for (final entry in expansionListData.entries)
|
final filtered = <ContactsBrowserElement>[];
|
||||||
entry.key: (expansionListData[entry.key] ?? []).where((element) {
|
for (final element in initialList) {
|
||||||
switch (element.kind) {
|
switch (element.kind) {
|
||||||
case ContactsBrowserElementKind.contact:
|
case ContactsBrowserElementKind.contact:
|
||||||
final contact = element.contact!;
|
final contact = element.contact!;
|
||||||
return contact.nickname
|
if (contact.nickname.toLowerCase().contains(lowerValue) ||
|
||||||
.toLowerCase()
|
|
||||||
.contains(lowerValue) ||
|
|
||||||
contact.profile.name
|
contact.profile.name
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.contains(lowerValue) ||
|
.contains(lowerValue) ||
|
||||||
contact.profile.pronouns
|
contact.profile.pronouns
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.contains(lowerValue);
|
.contains(lowerValue)) {
|
||||||
|
filtered.add(element);
|
||||||
|
}
|
||||||
case ContactsBrowserElementKind.invitation:
|
case ContactsBrowserElementKind.invitation:
|
||||||
final invitation = element.invitation!;
|
final invitation = element.invitation!;
|
||||||
return invitation.message
|
if (invitation.message
|
||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.contains(lowerValue) ||
|
.contains(lowerValue) ||
|
||||||
invitation.recipient.toLowerCase().contains(lowerValue);
|
invitation.recipient
|
||||||
|
.toLowerCase()
|
||||||
|
.contains(lowerValue)) {
|
||||||
|
filtered.add(element);
|
||||||
}
|
}
|
||||||
}).toList()
|
}
|
||||||
};
|
}
|
||||||
return filteredMap;
|
return filtered;
|
||||||
},
|
},
|
||||||
hideEmptyExpansionItems: true,
|
|
||||||
searchFieldHeight: 40,
|
searchFieldHeight: 40,
|
||||||
listViewPadding: const EdgeInsets.all(4),
|
listViewPadding: const EdgeInsets.fromLTRB(4, 0, 4, 4),
|
||||||
spaceBetweenSearchAndList: 4,
|
searchFieldPadding: const EdgeInsets.fromLTRB(4, 8, 4, 4),
|
||||||
emptyWidget: contactList == null
|
emptyWidget: contactList == null
|
||||||
? waitingPage(text: translate('contact_list.loading_contacts'))
|
? waitingPage(
|
||||||
|
text: translate('contact_list.loading_contacts'))
|
||||||
: const EmptyContactListWidget(),
|
: const EmptyContactListWidget(),
|
||||||
defaultSuffixIconColor: scale.primaryScale.border,
|
defaultSuffixIconColor: scale.primaryScale.border,
|
||||||
closeKeyboardWhenScrolling: true,
|
closeKeyboardWhenScrolling: true,
|
||||||
searchFieldEnabled: contactList != null,
|
searchFieldEnabled: contactList != null,
|
||||||
inputDecoration:
|
inputDecoration:
|
||||||
InputDecoration(labelText: translate('contact_list.search')),
|
InputDecoration(labelText: translate('contact_list.search')),
|
||||||
).expanded()
|
secondaryWidget:
|
||||||
|
buildInvitationButton(context).paddingLTRB(4, 0, 0, 0))
|
||||||
|
.expanded()
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -318,5 +286,5 @@ class _ContactsBrowserState extends State<ContactsBrowser>
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////
|
||||||
final _receiveInviteMenuController = StarMenuController();
|
final _invitationMenuController = StarMenuController();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,196 +0,0 @@
|
||||||
import 'package:async_tools/async_tools.dart';
|
|
||||||
import 'package:awesome_extensions/awesome_extensions.dart';
|
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_translate/flutter_translate.dart';
|
|
||||||
import 'package:provider/provider.dart';
|
|
||||||
|
|
||||||
import '../../chat/chat.dart';
|
|
||||||
import '../../chat_list/chat_list.dart';
|
|
||||||
import '../../layout/layout.dart';
|
|
||||||
import '../../proto/proto.dart' as proto;
|
|
||||||
import '../../theme/theme.dart';
|
|
||||||
import '../contacts.dart';
|
|
||||||
|
|
||||||
const _kDoBackArrow = 'doBackArrow';
|
|
||||||
|
|
||||||
class ContactsDialog extends StatefulWidget {
|
|
||||||
const ContactsDialog._({required this.modalContext});
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<ContactsDialog> createState() => _ContactsDialogState();
|
|
||||||
|
|
||||||
static Future<void> show(BuildContext modalContext) async {
|
|
||||||
await showDialog<void>(
|
|
||||||
context: modalContext,
|
|
||||||
barrierDismissible: false,
|
|
||||||
useRootNavigator: false,
|
|
||||||
builder: (context) => ContactsDialog._(modalContext: modalContext));
|
|
||||||
}
|
|
||||||
|
|
||||||
final BuildContext modalContext;
|
|
||||||
|
|
||||||
@override
|
|
||||||
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
|
||||||
super.debugFillProperties(properties);
|
|
||||||
properties
|
|
||||||
.add(DiagnosticsProperty<BuildContext>('modalContext', modalContext));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _ContactsDialogState extends State<ContactsDialog> {
|
|
||||||
@override
|
|
||||||
void initState() {
|
|
||||||
super.initState();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final theme = Theme.of(context);
|
|
||||||
final scale = theme.extension<ScaleScheme>()!;
|
|
||||||
final appBarIconColor = scale.primaryScale.borderText;
|
|
||||||
|
|
||||||
final enableSplit = !isMobileWidth(context);
|
|
||||||
final enableLeft = enableSplit || _selectedContact == null;
|
|
||||||
final enableRight = enableSplit || _selectedContact != null;
|
|
||||||
|
|
||||||
return SizedBox(
|
|
||||||
width: MediaQuery.of(context).size.width,
|
|
||||||
child: StyledScaffold(
|
|
||||||
appBar: DefaultAppBar(
|
|
||||||
title: Text(!enableSplit && enableRight
|
|
||||||
? translate('contacts_dialog.edit_contact')
|
|
||||||
: translate('contacts_dialog.contacts')),
|
|
||||||
leading: IconButton(
|
|
||||||
icon: const Icon(Icons.arrow_back),
|
|
||||||
onPressed: () {
|
|
||||||
singleFuture((this, _kDoBackArrow), () async {
|
|
||||||
final confirmed = await _onContactSelected(null);
|
|
||||||
if (!enableSplit && enableRight) {
|
|
||||||
} else {
|
|
||||||
if (confirmed) {
|
|
||||||
if (context.mounted) {
|
|
||||||
Navigator.pop(context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
),
|
|
||||||
actions: [
|
|
||||||
if (_selectedContact != null)
|
|
||||||
FittedBox(
|
|
||||||
fit: BoxFit.scaleDown,
|
|
||||||
child:
|
|
||||||
Column(mainAxisSize: MainAxisSize.min, children: [
|
|
||||||
IconButton(
|
|
||||||
icon: const Icon(Icons.chat_bubble),
|
|
||||||
color: appBarIconColor,
|
|
||||||
tooltip: translate('contacts_dialog.new_chat'),
|
|
||||||
onPressed: () async {
|
|
||||||
await _onChatStarted(_selectedContact!);
|
|
||||||
}),
|
|
||||||
Text(translate('contacts_dialog.new_chat'),
|
|
||||||
style: theme.textTheme.labelSmall!
|
|
||||||
.copyWith(color: appBarIconColor)),
|
|
||||||
])).paddingLTRB(8, 0, 8, 0),
|
|
||||||
if (enableSplit && _selectedContact != null)
|
|
||||||
FittedBox(
|
|
||||||
fit: BoxFit.scaleDown,
|
|
||||||
child:
|
|
||||||
Column(mainAxisSize: MainAxisSize.min, children: [
|
|
||||||
IconButton(
|
|
||||||
icon: const Icon(Icons.close),
|
|
||||||
color: appBarIconColor,
|
|
||||||
tooltip:
|
|
||||||
translate('contacts_dialog.close_contact'),
|
|
||||||
onPressed: () async {
|
|
||||||
await _onContactSelected(null);
|
|
||||||
}),
|
|
||||||
Text(translate('contacts_dialog.close_contact'),
|
|
||||||
style: theme.textTheme.labelSmall!
|
|
||||||
.copyWith(color: appBarIconColor)),
|
|
||||||
])).paddingLTRB(8, 0, 8, 0),
|
|
||||||
]),
|
|
||||||
body: LayoutBuilder(builder: (context, constraint) {
|
|
||||||
final maxWidth = constraint.maxWidth;
|
|
||||||
|
|
||||||
return ColoredBox(
|
|
||||||
color: scale.primaryScale.appBackground,
|
|
||||||
child: Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Offstage(
|
|
||||||
offstage: !enableLeft,
|
|
||||||
child: SizedBox(
|
|
||||||
width: enableLeft && !enableRight
|
|
||||||
? maxWidth
|
|
||||||
: (maxWidth / 3).clamp(200, 500),
|
|
||||||
child: DecoratedBox(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: scale
|
|
||||||
.primaryScale.subtleBackground),
|
|
||||||
child: ContactsBrowser(
|
|
||||||
selectedContactRecordKey: _selectedContact
|
|
||||||
?.localConversationRecordKey
|
|
||||||
.toVeilid(),
|
|
||||||
onContactSelected: _onContactSelected,
|
|
||||||
onStartChat: _onChatStarted,
|
|
||||||
).paddingLTRB(8, 0, 8, 8)))),
|
|
||||||
if (enableRight && enableLeft)
|
|
||||||
Container(
|
|
||||||
constraints: const BoxConstraints(
|
|
||||||
minWidth: 1, maxWidth: 1),
|
|
||||||
color: scale.primaryScale.subtleBorder),
|
|
||||||
if (enableRight)
|
|
||||||
if (_selectedContact == null)
|
|
||||||
const NoContactWidget().expanded()
|
|
||||||
else
|
|
||||||
ContactDetailsWidget(
|
|
||||||
contact: _selectedContact!,
|
|
||||||
onModifiedState: _onModifiedState)
|
|
||||||
.paddingLTRB(16, 16, 16, 16)
|
|
||||||
.expanded(),
|
|
||||||
]));
|
|
||||||
})));
|
|
||||||
}
|
|
||||||
|
|
||||||
void _onModifiedState(bool isModified) {
|
|
||||||
setState(() {
|
|
||||||
_isModified = isModified;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<bool> _onContactSelected(proto.Contact? contact) async {
|
|
||||||
if (contact != _selectedContact && _isModified) {
|
|
||||||
final ok = await showConfirmModal(
|
|
||||||
context: context,
|
|
||||||
title: translate('confirmation.discard_changes'),
|
|
||||||
text: translate('confirmation.are_you_sure_discard'));
|
|
||||||
if (!ok) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
setState(() {
|
|
||||||
_selectedContact = contact;
|
|
||||||
_isModified = false;
|
|
||||||
});
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<void> _onChatStarted(proto.Contact contact) async {
|
|
||||||
final chatListCubit = context.read<ChatListCubit>();
|
|
||||||
await chatListCubit.getOrCreateChatSingleContact(contact: contact);
|
|
||||||
|
|
||||||
if (mounted) {
|
|
||||||
context
|
|
||||||
.read<ActiveChatCubit>()
|
|
||||||
.setActiveChat(contact.localConversationRecordKey.toVeilid());
|
|
||||||
|
|
||||||
Navigator.pop(context);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
proto.Contact? _selectedContact;
|
|
||||||
bool _isModified = false;
|
|
||||||
}
|
|
171
lib/contacts/views/contacts_page.dart
Normal file
171
lib/contacts/views/contacts_page.dart
Normal file
|
@ -0,0 +1,171 @@
|
||||||
|
import 'package:async_tools/async_tools.dart';
|
||||||
|
import 'package:awesome_extensions/awesome_extensions.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_translate/flutter_translate.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
|
import '../../chat/chat.dart';
|
||||||
|
import '../../chat_list/chat_list.dart';
|
||||||
|
import '../../layout/layout.dart';
|
||||||
|
import '../../proto/proto.dart' as proto;
|
||||||
|
import '../../theme/theme.dart';
|
||||||
|
import '../contacts.dart';
|
||||||
|
|
||||||
|
const _kDoBackArrow = 'doBackArrow';
|
||||||
|
|
||||||
|
class ContactsPage extends StatefulWidget {
|
||||||
|
const ContactsPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<ContactsPage> createState() => _ContactsPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ContactsPageState extends State<ContactsPage> {
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
final scale = theme.extension<ScaleScheme>()!;
|
||||||
|
final appBarIconColor = scale.primaryScale.borderText;
|
||||||
|
|
||||||
|
final enableSplit = !isMobileSize(context);
|
||||||
|
final enableLeft = enableSplit || _selectedContact == null;
|
||||||
|
final enableRight = enableSplit || _selectedContact != null;
|
||||||
|
|
||||||
|
return StyledScaffold(
|
||||||
|
appBar: DefaultAppBar(
|
||||||
|
title: Text(!enableSplit && enableRight
|
||||||
|
? translate('contacts_dialog.edit_contact')
|
||||||
|
: translate('contacts_dialog.contacts')),
|
||||||
|
leading: IconButton(
|
||||||
|
icon: const Icon(Icons.arrow_back),
|
||||||
|
onPressed: () {
|
||||||
|
singleFuture((this, _kDoBackArrow), () async {
|
||||||
|
final confirmed = await _onContactSelected(null);
|
||||||
|
if (!enableSplit && enableRight) {
|
||||||
|
} else {
|
||||||
|
if (confirmed) {
|
||||||
|
if (context.mounted) {
|
||||||
|
Navigator.pop(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
actions: [
|
||||||
|
if (_selectedContact != null)
|
||||||
|
FittedBox(
|
||||||
|
fit: BoxFit.scaleDown,
|
||||||
|
child: Column(mainAxisSize: MainAxisSize.min, children: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.chat_bubble),
|
||||||
|
color: appBarIconColor,
|
||||||
|
tooltip: translate('contacts_dialog.new_chat'),
|
||||||
|
onPressed: () async {
|
||||||
|
await _onChatStarted(_selectedContact!);
|
||||||
|
}),
|
||||||
|
Text(translate('contacts_dialog.new_chat'),
|
||||||
|
style: theme.textTheme.labelSmall!
|
||||||
|
.copyWith(color: appBarIconColor)),
|
||||||
|
])).paddingLTRB(8, 0, 8, 0),
|
||||||
|
if (enableSplit && _selectedContact != null)
|
||||||
|
FittedBox(
|
||||||
|
fit: BoxFit.scaleDown,
|
||||||
|
child: Column(mainAxisSize: MainAxisSize.min, children: [
|
||||||
|
IconButton(
|
||||||
|
icon: const Icon(Icons.close),
|
||||||
|
color: appBarIconColor,
|
||||||
|
tooltip: translate('contacts_dialog.close_contact'),
|
||||||
|
onPressed: () async {
|
||||||
|
await _onContactSelected(null);
|
||||||
|
}),
|
||||||
|
Text(translate('contacts_dialog.close_contact'),
|
||||||
|
style: theme.textTheme.labelSmall!
|
||||||
|
.copyWith(color: appBarIconColor)),
|
||||||
|
])).paddingLTRB(8, 0, 8, 0),
|
||||||
|
]),
|
||||||
|
body: LayoutBuilder(builder: (context, constraint) {
|
||||||
|
final maxWidth = constraint.maxWidth;
|
||||||
|
|
||||||
|
return ColoredBox(
|
||||||
|
color: scale.primaryScale.appBackground,
|
||||||
|
child:
|
||||||
|
Row(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||||
|
Offstage(
|
||||||
|
offstage: !enableLeft,
|
||||||
|
child: SizedBox(
|
||||||
|
width: enableLeft && !enableRight
|
||||||
|
? maxWidth
|
||||||
|
: (maxWidth / 3).clamp(200, 500),
|
||||||
|
child: DecoratedBox(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: scale.primaryScale.subtleBackground),
|
||||||
|
child: ContactsBrowser(
|
||||||
|
selectedContactRecordKey: _selectedContact
|
||||||
|
?.localConversationRecordKey
|
||||||
|
.toVeilid(),
|
||||||
|
onContactSelected: _onContactSelected,
|
||||||
|
onStartChat: _onChatStarted,
|
||||||
|
).paddingLTRB(8, 0, 8, 8)))),
|
||||||
|
if (enableRight && enableLeft)
|
||||||
|
Container(
|
||||||
|
constraints:
|
||||||
|
const BoxConstraints(minWidth: 1, maxWidth: 1),
|
||||||
|
color: scale.primaryScale.subtleBorder),
|
||||||
|
if (enableRight)
|
||||||
|
if (_selectedContact == null)
|
||||||
|
const NoContactWidget().expanded()
|
||||||
|
else
|
||||||
|
ContactDetailsWidget(
|
||||||
|
contact: _selectedContact!,
|
||||||
|
onModifiedState: _onModifiedState)
|
||||||
|
.paddingLTRB(16, 16, 16, 16)
|
||||||
|
.expanded(),
|
||||||
|
]));
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onModifiedState(bool isModified) {
|
||||||
|
setState(() {
|
||||||
|
_isModified = isModified;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> _onContactSelected(proto.Contact? contact) async {
|
||||||
|
if (contact != _selectedContact && _isModified) {
|
||||||
|
final ok = await showConfirmModal(
|
||||||
|
context: context,
|
||||||
|
title: translate('confirmation.discard_changes'),
|
||||||
|
text: translate('confirmation.are_you_sure_discard'));
|
||||||
|
if (!ok) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setState(() {
|
||||||
|
_selectedContact = contact;
|
||||||
|
_isModified = false;
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onChatStarted(proto.Contact contact) async {
|
||||||
|
final chatListCubit = context.read<ChatListCubit>();
|
||||||
|
await chatListCubit.getOrCreateChatSingleContact(contact: contact);
|
||||||
|
|
||||||
|
if (mounted) {
|
||||||
|
context
|
||||||
|
.read<ActiveChatCubit>()
|
||||||
|
.setActiveChat(contact.localConversationRecordKey.toVeilid());
|
||||||
|
Navigator.pop(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
proto.Contact? _selectedContact;
|
||||||
|
bool _isModified = false;
|
||||||
|
}
|
|
@ -2,7 +2,7 @@ export 'availability_widget.dart';
|
||||||
export 'contact_details_widget.dart';
|
export 'contact_details_widget.dart';
|
||||||
export 'contact_item_widget.dart';
|
export 'contact_item_widget.dart';
|
||||||
export 'contacts_browser.dart';
|
export 'contacts_browser.dart';
|
||||||
export 'contacts_dialog.dart';
|
export 'contacts_page.dart';
|
||||||
export 'edit_contact_form.dart';
|
export 'edit_contact_form.dart';
|
||||||
export 'empty_contact_list_widget.dart';
|
export 'empty_contact_list_widget.dart';
|
||||||
export 'no_contact_widget.dart';
|
export 'no_contact_widget.dart';
|
||||||
|
|
|
@ -84,8 +84,9 @@ class _DrawerMenuState extends State<DrawerMenu> {
|
||||||
hoverBorder = border;
|
hoverBorder = border;
|
||||||
activeBorder = border;
|
activeBorder = border;
|
||||||
} else {
|
} else {
|
||||||
background =
|
background = selected
|
||||||
selected ? scale.activeElementBackground : scale.elementBackground;
|
? scale.elementBackground
|
||||||
|
: scale.elementBackground.withAlpha(128);
|
||||||
hoverBackground = scale.hoverElementBackground;
|
hoverBackground = scale.hoverElementBackground;
|
||||||
activeBackground = scale.activeElementBackground;
|
activeBackground = scale.activeElementBackground;
|
||||||
border = loggedIn ? scale.border : scale.subtleBorder;
|
border = loggedIn ? scale.border : scale.subtleBorder;
|
||||||
|
@ -132,9 +133,16 @@ class _DrawerMenuState extends State<DrawerMenu> {
|
||||||
callback: callback,
|
callback: callback,
|
||||||
footerButtonIcon: loggedIn ? Icons.edit_outlined : null,
|
footerButtonIcon: loggedIn ? Icons.edit_outlined : null,
|
||||||
footerCallback: footerCallback,
|
footerCallback: footerCallback,
|
||||||
footerButtonIconColor: border,
|
footerButtonIconColor:
|
||||||
footerButtonIconHoverColor: hoverBackground,
|
scaleConfig.preferBorders ? scale.border : scale.borderText,
|
||||||
footerButtonIconFocusColor: activeBackground,
|
footerButtonIconHoverColor:
|
||||||
|
(scaleConfig.preferBorders || scaleConfig.useVisualIndicators)
|
||||||
|
? null
|
||||||
|
: hoverBorder,
|
||||||
|
footerButtonIconFocusColor:
|
||||||
|
(scaleConfig.preferBorders || scaleConfig.useVisualIndicators)
|
||||||
|
? null
|
||||||
|
: activeBorder,
|
||||||
minHeight: 48,
|
minHeight: 48,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,6 +39,8 @@ class MenuItemWidget extends StatelessWidget {
|
||||||
}
|
}
|
||||||
return backgroundColor;
|
return backgroundColor;
|
||||||
}),
|
}),
|
||||||
|
overlayColor:
|
||||||
|
WidgetStateProperty.resolveWith((states) => backgroundHoverColor),
|
||||||
side: WidgetStateBorderSide.resolveWith((states) {
|
side: WidgetStateBorderSide.resolveWith((states) {
|
||||||
if (states.contains(WidgetState.hovered)) {
|
if (states.contains(WidgetState.hovered)) {
|
||||||
return borderColor != null
|
return borderColor != null
|
||||||
|
|
|
@ -89,7 +89,11 @@ class _HomeAccountReadyState extends State<HomeAccountReady> {
|
||||||
)),
|
)),
|
||||||
tooltip: translate('menu.contacts_tooltip'),
|
tooltip: translate('menu.contacts_tooltip'),
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
await ContactsDialog.show(context);
|
await Navigator.of(context).push(
|
||||||
|
MaterialPageRoute<void>(
|
||||||
|
builder: (_) => const ContactsPage(),
|
||||||
|
),
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -139,10 +143,7 @@ class _HomeAccountReadyState extends State<HomeAccountReady> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final isLarge = responsiveVisibility(
|
final isSmallScreen = isMobileSize(context);
|
||||||
context: context,
|
|
||||||
phone: false,
|
|
||||||
);
|
|
||||||
|
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final scaleScheme = theme.extension<ScaleScheme>()!;
|
final scaleScheme = theme.extension<ScaleScheme>()!;
|
||||||
|
@ -159,14 +160,7 @@ class _HomeAccountReadyState extends State<HomeAccountReady> {
|
||||||
late final bool visibleRight;
|
late final bool visibleRight;
|
||||||
late final double leftWidth;
|
late final double leftWidth;
|
||||||
late final double rightWidth;
|
late final double rightWidth;
|
||||||
if (isLarge) {
|
if (isSmallScreen) {
|
||||||
visibleLeft = true;
|
|
||||||
visibleRight = true;
|
|
||||||
leftWidth = leftColumnSize;
|
|
||||||
rightWidth = constraints.maxWidth -
|
|
||||||
leftColumnSize -
|
|
||||||
(scaleConfig.useVisualIndicators ? 2 : 0);
|
|
||||||
} else {
|
|
||||||
if (hasActiveChat) {
|
if (hasActiveChat) {
|
||||||
visibleLeft = false;
|
visibleLeft = false;
|
||||||
visibleRight = true;
|
visibleRight = true;
|
||||||
|
@ -178,6 +172,13 @@ class _HomeAccountReadyState extends State<HomeAccountReady> {
|
||||||
leftWidth = constraints.maxWidth;
|
leftWidth = constraints.maxWidth;
|
||||||
rightWidth = 400; // whatever
|
rightWidth = 400; // whatever
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
visibleLeft = true;
|
||||||
|
visibleRight = true;
|
||||||
|
leftWidth = leftColumnSize;
|
||||||
|
rightWidth = constraints.maxWidth -
|
||||||
|
leftColumnSize -
|
||||||
|
(scaleConfig.useVisualIndicators ? 2 : 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
return Row(crossAxisAlignment: CrossAxisAlignment.stretch, children: [
|
return Row(crossAxisAlignment: CrossAxisAlignment.stretch, children: [
|
||||||
|
|
|
@ -136,15 +136,11 @@ class HomeScreenState extends State<HomeScreen>
|
||||||
}
|
}
|
||||||
|
|
||||||
// Re-export all ready blocs to the account display subtree
|
// Re-export all ready blocs to the account display subtree
|
||||||
return perAccountCollectionState.provide(
|
final pages = <MaterialPage<void>>[
|
||||||
child: Navigator(
|
const MaterialPage<void>(child: HomeAccountReady())
|
||||||
onPopPage: (route, result) {
|
];
|
||||||
if (!route.didPop(result)) {
|
return perAccountCollectionState.provideReady(
|
||||||
return false;
|
child: Navigator(onDidRemovePage: pages.remove, pages: pages));
|
||||||
}
|
|
||||||
return true;
|
|
||||||
},
|
|
||||||
pages: const [MaterialPage(child: HomeAccountReady())]));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -65,29 +65,49 @@ class RouterCubit extends Cubit<RouterState> {
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/edit_account',
|
path: '/edit_account',
|
||||||
|
redirect: (_, state) {
|
||||||
|
final extra = state.extra;
|
||||||
|
if (extra == null ||
|
||||||
|
extra is! List<Object> ||
|
||||||
|
extra[0] is! TypedKey) {
|
||||||
|
return '/';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final extra = state.extra! as List<Object?>;
|
final extra = state.extra! as List<Object>;
|
||||||
return EditAccountPage(
|
return EditAccountPage(
|
||||||
superIdentityRecordKey: extra[0]! as TypedKey,
|
superIdentityRecordKey: extra[0] as TypedKey,
|
||||||
initialValue: extra[1]! as AccountSpec,
|
initialValue: extra[1] as AccountSpec,
|
||||||
accountRecord: extra[2]! as OwnedDHTRecordPointer,
|
accountRecord: extra[2] as OwnedDHTRecordPointer,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/new_account',
|
path: '/new_account',
|
||||||
builder: (context, state) => const NewAccountPage(),
|
builder: (context, state) => const NewAccountPage(),
|
||||||
),
|
routes: [
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/new_account/recovery_key',
|
path: 'recovery_key',
|
||||||
|
redirect: (_, state) {
|
||||||
|
final extra = state.extra;
|
||||||
|
if (extra == null ||
|
||||||
|
extra is! List<Object> ||
|
||||||
|
extra[0] is! WritableSuperIdentity ||
|
||||||
|
extra[1] is! String) {
|
||||||
|
return '/';
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
builder: (context, state) {
|
builder: (context, state) {
|
||||||
final extra = state.extra! as List<Object?>;
|
final extra = state.extra! as List<Object>;
|
||||||
|
|
||||||
return ShowRecoveryKeyPage(
|
return ShowRecoveryKeyPage(
|
||||||
writableSuperIdentity:
|
writableSuperIdentity:
|
||||||
extra[0]! as WritableSuperIdentity,
|
extra[0] as WritableSuperIdentity,
|
||||||
name: extra[1]! as String);
|
name: extra[1] as String);
|
||||||
}),
|
}),
|
||||||
|
]),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/settings',
|
path: '/settings',
|
||||||
builder: (context, state) => const SettingsPage(),
|
builder: (context, state) => const SettingsPage(),
|
||||||
|
|
|
@ -16,7 +16,7 @@ ChatTheme makeChatTheme(
|
||||||
: scale.secondaryScale.calloutBackground,
|
: scale.secondaryScale.calloutBackground,
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
scale.grayScale.appBackground.withAlpha(scaleConfig.wallpaperAlpha),
|
scale.grayScale.appBackground.withAlpha(scaleConfig.wallpaperAlpha),
|
||||||
messageBorderRadius: scaleConfig.borderRadiusScale * 16,
|
messageBorderRadius: scaleConfig.borderRadiusScale * 12,
|
||||||
bubbleBorderSide: scaleConfig.preferBorders
|
bubbleBorderSide: scaleConfig.preferBorders
|
||||||
? BorderSide(
|
? BorderSide(
|
||||||
color: scale.primaryScale.calloutBackground,
|
color: scale.primaryScale.calloutBackground,
|
||||||
|
@ -37,7 +37,7 @@ ChatTheme makeChatTheme(
|
||||||
filled: !scaleConfig.preferBorders,
|
filled: !scaleConfig.preferBorders,
|
||||||
fillColor: scale.primaryScale.subtleBackground,
|
fillColor: scale.primaryScale.subtleBackground,
|
||||||
isDense: true,
|
isDense: true,
|
||||||
contentPadding: const EdgeInsets.fromLTRB(8, 12, 8, 12),
|
contentPadding: const EdgeInsets.fromLTRB(8, 8, 8, 8),
|
||||||
disabledBorder: OutlineInputBorder(
|
disabledBorder: OutlineInputBorder(
|
||||||
borderSide: scaleConfig.preferBorders
|
borderSide: scaleConfig.preferBorders
|
||||||
? BorderSide(color: scale.grayScale.border, width: 2)
|
? BorderSide(color: scale.grayScale.border, width: 2)
|
||||||
|
@ -65,10 +65,12 @@ ChatTheme makeChatTheme(
|
||||||
color: scaleConfig.preferBorders
|
color: scaleConfig.preferBorders
|
||||||
? scale.primaryScale.elementBackground
|
? scale.primaryScale.elementBackground
|
||||||
: scale.primaryScale.border),
|
: scale.primaryScale.border),
|
||||||
inputPadding: const EdgeInsets.all(12),
|
inputPadding: const EdgeInsets.all(6),
|
||||||
inputTextColor: !scaleConfig.preferBorders
|
inputTextColor: !scaleConfig.preferBorders
|
||||||
? scale.primaryScale.appText
|
? scale.primaryScale.appText
|
||||||
: scale.primaryScale.border,
|
: scale.primaryScale.border,
|
||||||
|
messageInsetsHorizontal: 12,
|
||||||
|
messageInsetsVertical: 8,
|
||||||
attachmentButtonIcon: const Icon(Icons.attach_file),
|
attachmentButtonIcon: const Icon(Icons.attach_file),
|
||||||
sentMessageBodyTextStyle: textTheme.bodyLarge!.copyWith(
|
sentMessageBodyTextStyle: textTheme.bodyLarge!.copyWith(
|
||||||
color: scaleConfig.preferBorders
|
color: scaleConfig.preferBorders
|
||||||
|
|
|
@ -263,6 +263,36 @@ ThemeData contrastGenerator({
|
||||||
|
|
||||||
final baseThemeData = scaleTheme.toThemeData(brightness);
|
final baseThemeData = scaleTheme.toThemeData(brightness);
|
||||||
|
|
||||||
|
WidgetStateProperty<BorderSide?> elementBorderWidgetStateProperty() =>
|
||||||
|
WidgetStateProperty.resolveWith((states) {
|
||||||
|
if (states.contains(WidgetState.disabled)) {
|
||||||
|
return BorderSide(color: scheme.grayScale.border.withAlpha(0x7F));
|
||||||
|
} else if (states.contains(WidgetState.pressed)) {
|
||||||
|
return BorderSide(
|
||||||
|
color: scheme.primaryScale.border,
|
||||||
|
strokeAlign: BorderSide.strokeAlignOutside);
|
||||||
|
} else if (states.contains(WidgetState.hovered)) {
|
||||||
|
return BorderSide(color: scheme.primaryScale.hoverBorder, width: 2);
|
||||||
|
} else if (states.contains(WidgetState.focused)) {
|
||||||
|
return BorderSide(color: scheme.primaryScale.hoverBorder, width: 2);
|
||||||
|
}
|
||||||
|
return BorderSide(color: scheme.primaryScale.border);
|
||||||
|
});
|
||||||
|
|
||||||
|
WidgetStateProperty<Color?> elementBackgroundWidgetStateProperty() =>
|
||||||
|
WidgetStateProperty.resolveWith((states) {
|
||||||
|
if (states.contains(WidgetState.disabled)) {
|
||||||
|
return scheme.grayScale.elementBackground;
|
||||||
|
} else if (states.contains(WidgetState.pressed)) {
|
||||||
|
return scheme.primaryScale.activeElementBackground;
|
||||||
|
} else if (states.contains(WidgetState.hovered)) {
|
||||||
|
return scheme.primaryScale.hoverElementBackground;
|
||||||
|
} else if (states.contains(WidgetState.focused)) {
|
||||||
|
return scheme.primaryScale.activeElementBackground;
|
||||||
|
}
|
||||||
|
return scheme.primaryScale.elementBackground;
|
||||||
|
});
|
||||||
|
|
||||||
final elevatedButtonTheme = ElevatedButtonThemeData(
|
final elevatedButtonTheme = ElevatedButtonThemeData(
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: scheme.primaryScale.elementBackground,
|
backgroundColor: scheme.primaryScale.elementBackground,
|
||||||
|
@ -274,20 +304,9 @@ ThemeData contrastGenerator({
|
||||||
side: BorderSide(color: scheme.primaryScale.border),
|
side: BorderSide(color: scheme.primaryScale.border),
|
||||||
borderRadius:
|
borderRadius:
|
||||||
BorderRadius.circular(8 * scaleConfig.borderRadiusScale)))
|
BorderRadius.circular(8 * scaleConfig.borderRadiusScale)))
|
||||||
.copyWith(side: WidgetStateProperty.resolveWith((states) {
|
.copyWith(
|
||||||
if (states.contains(WidgetState.disabled)) {
|
side: elementBorderWidgetStateProperty(),
|
||||||
return BorderSide(color: scheme.grayScale.border.withAlpha(0x7F));
|
backgroundColor: elementBackgroundWidgetStateProperty()));
|
||||||
} else if (states.contains(WidgetState.pressed)) {
|
|
||||||
return BorderSide(
|
|
||||||
color: scheme.primaryScale.border,
|
|
||||||
strokeAlign: BorderSide.strokeAlignOutside);
|
|
||||||
} else if (states.contains(WidgetState.hovered)) {
|
|
||||||
return BorderSide(color: scheme.primaryScale.hoverBorder);
|
|
||||||
} else if (states.contains(WidgetState.focused)) {
|
|
||||||
return BorderSide(color: scheme.primaryScale.hoverBorder, width: 2);
|
|
||||||
}
|
|
||||||
return BorderSide(color: scheme.primaryScale.border);
|
|
||||||
})));
|
|
||||||
|
|
||||||
final themeData = baseThemeData.copyWith(
|
final themeData = baseThemeData.copyWith(
|
||||||
// chipTheme: baseThemeData.chipTheme.copyWith(
|
// chipTheme: baseThemeData.chipTheme.copyWith(
|
||||||
|
|
|
@ -97,7 +97,7 @@ class ScaleScheme extends ThemeExtension<ScaleScheme> {
|
||||||
onSurfaceVariant: secondaryScale.appText,
|
onSurfaceVariant: secondaryScale.appText,
|
||||||
outline: primaryScale.border,
|
outline: primaryScale.border,
|
||||||
outlineVariant: secondaryScale.border,
|
outlineVariant: secondaryScale.border,
|
||||||
shadow: primaryScale.appBackground.darken(60),
|
shadow: primaryScale.primary.darken(60),
|
||||||
//scrim: primaryScale.background,
|
//scrim: primaryScale.background,
|
||||||
// inverseSurface: primaryScale.subtleText,
|
// inverseSurface: primaryScale.subtleText,
|
||||||
// onInverseSurface: primaryScale.subtleBackground,
|
// onInverseSurface: primaryScale.subtleBackground,
|
||||||
|
|
|
@ -43,6 +43,50 @@ class ScaleTheme extends ThemeExtension<ScaleTheme> {
|
||||||
config: config.lerp(other.config, t));
|
config: config.lerp(other.config, t));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WidgetStateProperty<BorderSide?> elementBorderWidgetStateProperty() =>
|
||||||
|
WidgetStateProperty.resolveWith((states) {
|
||||||
|
if (states.contains(WidgetState.disabled)) {
|
||||||
|
return BorderSide(
|
||||||
|
color: scheme.grayScale.border.withAlpha(0x7F),
|
||||||
|
strokeAlign: BorderSide.strokeAlignOutside);
|
||||||
|
} else if (states.contains(WidgetState.pressed)) {
|
||||||
|
return BorderSide(
|
||||||
|
color: scheme.primaryScale.border,
|
||||||
|
);
|
||||||
|
} else if (states.contains(WidgetState.hovered)) {
|
||||||
|
return BorderSide(
|
||||||
|
color: scheme.primaryScale.hoverBorder,
|
||||||
|
strokeAlign: BorderSide.strokeAlignOutside);
|
||||||
|
} else if (states.contains(WidgetState.focused)) {
|
||||||
|
return BorderSide(
|
||||||
|
color: scheme.primaryScale.hoverBorder,
|
||||||
|
width: 2,
|
||||||
|
strokeAlign: BorderSide.strokeAlignOutside);
|
||||||
|
}
|
||||||
|
return BorderSide(
|
||||||
|
color: scheme.primaryScale.border,
|
||||||
|
strokeAlign: BorderSide.strokeAlignOutside);
|
||||||
|
});
|
||||||
|
|
||||||
|
WidgetStateProperty<Color?> elementColorWidgetStateProperty() =>
|
||||||
|
WidgetStateProperty.resolveWith((states) {
|
||||||
|
if (states.contains(WidgetState.disabled)) {
|
||||||
|
return scheme.grayScale.primary.withAlpha(0x7F);
|
||||||
|
} else if (states.contains(WidgetState.pressed)) {
|
||||||
|
return scheme.primaryScale.borderText;
|
||||||
|
} else if (states.contains(WidgetState.hovered)) {
|
||||||
|
return scheme.primaryScale.borderText;
|
||||||
|
} else if (states.contains(WidgetState.focused)) {
|
||||||
|
return scheme.primaryScale.borderText;
|
||||||
|
}
|
||||||
|
return Color.lerp(
|
||||||
|
scheme.primaryScale.borderText, scheme.primaryScale.primary, 0.25);
|
||||||
|
});
|
||||||
|
|
||||||
|
// WidgetStateProperty<Color?> elementBackgroundWidgetStateProperty() {
|
||||||
|
// return null;
|
||||||
|
// }
|
||||||
|
|
||||||
ThemeData toThemeData(Brightness brightness) {
|
ThemeData toThemeData(Brightness brightness) {
|
||||||
final colorScheme = scheme.toColorScheme(brightness);
|
final colorScheme = scheme.toColorScheme(brightness);
|
||||||
|
|
||||||
|
@ -51,8 +95,9 @@ class ScaleTheme extends ThemeExtension<ScaleTheme> {
|
||||||
|
|
||||||
final elevatedButtonTheme = ElevatedButtonThemeData(
|
final elevatedButtonTheme = ElevatedButtonThemeData(
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
|
elevation: 0,
|
||||||
|
textStyle: textTheme.labelSmall,
|
||||||
backgroundColor: scheme.primaryScale.elementBackground,
|
backgroundColor: scheme.primaryScale.elementBackground,
|
||||||
foregroundColor: scheme.primaryScale.appText,
|
|
||||||
disabledBackgroundColor:
|
disabledBackgroundColor:
|
||||||
scheme.grayScale.elementBackground.withAlpha(0x7F),
|
scheme.grayScale.elementBackground.withAlpha(0x7F),
|
||||||
disabledForegroundColor:
|
disabledForegroundColor:
|
||||||
|
@ -61,20 +106,11 @@ class ScaleTheme extends ThemeExtension<ScaleTheme> {
|
||||||
side: BorderSide(color: scheme.primaryScale.border),
|
side: BorderSide(color: scheme.primaryScale.border),
|
||||||
borderRadius:
|
borderRadius:
|
||||||
BorderRadius.circular(8 * config.borderRadiusScale)))
|
BorderRadius.circular(8 * config.borderRadiusScale)))
|
||||||
.copyWith(side: WidgetStateProperty.resolveWith((states) {
|
.copyWith(
|
||||||
if (states.contains(WidgetState.disabled)) {
|
foregroundColor: elementColorWidgetStateProperty(),
|
||||||
return BorderSide(color: scheme.grayScale.border.withAlpha(0x7F));
|
side: elementBorderWidgetStateProperty(),
|
||||||
} else if (states.contains(WidgetState.pressed)) {
|
iconColor: elementColorWidgetStateProperty(),
|
||||||
return BorderSide(
|
));
|
||||||
color: scheme.primaryScale.border,
|
|
||||||
strokeAlign: BorderSide.strokeAlignOutside);
|
|
||||||
} else if (states.contains(WidgetState.hovered)) {
|
|
||||||
return BorderSide(color: scheme.primaryScale.hoverBorder);
|
|
||||||
} else if (states.contains(WidgetState.focused)) {
|
|
||||||
return BorderSide(color: scheme.primaryScale.hoverBorder, width: 2);
|
|
||||||
}
|
|
||||||
return BorderSide(color: scheme.primaryScale.border);
|
|
||||||
})));
|
|
||||||
|
|
||||||
final themeData = baseThemeData.copyWith(
|
final themeData = baseThemeData.copyWith(
|
||||||
scrollbarTheme: baseThemeData.scrollbarTheme.copyWith(
|
scrollbarTheme: baseThemeData.scrollbarTheme.copyWith(
|
||||||
|
|
|
@ -29,11 +29,12 @@ class PopControl extends StatelessWidget {
|
||||||
|
|
||||||
return PopScope(
|
return PopScope(
|
||||||
canPop: false,
|
canPop: false,
|
||||||
onPopInvoked: (didPop) {
|
onPopInvokedWithResult: (didPop, _) {
|
||||||
if (didPop) {
|
if (didPop) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
_doDismiss(navigator);
|
_doDismiss(navigator);
|
||||||
|
return;
|
||||||
},
|
},
|
||||||
child: child);
|
child: child);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import 'package:awesome_extensions/awesome_extensions.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import '../theme.dart';
|
import '../theme.dart';
|
||||||
|
@ -13,7 +12,7 @@ class StyledScaffold extends StatelessWidget {
|
||||||
final scaleConfig = theme.extension<ScaleConfig>()!;
|
final scaleConfig = theme.extension<ScaleConfig>()!;
|
||||||
final scale = scaleScheme.scale(ScaleKind.primary);
|
final scale = scaleScheme.scale(ScaleKind.primary);
|
||||||
|
|
||||||
final enableBorder = !isMobileSize(context);
|
const enableBorder = false; //!isMobileSize(context);
|
||||||
|
|
||||||
var scaffold = clipBorder(
|
var scaffold = clipBorder(
|
||||||
clipEnabled: enableBorder,
|
clipEnabled: enableBorder,
|
||||||
|
@ -28,7 +27,7 @@ class StyledScaffold extends StatelessWidget {
|
||||||
|
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () => FocusManager.instance.primaryFocus?.unfocus(),
|
onTap: () => FocusManager.instance.primaryFocus?.unfocus(),
|
||||||
child: scaffold.paddingAll(enableBorder ? 32 : 0));
|
child: scaffold /*.paddingAll(enableBorder ? 32 : 0) */);
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
|
@ -7,7 +7,6 @@ import 'package:flutter/services.dart';
|
||||||
import 'package:window_manager/window_manager.dart';
|
import 'package:window_manager/window_manager.dart';
|
||||||
|
|
||||||
import '../theme/views/responsive.dart';
|
import '../theme/views/responsive.dart';
|
||||||
import 'tools.dart';
|
|
||||||
|
|
||||||
export 'package:window_manager/window_manager.dart' show TitleBarStyle;
|
export 'package:window_manager/window_manager.dart' show TitleBarStyle;
|
||||||
|
|
||||||
|
|
|
@ -1310,10 +1310,10 @@ packages:
|
||||||
description:
|
description:
|
||||||
path: "."
|
path: "."
|
||||||
ref: main
|
ref: main
|
||||||
resolved-ref: db0f7b6f1baec0250ecba82f3d161bac1cf23d7d
|
resolved-ref: f367c2f713dcc0c965a4f7af5952d94b2f699998
|
||||||
url: "https://gitlab.com/veilid/Searchable-Listview.git"
|
url: "https://gitlab.com/veilid/Searchable-Listview.git"
|
||||||
source: git
|
source: git
|
||||||
version: "2.14.1"
|
version: "2.16.0"
|
||||||
share_plus:
|
share_plus:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
|
@ -111,7 +111,7 @@ dependencies:
|
||||||
xterm: ^4.0.0
|
xterm: ^4.0.0
|
||||||
zxing2: ^0.2.3
|
zxing2: ^0.2.3
|
||||||
|
|
||||||
#dependency_overrides:
|
# dependency_overrides:
|
||||||
# async_tools:
|
# async_tools:
|
||||||
# path: ../dart_async_tools
|
# path: ../dart_async_tools
|
||||||
# bloc_advanced_tools:
|
# bloc_advanced_tools:
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue