mirror of
https://gitlab.com/veilid/veilidchat.git
synced 2025-06-08 06:32:49 -04:00
more refactor
This commit is contained in:
parent
b83aa3a64b
commit
c7b541c643
45 changed files with 860 additions and 336 deletions
|
@ -1,3 +1,4 @@
|
||||||
export 'cubit/cubit.dart';
|
export 'cubit/cubit.dart';
|
||||||
|
export 'models/models.dart';
|
||||||
export 'repository/repository.dart';
|
export 'repository/repository.dart';
|
||||||
export 'views/views.dart';
|
export 'views/views.dart';
|
||||||
|
|
1
lib/chat/chat.dart
Normal file
1
lib/chat/chat.dart
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export 'views/views.dart';
|
37
lib/chat/views/build_chat_component.dart
Normal file
37
lib/chat/views/build_chat_component.dart
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import '../../tools/tools.dart';
|
||||||
|
|
||||||
|
Widget buildChatComponent() {
|
||||||
|
// final contactList = ref.watch(fetchContactListProvider).asData?.value ??
|
||||||
|
// const IListConst([]);
|
||||||
|
|
||||||
|
// final activeChat = ref.watch(activeChatStateProvider);
|
||||||
|
// if (activeChat == null) {
|
||||||
|
// return const EmptyChatWidget();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// final activeAccountInfo =
|
||||||
|
// ref.watch(fetchActiveAccountProvider).asData?.value;
|
||||||
|
// if (activeAccountInfo == null) {
|
||||||
|
// return const EmptyChatWidget();
|
||||||
|
// }
|
||||||
|
|
||||||
|
// final activeChatContactIdx = contactList.indexWhere(
|
||||||
|
// (c) =>
|
||||||
|
// proto.TypedKeyProto.fromProto(c.remoteConversationRecordKey) ==
|
||||||
|
// activeChat,
|
||||||
|
// );
|
||||||
|
// if (activeChatContactIdx == -1) {
|
||||||
|
// ref.read(activeChatStateProvider.notifier).state = null;
|
||||||
|
// return const EmptyChatWidget();
|
||||||
|
// }
|
||||||
|
// final activeChatContact = contactList[activeChatContactIdx];
|
||||||
|
|
||||||
|
// return ChatComponent(
|
||||||
|
// activeAccountInfo: activeAccountInfo,
|
||||||
|
// activeChat: activeChat,
|
||||||
|
// activeChatContact: activeChatContact);
|
||||||
|
// }
|
||||||
|
return Builder(builder: waitingPage);
|
||||||
|
}
|
|
@ -7,13 +7,13 @@ import 'package:flutter_chat_types/flutter_chat_types.dart' as types;
|
||||||
import 'package:flutter_chat_ui/flutter_chat_ui.dart';
|
import 'package:flutter_chat_ui/flutter_chat_ui.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
import '../proto/proto.dart' as proto;
|
import '../../old_to_refactor/proto/proto.dart' as proto;
|
||||||
import '../providers/account.dart';
|
import '../../old_to_refactor/providers/account.dart';
|
||||||
import '../providers/chat.dart';
|
import '../../old_to_refactor/providers/chat.dart';
|
||||||
import '../providers/conversation.dart';
|
import '../../old_to_refactor/providers/conversation.dart';
|
||||||
import '../tools/tools.dart';
|
import '../../old_to_refactor/tools/tools.dart';
|
||||||
import '../veilid_init.dart';
|
import '../../old_to_refactor/veilid_init.dart';
|
||||||
import '../veilid_support/veilid_support.dart';
|
import '../../old_to_refactor/veilid_support/veilid_support.dart';
|
||||||
|
|
||||||
class ChatComponent extends ConsumerStatefulWidget {
|
class ChatComponent extends ConsumerStatefulWidget {
|
||||||
const ChatComponent(
|
const ChatComponent(
|
34
lib/chat/views/empty_chat_widget.dart
Normal file
34
lib/chat/views/empty_chat_widget.dart
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class EmptyChatWidget extends StatelessWidget {
|
||||||
|
const EmptyChatWidget({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
// ignore: prefer_expression_function_bodies
|
||||||
|
Widget build(
|
||||||
|
BuildContext context,
|
||||||
|
) =>
|
||||||
|
Container(
|
||||||
|
width: double.infinity,
|
||||||
|
height: double.infinity,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).scaffoldBackgroundColor,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.chat,
|
||||||
|
color: Theme.of(context).disabledColor,
|
||||||
|
size: 48,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'Say Something',
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||||
|
color: Theme.of(context).disabledColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
35
lib/chat/views/no_conversation_widget.dart
Normal file
35
lib/chat/views/no_conversation_widget.dart
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
//XXX should rename this
|
||||||
|
class NoContactWidget extends StatelessWidget {
|
||||||
|
const NoContactWidget({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
// ignore: prefer_expression_function_bodies
|
||||||
|
Widget build(
|
||||||
|
BuildContext context,
|
||||||
|
) =>
|
||||||
|
Container(
|
||||||
|
width: double.infinity,
|
||||||
|
height: double.infinity,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).primaryColor,
|
||||||
|
),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
Icons.emoji_people_outlined,
|
||||||
|
color: Theme.of(context).disabledColor,
|
||||||
|
size: 48,
|
||||||
|
),
|
||||||
|
Text(
|
||||||
|
'Choose A Conversation To Chat',
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||||
|
color: Theme.of(context).disabledColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
1
lib/chat/views/views.dart
Normal file
1
lib/chat/views/views.dart
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export 'build_chat_component.dart';
|
|
@ -1,15 +1,14 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
import 'package:flutter_translate/flutter_translate.dart';
|
import 'package:flutter_translate/flutter_translate.dart';
|
||||||
|
|
||||||
import '../tools/tools.dart';
|
import '../../theme/theme.dart';
|
||||||
|
|
||||||
class EmptyChatListWidget extends ConsumerWidget {
|
class EmptyChatListWidget extends StatelessWidget {
|
||||||
const EmptyChatListWidget({super.key});
|
const EmptyChatListWidget({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
// ignore: prefer_expression_function_bodies
|
// ignore: prefer_expression_function_bodies
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final textTheme = theme.textTheme;
|
final textTheme = theme.textTheme;
|
||||||
final scale = theme.extension<ScaleScheme>()!;
|
final scale = theme.extension<ScaleScheme>()!;
|
0
lib/chat_list/views/views.dart
Normal file
0
lib/chat_list/views/views.dart
Normal file
1
lib/contact_invitation/contact_invitation.dart
Normal file
1
lib/contact_invitation/contact_invitation.dart
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export 'views/views.dart';
|
|
@ -6,9 +6,9 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:flutter_translate/flutter_translate.dart';
|
import 'package:flutter_translate/flutter_translate.dart';
|
||||||
|
|
||||||
import '../tools/tools.dart';
|
import '../old_to_refactor/tools/tools.dart';
|
||||||
import '../veilid_support/veilid_support.dart';
|
import '../old_to_refactor/veilid_support/veilid_support.dart';
|
||||||
import 'invite_dialog.dart';
|
import 'views/invite_dialog.dart';
|
||||||
|
|
||||||
class PasteInviteDialog extends ConsumerStatefulWidget {
|
class PasteInviteDialog extends ConsumerStatefulWidget {
|
||||||
const PasteInviteDialog({super.key});
|
const PasteInviteDialog({super.key});
|
|
@ -13,8 +13,8 @@ import 'package:mobile_scanner/mobile_scanner.dart';
|
||||||
import 'package:pasteboard/pasteboard.dart';
|
import 'package:pasteboard/pasteboard.dart';
|
||||||
import 'package:zxing2/qrcode.dart';
|
import 'package:zxing2/qrcode.dart';
|
||||||
|
|
||||||
import '../tools/tools.dart';
|
import '../old_to_refactor/tools/tools.dart';
|
||||||
import 'invite_dialog.dart';
|
import 'views/invite_dialog.dart';
|
||||||
|
|
||||||
class BarcodeOverlay extends CustomPainter {
|
class BarcodeOverlay extends CustomPainter {
|
||||||
BarcodeOverlay({
|
BarcodeOverlay({
|
|
@ -6,14 +6,13 @@ import 'package:basic_utils/basic_utils.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
import 'package:flutter_translate/flutter_translate.dart';
|
import 'package:flutter_translate/flutter_translate.dart';
|
||||||
import 'package:qr_flutter/qr_flutter.dart';
|
import 'package:qr_flutter/qr_flutter.dart';
|
||||||
|
import 'package:veilid_support/veilid_support.dart';
|
||||||
|
|
||||||
import '../tools/tools.dart';
|
import '../../tools/tools.dart';
|
||||||
import '../veilid_support/veilid_support.dart';
|
|
||||||
|
|
||||||
class ContactInvitationDisplayDialog extends ConsumerStatefulWidget {
|
class ContactInvitationDisplayDialog extends StatefulWidget {
|
||||||
const ContactInvitationDisplayDialog({
|
const ContactInvitationDisplayDialog({
|
||||||
required this.name,
|
required this.name,
|
||||||
required this.message,
|
required this.message,
|
||||||
|
@ -40,7 +39,7 @@ class ContactInvitationDisplayDialog extends ConsumerStatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class ContactInvitationDisplayDialogState
|
class ContactInvitationDisplayDialogState
|
||||||
extends ConsumerState<ContactInvitationDisplayDialog> {
|
extends State<ContactInvitationDisplayDialog> {
|
||||||
final focusNode = FocusNode();
|
final focusNode = FocusNode();
|
||||||
final formKey = GlobalKey<FormState>();
|
final formKey = GlobalKey<FormState>();
|
||||||
late final AutoDisposeFutureProvider<Uint8List?> _generateFutureProvider;
|
late final AutoDisposeFutureProvider<Uint8List?> _generateFutureProvider;
|
|
@ -1,15 +1,12 @@
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
import 'package:flutter_slidable/flutter_slidable.dart';
|
import 'package:flutter_slidable/flutter_slidable.dart';
|
||||||
import 'package:flutter_translate/flutter_translate.dart';
|
import 'package:flutter_translate/flutter_translate.dart';
|
||||||
import '../proto/proto.dart' as proto;
|
import '../../proto/proto.dart' as proto;
|
||||||
import '../providers/account.dart';
|
import '../../theme/theme.dart';
|
||||||
import '../providers/contact_invite.dart';
|
|
||||||
import '../tools/tools.dart';
|
|
||||||
import 'contact_invitation_display.dart';
|
import 'contact_invitation_display.dart';
|
||||||
|
|
||||||
class ContactInvitationItemWidget extends ConsumerWidget {
|
class ContactInvitationItemWidget extends StatelessWidget {
|
||||||
const ContactInvitationItemWidget(
|
const ContactInvitationItemWidget(
|
||||||
{required this.contactInvitationRecord, super.key});
|
{required this.contactInvitationRecord, super.key});
|
||||||
|
|
||||||
|
@ -24,7 +21,7 @@ class ContactInvitationItemWidget extends ConsumerWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
// ignore: prefer_expression_function_bodies
|
// ignore: prefer_expression_function_bodies
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
//final textTheme = theme.textTheme;
|
//final textTheme = theme.textTheme;
|
||||||
final scale = theme.extension<ScaleScheme>()!;
|
final scale = theme.extension<ScaleScheme>()!;
|
|
@ -2,13 +2,12 @@ import 'package:awesome_extensions/awesome_extensions.dart';
|
||||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
|
|
||||||
import '../proto/proto.dart' as proto;
|
import '../../proto/proto.dart' as proto;
|
||||||
import '../tools/tools.dart';
|
import '../../theme/theme.dart';
|
||||||
import 'contact_invitation_item_widget.dart';
|
import 'contact_invitation_item_widget.dart';
|
||||||
|
|
||||||
class ContactInvitationListWidget extends ConsumerStatefulWidget {
|
class ContactInvitationListWidget extends StatefulWidget {
|
||||||
const ContactInvitationListWidget({
|
const ContactInvitationListWidget({
|
||||||
required this.contactInvitationRecordList,
|
required this.contactInvitationRecordList,
|
||||||
super.key,
|
super.key,
|
||||||
|
@ -28,7 +27,7 @@ class ContactInvitationListWidget extends ConsumerStatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class ContactInvitationListWidgetState
|
class ContactInvitationListWidgetState
|
||||||
extends ConsumerState<ContactInvitationListWidget> {
|
extends State<ContactInvitationListWidget> {
|
||||||
final ScrollController _scrollController = ScrollController();
|
final ScrollController _scrollController = ScrollController();
|
||||||
|
|
||||||
@override
|
@override
|
|
@ -3,19 +3,12 @@ import 'dart:async';
|
||||||
import 'package:awesome_extensions/awesome_extensions.dart';
|
import 'package:awesome_extensions/awesome_extensions.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
import 'package:flutter_translate/flutter_translate.dart';
|
import 'package:flutter_translate/flutter_translate.dart';
|
||||||
|
|
||||||
import '../entities/local_account.dart';
|
import '../../account_manager/account_manager.dart';
|
||||||
import '../providers/account.dart';
|
import '../../tools/tools.dart';
|
||||||
import '../providers/contact.dart';
|
|
||||||
import '../providers/contact_invite.dart';
|
|
||||||
import '../tools/tools.dart';
|
|
||||||
import 'enter_password.dart';
|
|
||||||
import 'enter_pin.dart';
|
|
||||||
import 'profile_widget.dart';
|
|
||||||
|
|
||||||
class InviteDialog extends ConsumerStatefulWidget {
|
class InviteDialog extends StatefulWidget {
|
||||||
const InviteDialog(
|
const InviteDialog(
|
||||||
{required this.onValidationCancelled,
|
{required this.onValidationCancelled,
|
||||||
required this.onValidationSuccess,
|
required this.onValidationSuccess,
|
||||||
|
@ -58,7 +51,7 @@ class InviteDialog extends ConsumerStatefulWidget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class InviteDialogState extends ConsumerState<InviteDialog> {
|
class InviteDialogState extends State<InviteDialog> {
|
||||||
ValidContactInvitation? _validInvitation;
|
ValidContactInvitation? _validInvitation;
|
||||||
bool _isValidating = false;
|
bool _isValidating = false;
|
||||||
bool _isAccepting = false;
|
bool _isAccepting = false;
|
|
@ -0,0 +1,65 @@
|
||||||
|
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';
|
||||||
|
import 'paste_invite_dialog.dart';
|
||||||
|
import 'scan_invite_dialog.dart';
|
||||||
|
import 'send_invite_dialog.dart';
|
||||||
|
|
||||||
|
Widget newContactInvitationBottomSheetBuilder(BuildContext context) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
final textTheme = theme.textTheme;
|
||||||
|
final scale = theme.extension<ScaleScheme>()!;
|
||||||
|
|
||||||
|
return KeyboardListener(
|
||||||
|
focusNode: FocusNode(),
|
||||||
|
onKeyEvent: (ke) {
|
||||||
|
if (ke.logicalKey == LogicalKeyboardKey.escape) {
|
||||||
|
Navigator.pop(context);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: SizedBox(
|
||||||
|
height: 200,
|
||||||
|
child: Column(children: [
|
||||||
|
Text(translate('accounts_menu.invite_contact'),
|
||||||
|
style: textTheme.titleMedium)
|
||||||
|
.paddingAll(8),
|
||||||
|
Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [
|
||||||
|
Column(children: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: () async {
|
||||||
|
Navigator.pop(context);
|
||||||
|
await SendInviteDialog.show(context);
|
||||||
|
},
|
||||||
|
iconSize: 64,
|
||||||
|
icon: const Icon(Icons.contact_page),
|
||||||
|
color: scale.primaryScale.background),
|
||||||
|
Text(translate('accounts_menu.create_invite'))
|
||||||
|
]),
|
||||||
|
Column(children: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: () async {
|
||||||
|
Navigator.pop(context);
|
||||||
|
await ScanInviteDialog.show(context);
|
||||||
|
},
|
||||||
|
iconSize: 64,
|
||||||
|
icon: const Icon(Icons.qr_code_scanner),
|
||||||
|
color: scale.primaryScale.background),
|
||||||
|
Text(translate('accounts_menu.scan_invite'))
|
||||||
|
]),
|
||||||
|
Column(children: [
|
||||||
|
IconButton(
|
||||||
|
onPressed: () async {
|
||||||
|
Navigator.pop(context);
|
||||||
|
await PasteInviteDialog.show(context);
|
||||||
|
},
|
||||||
|
iconSize: 64,
|
||||||
|
icon: const Icon(Icons.paste),
|
||||||
|
color: scale.primaryScale.background),
|
||||||
|
Text(translate('accounts_menu.paste_invite'))
|
||||||
|
])
|
||||||
|
]).expanded()
|
||||||
|
])));
|
||||||
|
}
|
131
lib/contact_invitation/views/paste_invite_dialog.dart
Normal file
131
lib/contact_invitation/views/paste_invite_dialog.dart
Normal file
|
@ -0,0 +1,131 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:awesome_extensions/awesome_extensions.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_translate/flutter_translate.dart';
|
||||||
|
import 'package:veilid_support/veilid_support.dart';
|
||||||
|
|
||||||
|
import '../../theme/theme.dart';
|
||||||
|
import '../../tools/tools.dart';
|
||||||
|
import 'invite_dialog.dart';
|
||||||
|
|
||||||
|
class PasteInviteDialog extends StatefulWidget {
|
||||||
|
const PasteInviteDialog({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
PasteInviteDialogState createState() => PasteInviteDialogState();
|
||||||
|
|
||||||
|
static Future<void> show(BuildContext context) async {
|
||||||
|
await showStyledDialog<void>(
|
||||||
|
context: context,
|
||||||
|
title: translate('paste_invite_dialog.title'),
|
||||||
|
child: const PasteInviteDialog());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class PasteInviteDialogState extends State<PasteInviteDialog> {
|
||||||
|
final _pasteTextController = TextEditingController();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onPasteChanged(
|
||||||
|
String text,
|
||||||
|
Future<void> Function({
|
||||||
|
required Uint8List inviteData,
|
||||||
|
}) validateInviteData) async {
|
||||||
|
final lines = text.split('\n');
|
||||||
|
if (lines.isEmpty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var firstline =
|
||||||
|
lines.indexWhere((element) => element.contains('BEGIN VEILIDCHAT'));
|
||||||
|
firstline += 1;
|
||||||
|
|
||||||
|
var lastline =
|
||||||
|
lines.indexWhere((element) => element.contains('END VEILIDCHAT'));
|
||||||
|
if (lastline == -1) {
|
||||||
|
lastline = lines.length;
|
||||||
|
}
|
||||||
|
if (lastline <= firstline) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
final inviteDataBase64 = lines
|
||||||
|
.sublist(firstline, lastline)
|
||||||
|
.join()
|
||||||
|
.replaceAll(RegExp(r'[^A-Za-z0-9\-_]'), '');
|
||||||
|
final inviteData = base64UrlNoPadDecode(inviteDataBase64);
|
||||||
|
|
||||||
|
await validateInviteData(inviteData: inviteData);
|
||||||
|
}
|
||||||
|
|
||||||
|
void onValidationCancelled() {
|
||||||
|
_pasteTextController.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void onValidationSuccess() {
|
||||||
|
//_pasteTextController.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
void onValidationFailed() {
|
||||||
|
_pasteTextController.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool inviteControlIsValid() => _pasteTextController.text.isNotEmpty;
|
||||||
|
|
||||||
|
Widget buildInviteControl(
|
||||||
|
BuildContext context,
|
||||||
|
InviteDialogState dialogState,
|
||||||
|
Future<void> Function({required Uint8List inviteData})
|
||||||
|
validateInviteData) {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
final scale = theme.extension<ScaleScheme>()!;
|
||||||
|
//final textTheme = theme.textTheme;
|
||||||
|
//final height = MediaQuery.of(context).size.height;
|
||||||
|
|
||||||
|
final monoStyle = TextStyle(
|
||||||
|
fontFamily: 'Source Code Pro',
|
||||||
|
fontSize: 11,
|
||||||
|
color: scale.primaryScale.text,
|
||||||
|
);
|
||||||
|
|
||||||
|
return Column(mainAxisSize: MainAxisSize.min, children: [
|
||||||
|
Text(
|
||||||
|
translate('paste_invite_dialog.paste_invite_here'),
|
||||||
|
).paddingLTRB(0, 0, 0, 8),
|
||||||
|
Container(
|
||||||
|
constraints: const BoxConstraints(maxHeight: 200),
|
||||||
|
child: TextField(
|
||||||
|
enabled: !dialogState.isValidating,
|
||||||
|
onChanged: (text) async =>
|
||||||
|
_onPasteChanged(text, validateInviteData),
|
||||||
|
style: monoStyle,
|
||||||
|
keyboardType: TextInputType.multiline,
|
||||||
|
maxLines: null,
|
||||||
|
controller: _pasteTextController,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
border: OutlineInputBorder(),
|
||||||
|
hintText: '--- BEGIN VEILIDCHAT CONTACT INVITE ----\n'
|
||||||
|
'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\n'
|
||||||
|
'---- END VEILIDCHAT CONTACT INVITE -----\n',
|
||||||
|
//labelText: translate('paste_invite_dialog.paste')
|
||||||
|
),
|
||||||
|
)).paddingLTRB(0, 0, 0, 8)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
// ignore: prefer_expression_function_bodies
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return InviteDialog(
|
||||||
|
onValidationCancelled: onValidationCancelled,
|
||||||
|
onValidationSuccess: onValidationSuccess,
|
||||||
|
onValidationFailed: onValidationFailed,
|
||||||
|
inviteControlIsValid: inviteControlIsValid,
|
||||||
|
buildInviteControl: buildInviteControl);
|
||||||
|
}
|
||||||
|
}
|
395
lib/contact_invitation/views/scan_invite_dialog.dart
Normal file
395
lib/contact_invitation/views/scan_invite_dialog.dart
Normal file
|
@ -0,0 +1,395 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:io';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:awesome_extensions/awesome_extensions.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/scheduler.dart';
|
||||||
|
import 'package:flutter_translate/flutter_translate.dart';
|
||||||
|
import 'package:image/image.dart' as img;
|
||||||
|
import 'package:mobile_scanner/mobile_scanner.dart';
|
||||||
|
import 'package:pasteboard/pasteboard.dart';
|
||||||
|
import 'package:zxing2/qrcode.dart';
|
||||||
|
|
||||||
|
import '../../theme/theme.dart';
|
||||||
|
import '../../tools/tools.dart';
|
||||||
|
import 'invite_dialog.dart';
|
||||||
|
|
||||||
|
class BarcodeOverlay extends CustomPainter {
|
||||||
|
BarcodeOverlay({
|
||||||
|
required this.barcode,
|
||||||
|
required this.arguments,
|
||||||
|
required this.boxFit,
|
||||||
|
required this.capture,
|
||||||
|
});
|
||||||
|
|
||||||
|
final BarcodeCapture capture;
|
||||||
|
final Barcode barcode;
|
||||||
|
final MobileScannerArguments arguments;
|
||||||
|
final BoxFit boxFit;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void paint(Canvas canvas, Size size) {
|
||||||
|
final adjustedSize = applyBoxFit(boxFit, arguments.size, size);
|
||||||
|
|
||||||
|
var verticalPadding = size.height - adjustedSize.destination.height;
|
||||||
|
var horizontalPadding = size.width - adjustedSize.destination.width;
|
||||||
|
if (verticalPadding > 0) {
|
||||||
|
verticalPadding = verticalPadding / 2;
|
||||||
|
} else {
|
||||||
|
verticalPadding = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (horizontalPadding > 0) {
|
||||||
|
horizontalPadding = horizontalPadding / 2;
|
||||||
|
} else {
|
||||||
|
horizontalPadding = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
final ratioWidth = (Platform.isIOS ? capture.width : arguments.size.width) /
|
||||||
|
adjustedSize.destination.width;
|
||||||
|
final ratioHeight =
|
||||||
|
(Platform.isIOS ? capture.height : arguments.size.height) /
|
||||||
|
adjustedSize.destination.height;
|
||||||
|
|
||||||
|
final adjustedOffset = <Offset>[];
|
||||||
|
for (final offset in barcode.corners) {
|
||||||
|
adjustedOffset.add(
|
||||||
|
Offset(
|
||||||
|
offset.dx / ratioWidth + horizontalPadding,
|
||||||
|
offset.dy / ratioHeight + verticalPadding,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
final cutoutPath = Path()..addPolygon(adjustedOffset, true);
|
||||||
|
|
||||||
|
final backgroundPaint = Paint()
|
||||||
|
..color = Colors.red.withOpacity(0.3)
|
||||||
|
..style = PaintingStyle.fill
|
||||||
|
..blendMode = BlendMode.dstOut;
|
||||||
|
|
||||||
|
canvas.drawPath(cutoutPath, backgroundPaint);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ScannerOverlay extends CustomPainter {
|
||||||
|
ScannerOverlay(this.scanWindow);
|
||||||
|
|
||||||
|
final Rect scanWindow;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void paint(Canvas canvas, Size size) {
|
||||||
|
final backgroundPath = Path()..addRect(Rect.largest);
|
||||||
|
final cutoutPath = Path()..addRect(scanWindow);
|
||||||
|
|
||||||
|
final backgroundPaint = Paint()
|
||||||
|
..color = Colors.black.withOpacity(0.5)
|
||||||
|
..style = PaintingStyle.fill
|
||||||
|
..blendMode = BlendMode.dstOut;
|
||||||
|
|
||||||
|
final backgroundWithCutout = Path.combine(
|
||||||
|
PathOperation.difference,
|
||||||
|
backgroundPath,
|
||||||
|
cutoutPath,
|
||||||
|
);
|
||||||
|
canvas.drawPath(backgroundWithCutout, backgroundPaint);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
|
||||||
|
}
|
||||||
|
|
||||||
|
class ScanInviteDialog extends StatefulWidget {
|
||||||
|
const ScanInviteDialog({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
ScanInviteDialogState createState() => ScanInviteDialogState();
|
||||||
|
|
||||||
|
static Future<void> show(BuildContext context) async {
|
||||||
|
await showStyledDialog<void>(
|
||||||
|
context: context,
|
||||||
|
title: translate('scan_invite_dialog.title'),
|
||||||
|
child: const ScanInviteDialog());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ScanInviteDialogState extends State<ScanInviteDialog> {
|
||||||
|
bool scanned = false;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
void onValidationCancelled() {
|
||||||
|
setState(() {
|
||||||
|
scanned = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void onValidationSuccess() {}
|
||||||
|
void onValidationFailed() {
|
||||||
|
setState(() {
|
||||||
|
scanned = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
bool inviteControlIsValid() => false; // _pasteTextController.text.isNotEmpty;
|
||||||
|
|
||||||
|
Future<Uint8List?> scanQRImage(BuildContext context) async {
|
||||||
|
final theme = Theme.of(context);
|
||||||
|
//final textTheme = theme.textTheme;
|
||||||
|
final scale = theme.extension<ScaleScheme>()!;
|
||||||
|
final windowSize = MediaQuery.of(context).size;
|
||||||
|
//final maxDialogWidth = min(windowSize.width - 64.0, 800.0 - 64.0);
|
||||||
|
//final maxDialogHeight = windowSize.height - 64.0;
|
||||||
|
|
||||||
|
final scanWindow = Rect.fromCenter(
|
||||||
|
center: MediaQuery.of(context).size.center(Offset.zero),
|
||||||
|
width: 200,
|
||||||
|
height: 200,
|
||||||
|
);
|
||||||
|
|
||||||
|
final cameraController = MobileScannerController();
|
||||||
|
try {
|
||||||
|
return showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (context) => Stack(
|
||||||
|
fit: StackFit.expand,
|
||||||
|
children: [
|
||||||
|
MobileScanner(
|
||||||
|
fit: BoxFit.contain,
|
||||||
|
scanWindow: scanWindow,
|
||||||
|
controller: cameraController,
|
||||||
|
errorBuilder: (context, error, child) =>
|
||||||
|
ScannerErrorWidget(error: error),
|
||||||
|
onDetect: (c) {
|
||||||
|
final barcode = c.barcodes.firstOrNull;
|
||||||
|
|
||||||
|
final barcodeBytes = barcode?.rawBytes;
|
||||||
|
if (barcodeBytes != null) {
|
||||||
|
cameraController.dispose();
|
||||||
|
Navigator.pop(context, barcodeBytes);
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
CustomPaint(
|
||||||
|
painter: ScannerOverlay(scanWindow),
|
||||||
|
),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.bottomCenter,
|
||||||
|
child: Container(
|
||||||
|
alignment: Alignment.bottomCenter,
|
||||||
|
height: 100,
|
||||||
|
color: Colors.black.withOpacity(0.4),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
|
children: [
|
||||||
|
IconButton(
|
||||||
|
color: Colors.white,
|
||||||
|
icon: ValueListenableBuilder(
|
||||||
|
valueListenable: cameraController.torchState,
|
||||||
|
builder: (context, state, child) {
|
||||||
|
switch (state) {
|
||||||
|
case TorchState.off:
|
||||||
|
return Icon(Icons.flash_off,
|
||||||
|
color:
|
||||||
|
scale.grayScale.subtleBackground);
|
||||||
|
case TorchState.on:
|
||||||
|
return Icon(Icons.flash_on,
|
||||||
|
color: scale.primaryScale.background);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
iconSize: 32,
|
||||||
|
onPressed: cameraController.toggleTorch,
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
width: windowSize.width - 120,
|
||||||
|
height: 50,
|
||||||
|
child: FittedBox(
|
||||||
|
child: Text(
|
||||||
|
translate('scan_invite_dialog.instructions'),
|
||||||
|
overflow: TextOverflow.fade,
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.labelLarge!
|
||||||
|
.copyWith(color: Colors.white),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
color: Colors.white,
|
||||||
|
icon: ValueListenableBuilder(
|
||||||
|
valueListenable:
|
||||||
|
cameraController.cameraFacingState,
|
||||||
|
builder: (context, state, child) {
|
||||||
|
switch (state) {
|
||||||
|
case CameraFacing.front:
|
||||||
|
return const Icon(Icons.camera_front);
|
||||||
|
case CameraFacing.back:
|
||||||
|
return const Icon(Icons.camera_rear);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
iconSize: 32,
|
||||||
|
onPressed: cameraController.switchCamera,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Align(
|
||||||
|
alignment: Alignment.topRight,
|
||||||
|
child: IconButton(
|
||||||
|
color: Colors.white,
|
||||||
|
icon: Icon(Icons.close,
|
||||||
|
color: scale.grayScale.background),
|
||||||
|
iconSize: 32,
|
||||||
|
onPressed: () => {
|
||||||
|
SchedulerBinding.instance
|
||||||
|
.addPostFrameCallback((_) {
|
||||||
|
cameraController.dispose();
|
||||||
|
Navigator.pop(context, null);
|
||||||
|
})
|
||||||
|
})),
|
||||||
|
],
|
||||||
|
));
|
||||||
|
} on MobileScannerException catch (e) {
|
||||||
|
if (e.errorCode == MobileScannerErrorCode.permissionDenied) {
|
||||||
|
showErrorToast(
|
||||||
|
context, translate('scan_invite_dialog.permission_error'));
|
||||||
|
} else {
|
||||||
|
showErrorToast(context, translate('scan_invite_dialog.error'));
|
||||||
|
}
|
||||||
|
} on Exception catch (_) {
|
||||||
|
showErrorToast(context, translate('scan_invite_dialog.error'));
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<Uint8List?> pasteQRImage(BuildContext context) async {
|
||||||
|
final imageBytes = await Pasteboard.image;
|
||||||
|
if (imageBytes == null) {
|
||||||
|
if (context.mounted) {
|
||||||
|
showErrorToast(context, translate('scan_invite_dialog.not_an_image'));
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
final image = img.decodeImage(imageBytes);
|
||||||
|
if (image == null) {
|
||||||
|
if (context.mounted) {
|
||||||
|
showErrorToast(
|
||||||
|
context, translate('scan_invite_dialog.could_not_decode_image'));
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
final source = RGBLuminanceSource(
|
||||||
|
image.width,
|
||||||
|
image.height,
|
||||||
|
image
|
||||||
|
.convert(numChannels: 4)
|
||||||
|
.getBytes(order: img.ChannelOrder.abgr)
|
||||||
|
.buffer
|
||||||
|
.asInt32List());
|
||||||
|
final bitmap = BinaryBitmap(HybridBinarizer(source));
|
||||||
|
|
||||||
|
final reader = QRCodeReader();
|
||||||
|
final result = reader.decode(bitmap);
|
||||||
|
|
||||||
|
final segs = result.resultMetadata[ResultMetadataType.byteSegments]!
|
||||||
|
as List<Int8List>;
|
||||||
|
return Uint8List.fromList(segs[0].toList());
|
||||||
|
} on Exception catch (_) {
|
||||||
|
if (context.mounted) {
|
||||||
|
showErrorToast(
|
||||||
|
context, translate('scan_invite_dialog.not_a_valid_qr_code'));
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget buildInviteControl(
|
||||||
|
BuildContext context,
|
||||||
|
InviteDialogState dialogState,
|
||||||
|
Future<void> Function({required Uint8List inviteData})
|
||||||
|
validateInviteData) {
|
||||||
|
//final theme = Theme.of(context);
|
||||||
|
//final scale = theme.extension<ScaleScheme>()!;
|
||||||
|
//final textTheme = theme.textTheme;
|
||||||
|
//final height = MediaQuery.of(context).size.height;
|
||||||
|
|
||||||
|
if (isiOS || isAndroid) {
|
||||||
|
return Column(mainAxisSize: MainAxisSize.min, children: [
|
||||||
|
if (!scanned)
|
||||||
|
Text(
|
||||||
|
translate('scan_invite_dialog.scan_qr_here'),
|
||||||
|
).paddingLTRB(0, 0, 0, 8),
|
||||||
|
if (!scanned)
|
||||||
|
Container(
|
||||||
|
constraints: const BoxConstraints(maxHeight: 200),
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed: dialogState.isValidating
|
||||||
|
? null
|
||||||
|
: () async {
|
||||||
|
final inviteData = await scanQRImage(context);
|
||||||
|
if (inviteData != null) {
|
||||||
|
setState(() {
|
||||||
|
scanned = true;
|
||||||
|
});
|
||||||
|
await validateInviteData(inviteData: inviteData);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Text(translate('scan_invite_dialog.scan'))),
|
||||||
|
).paddingLTRB(0, 0, 0, 8)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
return Column(mainAxisSize: MainAxisSize.min, children: [
|
||||||
|
if (!scanned)
|
||||||
|
Text(
|
||||||
|
translate('scan_invite_dialog.paste_qr_here'),
|
||||||
|
).paddingLTRB(0, 0, 0, 8),
|
||||||
|
if (!scanned)
|
||||||
|
Container(
|
||||||
|
constraints: const BoxConstraints(maxHeight: 200),
|
||||||
|
child: ElevatedButton(
|
||||||
|
onPressed: dialogState.isValidating
|
||||||
|
? null
|
||||||
|
: () async {
|
||||||
|
final inviteData = await pasteQRImage(context);
|
||||||
|
if (inviteData != null) {
|
||||||
|
await validateInviteData(inviteData: inviteData);
|
||||||
|
setState(() {
|
||||||
|
scanned = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Text(translate('scan_invite_dialog.paste'))),
|
||||||
|
).paddingLTRB(0, 0, 0, 8)
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
// ignore: prefer_expression_function_bodies
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return InviteDialog(
|
||||||
|
onValidationCancelled: onValidationCancelled,
|
||||||
|
onValidationSuccess: onValidationSuccess,
|
||||||
|
onValidationFailed: onValidationFailed,
|
||||||
|
inviteControlIsValid: inviteControlIsValid,
|
||||||
|
buildInviteControl: buildInviteControl);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
|
||||||
|
super.debugFillProperties(properties);
|
||||||
|
properties.add(DiagnosticsProperty<bool>('scanned', scanned));
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,19 +5,14 @@ import 'package:awesome_extensions/awesome_extensions.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
import 'package:flutter_translate/flutter_translate.dart';
|
import 'package:flutter_translate/flutter_translate.dart';
|
||||||
|
import 'package:veilid_support/veilid_support.dart';
|
||||||
|
|
||||||
import '../entities/local_account.dart';
|
import '../../account_manager/account_manager.dart';
|
||||||
import '../providers/account.dart';
|
import '../../tools/tools.dart';
|
||||||
import '../providers/contact_invite.dart';
|
|
||||||
import '../tools/tools.dart';
|
|
||||||
import '../veilid_support/veilid_support.dart';
|
|
||||||
import 'contact_invitation_display.dart';
|
import 'contact_invitation_display.dart';
|
||||||
import 'enter_password.dart';
|
|
||||||
import 'enter_pin.dart';
|
|
||||||
|
|
||||||
class SendInviteDialog extends ConsumerStatefulWidget {
|
class SendInviteDialog extends StatefulWidget {
|
||||||
const SendInviteDialog({super.key});
|
const SendInviteDialog({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -31,7 +26,7 @@ class SendInviteDialog extends ConsumerStatefulWidget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class SendInviteDialogState extends ConsumerState<SendInviteDialog> {
|
class SendInviteDialogState extends State<SendInviteDialog> {
|
||||||
final _messageTextController = TextEditingController(
|
final _messageTextController = TextEditingController(
|
||||||
text: translate('send_invite_dialog.connect_with_me'));
|
text: translate('send_invite_dialog.connect_with_me'));
|
||||||
|
|
||||||
|
@ -135,7 +130,8 @@ class SendInviteDialogState extends ConsumerState<SendInviteDialog> {
|
||||||
final navigator = Navigator.of(context);
|
final navigator = Navigator.of(context);
|
||||||
|
|
||||||
// Start generation
|
// Start generation
|
||||||
final activeAccountInfo = await ref.read(fetchActiveAccountProvider.future);
|
final activeAccountInfo =
|
||||||
|
await AccountRepository.instance.fetchActiveAccountInfo();
|
||||||
if (activeAccountInfo == null) {
|
if (activeAccountInfo == null) {
|
||||||
navigator.pop();
|
navigator.pop();
|
||||||
return;
|
return;
|
8
lib/contact_invitation/views/views.dart
Normal file
8
lib/contact_invitation/views/views.dart
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
export 'contact_invitation_display.dart';
|
||||||
|
export 'contact_invitation_item_widget.dart';
|
||||||
|
export 'contact_invitation_list_widget.dart';
|
||||||
|
export 'invite_dialog.dart';
|
||||||
|
export 'new_contact_invitation_bottom_sheet.dart';
|
||||||
|
export 'paste_invite_dialog.dart';
|
||||||
|
export 'scan_invite_dialog.dart';
|
||||||
|
export 'send_invite_dialog.dart';
|
1
lib/contacts/contacts.dart
Normal file
1
lib/contacts/contacts.dart
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export 'views/views.dart';
|
|
@ -1,24 +1,21 @@
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_animate/flutter_animate.dart';
|
import 'package:flutter_animate/flutter_animate.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
import 'package:flutter_slidable/flutter_slidable.dart';
|
import 'package:flutter_slidable/flutter_slidable.dart';
|
||||||
import 'package:flutter_translate/flutter_translate.dart';
|
import 'package:flutter_translate/flutter_translate.dart';
|
||||||
import '../proto/proto.dart' as proto;
|
import '../../proto/proto.dart' as proto;
|
||||||
import '../pages/main_pager/main_pager.dart';
|
import '../../theme/theme.dart';
|
||||||
import '../providers/account.dart';
|
|
||||||
import '../providers/chat.dart';
|
|
||||||
import '../providers/contact.dart';
|
|
||||||
import '../theme/theme.dart';
|
|
||||||
|
|
||||||
class ContactItemWidget extends ConsumerWidget {
|
class ContactItemWidget extends StatelessWidget {
|
||||||
const ContactItemWidget({required this.contact, super.key});
|
const ContactItemWidget({required this.contact, super.key});
|
||||||
|
|
||||||
final proto.Contact contact;
|
final proto.Contact contact;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
// ignore: prefer_expression_function_bodies
|
// ignore: prefer_expression_function_bodies
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(
|
||||||
|
BuildContext context,
|
||||||
|
) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
//final textTheme = theme.textTheme;
|
//final textTheme = theme.textTheme;
|
||||||
final scale = theme.extension<ScaleScheme>()!;
|
final scale = theme.extension<ScaleScheme>()!;
|
|
@ -1,15 +1,16 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
import 'package:flutter_translate/flutter_translate.dart';
|
import 'package:flutter_translate/flutter_translate.dart';
|
||||||
|
|
||||||
import '../tools/tools.dart';
|
import '../../theme/theme.dart';
|
||||||
|
|
||||||
class EmptyContactListWidget extends ConsumerWidget {
|
class EmptyContactListWidget extends StatelessWidget {
|
||||||
const EmptyContactListWidget({super.key});
|
const EmptyContactListWidget({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
// ignore: prefer_expression_function_bodies
|
// ignore: prefer_expression_function_bodies
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(
|
||||||
|
BuildContext context,
|
||||||
|
) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final textTheme = theme.textTheme;
|
final textTheme = theme.textTheme;
|
||||||
final scale = theme.extension<ScaleScheme>()!;
|
final scale = theme.extension<ScaleScheme>()!;
|
3
lib/contacts/views/views.dart
Normal file
3
lib/contacts/views/views.dart
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
export 'contact_item_widget.dart';
|
||||||
|
export 'contact_list_widget.dart';
|
||||||
|
export 'empty_contact_list_widget.dart';
|
|
@ -1,8 +1,7 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
|
|
||||||
import '../providers/window_control.dart';
|
import '../chat/chat.dart';
|
||||||
import 'home.dart';
|
import '../tools/tools.dart';
|
||||||
|
|
||||||
class ChatOnlyPage extends StatefulWidget {
|
class ChatOnlyPage extends StatefulWidget {
|
||||||
const ChatOnlyPage({super.key});
|
const ChatOnlyPage({super.key});
|
||||||
|
@ -11,7 +10,7 @@ class ChatOnlyPage extends StatefulWidget {
|
||||||
ChatOnlyPageState createState() => ChatOnlyPageState();
|
ChatOnlyPageState createState() => ChatOnlyPageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class ChatOnlyPageState extends ConsumerState<ChatOnlyPage>
|
class ChatOnlyPageState extends State<ChatOnlyPage>
|
||||||
with TickerProviderStateMixin {
|
with TickerProviderStateMixin {
|
||||||
final _unfocusNode = FocusNode();
|
final _unfocusNode = FocusNode();
|
||||||
|
|
||||||
|
@ -21,7 +20,7 @@ class ChatOnlyPageState extends ConsumerState<ChatOnlyPage>
|
||||||
|
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||||
setState(() {});
|
setState(() {});
|
||||||
await ref.read(windowControlProvider.notifier).changeWindowSetup(
|
await changeWindowSetup(
|
||||||
TitleBarStyle.normal, OrientationCapability.normal);
|
TitleBarStyle.normal, OrientationCapability.normal);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -33,13 +32,9 @@ class ChatOnlyPageState extends ConsumerState<ChatOnlyPage>
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) => SafeArea(
|
||||||
ref.watch(windowControlProvider);
|
|
||||||
|
|
||||||
return SafeArea(
|
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
onTap: () => FocusScope.of(context).requestFocus(_unfocusNode),
|
onTap: () => FocusScope.of(context).requestFocus(_unfocusNode),
|
||||||
child: HomePage.buildChatComponent(context, ref),
|
child: buildChatComponent(),
|
||||||
));
|
));
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
|
|
||||||
class ContactsPage extends ConsumerWidget {
|
class ContactsPage extends StatelessWidget {
|
||||||
const ContactsPage({super.key});
|
const ContactsPage({super.key});
|
||||||
static const path = '/contacts';
|
static const path = '/contacts';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) => const Scaffold(
|
Widget build(
|
||||||
|
BuildContext context,
|
||||||
|
) =>
|
||||||
|
const Scaffold(
|
||||||
body: Center(
|
body: Center(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
|
|
@ -9,7 +9,7 @@ import 'package:veilid_support/veilid_support.dart';
|
||||||
|
|
||||||
import '../../proto/proto.dart' as proto;
|
import '../../proto/proto.dart' as proto;
|
||||||
import '../account_manager/account_manager.dart';
|
import '../account_manager/account_manager.dart';
|
||||||
import '../account_manager/models/models.dart';
|
import '../chat/chat.dart';
|
||||||
import '../theme/theme.dart';
|
import '../theme/theme.dart';
|
||||||
import '../tools/tools.dart';
|
import '../tools/tools.dart';
|
||||||
import 'main_pager/main_pager.dart';
|
import 'main_pager/main_pager.dart';
|
||||||
|
@ -19,38 +19,6 @@ class HomePage extends StatefulWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
HomePageState createState() => HomePageState();
|
HomePageState createState() => HomePageState();
|
||||||
|
|
||||||
static Widget buildChatComponent() {
|
|
||||||
final contactList = ref.watch(fetchContactListProvider).asData?.value ??
|
|
||||||
const IListConst([]);
|
|
||||||
|
|
||||||
final activeChat = ref.watch(activeChatStateProvider);
|
|
||||||
if (activeChat == null) {
|
|
||||||
return const EmptyChatWidget();
|
|
||||||
}
|
|
||||||
|
|
||||||
final activeAccountInfo =
|
|
||||||
ref.watch(fetchActiveAccountProvider).asData?.value;
|
|
||||||
if (activeAccountInfo == null) {
|
|
||||||
return const EmptyChatWidget();
|
|
||||||
}
|
|
||||||
|
|
||||||
final activeChatContactIdx = contactList.indexWhere(
|
|
||||||
(c) =>
|
|
||||||
proto.TypedKeyProto.fromProto(c.remoteConversationRecordKey) ==
|
|
||||||
activeChat,
|
|
||||||
);
|
|
||||||
if (activeChatContactIdx == -1) {
|
|
||||||
ref.read(activeChatStateProvider.notifier).state = null;
|
|
||||||
return const EmptyChatWidget();
|
|
||||||
}
|
|
||||||
final activeChatContact = contactList[activeChatContactIdx];
|
|
||||||
|
|
||||||
return ChatComponent(
|
|
||||||
activeAccountInfo: activeAccountInfo,
|
|
||||||
activeChat: activeChat,
|
|
||||||
activeChatContact: activeChatContact);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class HomePageState extends State<HomePage> with TickerProviderStateMixin {
|
class HomePageState extends State<HomePage> with TickerProviderStateMixin {
|
||||||
|
@ -196,7 +164,7 @@ class HomePageState extends State<HomePage> with TickerProviderStateMixin {
|
||||||
Widget buildTabletLeftPane() =>
|
Widget buildTabletLeftPane() =>
|
||||||
Material(color: Colors.transparent, child: buildUserPanel());
|
Material(color: Colors.transparent, child: buildUserPanel());
|
||||||
|
|
||||||
Widget buildTabletRightPane() => HomePage.buildChatComponent();
|
Widget buildTabletRightPane() => buildChatComponent();
|
||||||
|
|
||||||
// ignore: prefer_expression_function_bodies
|
// ignore: prefer_expression_function_bodies
|
||||||
Widget buildTablet() => Builder(builder: (context) {
|
Widget buildTablet() => Builder(builder: (context) {
|
||||||
|
|
|
@ -4,20 +4,15 @@ import 'package:awesome_extensions/awesome_extensions.dart';
|
||||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
import 'package:flutter_translate/flutter_translate.dart';
|
import 'package:flutter_translate/flutter_translate.dart';
|
||||||
|
import 'package:veilid_support/veilid_support.dart';
|
||||||
|
|
||||||
import '../../../components/contact_invitation_list_widget.dart';
|
|
||||||
import '../../../components/contact_list_widget.dart';
|
|
||||||
import '../../../entities/local_account.dart';
|
|
||||||
import '../../../proto/proto.dart' as proto;
|
import '../../../proto/proto.dart' as proto;
|
||||||
import '../../providers/contact.dart';
|
import '../../account_manager/account_manager.dart';
|
||||||
import '../../providers/contact_invite.dart';
|
import '../../contact_invitation/contact_invitation.dart';
|
||||||
import '../../../theme/theme.dart';
|
import '../../contacts/contacts.dart';
|
||||||
import '../../../tools/tools.dart';
|
|
||||||
import '../../../../packages/veilid_support/veilid_support.dart';
|
|
||||||
|
|
||||||
class AccountPage extends ConsumerStatefulWidget {
|
class AccountPage extends StatefulWidget {
|
||||||
const AccountPage({
|
const AccountPage({
|
||||||
required this.localAccounts,
|
required this.localAccounts,
|
||||||
required this.activeUserLogin,
|
required this.activeUserLogin,
|
||||||
|
@ -41,7 +36,7 @@ class AccountPage extends ConsumerStatefulWidget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class AccountPageState extends ConsumerState<AccountPage> {
|
class AccountPageState extends State<AccountPage> {
|
||||||
final _unfocusNode = FocusNode();
|
final _unfocusNode = FocusNode();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
|
|
||||||
class BottomSheetActionButton extends ConsumerStatefulWidget {
|
class BottomSheetActionButton extends StatefulWidget {
|
||||||
const BottomSheetActionButton(
|
const BottomSheetActionButton(
|
||||||
{required this.bottomSheetBuilder,
|
{required this.bottomSheetBuilder,
|
||||||
required this.builder,
|
required this.builder,
|
||||||
|
@ -32,8 +31,7 @@ class BottomSheetActionButton extends ConsumerStatefulWidget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class BottomSheetActionButtonState
|
class BottomSheetActionButtonState extends State<BottomSheetActionButton> {
|
||||||
extends ConsumerState<BottomSheetActionButton> {
|
|
||||||
bool _showFab = true;
|
bool _showFab = true;
|
||||||
|
|
||||||
@override
|
@override
|
|
@ -1,28 +1,19 @@
|
||||||
import 'package:awesome_extensions/awesome_extensions.dart';
|
import 'package:awesome_extensions/awesome_extensions.dart';
|
||||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
|
|
||||||
import '../../../components/chat_single_contact_list_widget.dart';
|
|
||||||
import '../../../components/empty_chat_list_widget.dart';
|
|
||||||
import '../../../entities/local_account.dart';
|
|
||||||
import '../../../proto/proto.dart' as proto;
|
import '../../../proto/proto.dart' as proto;
|
||||||
import '../../providers/account.dart';
|
import '../../account_manager/account_manager.dart';
|
||||||
import '../../providers/chat.dart';
|
import '../../tools/tools.dart';
|
||||||
import '../../providers/contact.dart';
|
|
||||||
import '../../../local_accounts/local_accounts.dart';
|
|
||||||
import '../../providers/logins.dart';
|
|
||||||
import '../../../tools/tools.dart';
|
|
||||||
import '../../../../packages/veilid_support/veilid_support.dart';
|
|
||||||
|
|
||||||
class ChatsPage extends ConsumerStatefulWidget {
|
class ChatsPage extends StatefulWidget {
|
||||||
const ChatsPage({super.key});
|
const ChatsPage({super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
ChatsPageState createState() => ChatsPageState();
|
ChatsPageState createState() => ChatsPageState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class ChatsPageState extends ConsumerState<ChatsPage> {
|
class ChatsPageState extends State<ChatsPage> {
|
||||||
final _unfocusNode = FocusNode();
|
final _unfocusNode = FocusNode();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -1,27 +1,23 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:awesome_extensions/awesome_extensions.dart';
|
|
||||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
import 'package:flutter/services.dart';
|
|
||||||
import 'package:flutter_animate/flutter_animate.dart';
|
import 'package:flutter_animate/flutter_animate.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
import 'package:flutter_translate/flutter_translate.dart';
|
import 'package:flutter_translate/flutter_translate.dart';
|
||||||
import 'package:preload_page_view/preload_page_view.dart';
|
import 'package:preload_page_view/preload_page_view.dart';
|
||||||
import 'package:stylish_bottom_bar/model/bar_items.dart';
|
import 'package:stylish_bottom_bar/model/bar_items.dart';
|
||||||
import 'package:stylish_bottom_bar/stylish_bottom_bar.dart';
|
import 'package:stylish_bottom_bar/stylish_bottom_bar.dart';
|
||||||
|
import 'package:veilid_support/veilid_support.dart';
|
||||||
|
|
||||||
import '../../../components/bottom_sheet_action_button.dart';
|
|
||||||
import '../../../components/paste_invite_dialog.dart';
|
|
||||||
import '../../../components/scan_invite_dialog.dart';
|
|
||||||
import '../../../components/send_invite_dialog.dart';
|
|
||||||
import '../../../entities/local_account.dart';
|
|
||||||
import '../../../proto/proto.dart' as proto;
|
import '../../../proto/proto.dart' as proto;
|
||||||
import '../../../tools/tools.dart';
|
import '../../../tools/tools.dart';
|
||||||
import '../../../../packages/veilid_support/veilid_support.dart';
|
import '../../account_manager/account_manager.dart';
|
||||||
|
import '../../contact_invitation/contact_invitation.dart';
|
||||||
|
import '../../theme/theme.dart';
|
||||||
import 'account.dart';
|
import 'account.dart';
|
||||||
|
import 'bottom_sheet_action_button.dart';
|
||||||
import 'chats.dart';
|
import 'chats.dart';
|
||||||
|
|
||||||
class MainPager extends StatefulWidget {
|
class MainPager extends StatefulWidget {
|
||||||
|
@ -50,8 +46,7 @@ class MainPager extends StatefulWidget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class MainPagerState extends ConsumerState<MainPager>
|
class MainPagerState extends State<MainPager> with TickerProviderStateMixin {
|
||||||
with TickerProviderStateMixin {
|
|
||||||
//////////////////////////////////////////////////////////////////
|
//////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
final _unfocusNode = FocusNode();
|
final _unfocusNode = FocusNode();
|
||||||
|
@ -151,64 +146,6 @@ class MainPagerState extends ConsumerState<MainPager>
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _newContactInvitationBottomSheetBuilder(
|
|
||||||
// ignore: prefer_expression_function_bodies
|
|
||||||
BuildContext context) {
|
|
||||||
final theme = Theme.of(context);
|
|
||||||
final textTheme = theme.textTheme;
|
|
||||||
final scale = theme.extension<ScaleScheme>()!;
|
|
||||||
|
|
||||||
return KeyboardListener(
|
|
||||||
focusNode: FocusNode(),
|
|
||||||
onKeyEvent: (ke) {
|
|
||||||
if (ke.logicalKey == LogicalKeyboardKey.escape) {
|
|
||||||
Navigator.pop(context);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: SizedBox(
|
|
||||||
height: 200,
|
|
||||||
child: Column(children: [
|
|
||||||
Text(translate('accounts_menu.invite_contact'),
|
|
||||||
style: textTheme.titleMedium)
|
|
||||||
.paddingAll(8),
|
|
||||||
Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [
|
|
||||||
Column(children: [
|
|
||||||
IconButton(
|
|
||||||
onPressed: () async {
|
|
||||||
Navigator.pop(context);
|
|
||||||
await SendInviteDialog.show(context);
|
|
||||||
},
|
|
||||||
iconSize: 64,
|
|
||||||
icon: const Icon(Icons.contact_page),
|
|
||||||
color: scale.primaryScale.background),
|
|
||||||
Text(translate('accounts_menu.create_invite'))
|
|
||||||
]),
|
|
||||||
Column(children: [
|
|
||||||
IconButton(
|
|
||||||
onPressed: () async {
|
|
||||||
Navigator.pop(context);
|
|
||||||
await ScanInviteDialog.show(context);
|
|
||||||
},
|
|
||||||
iconSize: 64,
|
|
||||||
icon: const Icon(Icons.qr_code_scanner),
|
|
||||||
color: scale.primaryScale.background),
|
|
||||||
Text(translate('accounts_menu.scan_invite'))
|
|
||||||
]),
|
|
||||||
Column(children: [
|
|
||||||
IconButton(
|
|
||||||
onPressed: () async {
|
|
||||||
Navigator.pop(context);
|
|
||||||
await PasteInviteDialog.show(context);
|
|
||||||
},
|
|
||||||
iconSize: 64,
|
|
||||||
icon: const Icon(Icons.paste),
|
|
||||||
color: scale.primaryScale.background),
|
|
||||||
Text(translate('accounts_menu.paste_invite'))
|
|
||||||
])
|
|
||||||
]).expanded()
|
|
||||||
])));
|
|
||||||
}
|
|
||||||
|
|
||||||
// ignore: prefer_expression_function_bodies
|
// ignore: prefer_expression_function_bodies
|
||||||
Widget _onNewChatBottomSheetBuilder(BuildContext context) {
|
Widget _onNewChatBottomSheetBuilder(BuildContext context) {
|
||||||
return const SizedBox(
|
return const SizedBox(
|
||||||
|
@ -221,7 +158,7 @@ class MainPagerState extends ConsumerState<MainPager>
|
||||||
Widget _bottomSheetBuilder(BuildContext context) {
|
Widget _bottomSheetBuilder(BuildContext context) {
|
||||||
if (_currentPage == 0) {
|
if (_currentPage == 0) {
|
||||||
// New contact invitation
|
// New contact invitation
|
||||||
return _newContactInvitationBottomSheetBuilder(context);
|
return newContactInvitationBottomSheetBuilder(context);
|
||||||
} else if (_currentPage == 1) {
|
} else if (_currentPage == 1) {
|
||||||
// New chat
|
// New chat
|
||||||
return _onNewChatBottomSheetBuilder(context);
|
return _onNewChatBottomSheetBuilder(context);
|
||||||
|
|
|
@ -1,36 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
|
|
||||||
class EmptyChatWidget extends ConsumerWidget {
|
|
||||||
const EmptyChatWidget({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
// ignore: prefer_expression_function_bodies
|
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
|
||||||
//
|
|
||||||
|
|
||||||
return Container(
|
|
||||||
width: double.infinity,
|
|
||||||
height: double.infinity,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Theme.of(context).scaffoldBackgroundColor,
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Icon(
|
|
||||||
Icons.chat,
|
|
||||||
color: Theme.of(context).disabledColor,
|
|
||||||
size: 48,
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
'Say Something',
|
|
||||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
|
||||||
color: Theme.of(context).disabledColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,35 +0,0 @@
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
|
|
||||||
class NoContactWidget extends ConsumerWidget {
|
|
||||||
const NoContactWidget({super.key});
|
|
||||||
|
|
||||||
@override
|
|
||||||
// ignore: prefer_expression_function_bodies
|
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
|
||||||
//
|
|
||||||
return Container(
|
|
||||||
width: double.infinity,
|
|
||||||
height: double.infinity,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Theme.of(context).primaryColor,
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
|
||||||
children: [
|
|
||||||
Icon(
|
|
||||||
Icons.emoji_people_outlined,
|
|
||||||
color: Theme.of(context).disabledColor,
|
|
||||||
size: 48,
|
|
||||||
),
|
|
||||||
Text(
|
|
||||||
'Choose A Conversation To Chat',
|
|
||||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
|
||||||
color: Theme.of(context).disabledColor,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
import '../tools/tools.dart';
|
import '../tools/tools.dart';
|
||||||
import 'settings.dart';
|
import 'settings.dart';
|
||||||
|
|
||||||
xxx convert to non-asyncvalue based wrapper since there's always a default here
|
|
||||||
|
|
||||||
class PreferencesCubit extends StreamWrapperCubit<Preferences> {
|
class PreferencesCubit extends StreamWrapperCubit<Preferences> {
|
||||||
PreferencesCubit(PreferencesRepository repository)
|
PreferencesCubit(PreferencesRepository repository)
|
||||||
: super(repository.stream, defaultState: repository.value);
|
: super(repository.stream, defaultState: repository.value);
|
||||||
|
|
|
@ -1,17 +1,13 @@
|
||||||
import 'package:animated_theme_switcher/animated_theme_switcher.dart';
|
import 'package:animated_theme_switcher/animated_theme_switcher.dart';
|
||||||
import 'package:awesome_extensions/awesome_extensions.dart';
|
import 'package:awesome_extensions/awesome_extensions.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
||||||
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
import 'package:flutter_form_builder/flutter_form_builder.dart';
|
||||||
import 'package:flutter_translate/flutter_translate.dart';
|
import 'package:flutter_translate/flutter_translate.dart';
|
||||||
import 'package:veilid_support/veilid_support.dart';
|
|
||||||
|
|
||||||
import '../layout/default_app_bar.dart';
|
import '../layout/default_app_bar.dart';
|
||||||
import '../theme/theme.dart';
|
import '../theme/theme.dart';
|
||||||
|
import '../tools/tools.dart';
|
||||||
import '../veilid_processor/veilid_processor.dart';
|
import '../veilid_processor/veilid_processor.dart';
|
||||||
import 'preferences_cubit.dart';
|
|
||||||
import 'preferences_repository.dart';
|
|
||||||
import 'settings.dart';
|
import 'settings.dart';
|
||||||
|
|
||||||
class SettingsPage extends StatefulWidget {
|
class SettingsPage extends StatefulWidget {
|
||||||
|
@ -68,8 +64,8 @@ class SettingsPageState extends State<SettingsPage> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => BlocBuilder<PreferencesCubit,
|
Widget build(BuildContext context) => AsyncBlocBuilder<PreferencesCubit,
|
||||||
AsyncValue<Preferences>>(
|
Preferences>(
|
||||||
builder: (context, state) => ThemeSwitchingArea(
|
builder: (context, state) => ThemeSwitchingArea(
|
||||||
child: Scaffold(
|
child: Scaffold(
|
||||||
// resizeToAvoidBottomInset: false,
|
// resizeToAvoidBottomInset: false,
|
||||||
|
@ -94,14 +90,16 @@ class SettingsPageState extends State<SettingsPage> {
|
||||||
label:
|
label:
|
||||||
Text(translate('settings_page.color_theme'))),
|
Text(translate('settings_page.color_theme'))),
|
||||||
items: _getThemeDropdownItems(),
|
items: _getThemeDropdownItems(),
|
||||||
initialValue: themePreferences.colorPreference,
|
initialValue: state.themePreferences.colorPreference,
|
||||||
onChanged: (value) async {
|
onChanged: (value) async {
|
||||||
final newPrefs = themePreferences.copyWith(
|
final newPrefs = state.copyWith(
|
||||||
colorPreference: value as ColorPreference);
|
themePreferences: state.themePreferences
|
||||||
await themeService.save(newPrefs);
|
.copyWith(
|
||||||
|
colorPreference:
|
||||||
|
value as ColorPreference));
|
||||||
switcher.changeTheme(
|
switcher.changeTheme(
|
||||||
theme: themeService.get(newPrefs));
|
theme: newPrefs.themePreferences.themeData());
|
||||||
ref.invalidate(themeServiceProvider);
|
await PreferencesRepository.instance.set(newPrefs);
|
||||||
setState(() {});
|
setState(() {});
|
||||||
})),
|
})),
|
||||||
ThemeSwitcher.withTheme(
|
ThemeSwitcher.withTheme(
|
||||||
|
@ -111,15 +109,17 @@ class SettingsPageState extends State<SettingsPage> {
|
||||||
label: Text(
|
label: Text(
|
||||||
translate('settings_page.brightness_mode'))),
|
translate('settings_page.brightness_mode'))),
|
||||||
items: _getBrightnessDropdownItems(),
|
items: _getBrightnessDropdownItems(),
|
||||||
initialValue: themePreferences.brightnessPreference,
|
initialValue:
|
||||||
|
state.themePreferences.brightnessPreference,
|
||||||
onChanged: (value) async {
|
onChanged: (value) async {
|
||||||
final newPrefs = themePreferences.copyWith(
|
final newPrefs = state.copyWith(
|
||||||
|
themePreferences: state.themePreferences
|
||||||
|
.copyWith(
|
||||||
brightnessPreference:
|
brightnessPreference:
|
||||||
value as BrightnessPreference);
|
value as BrightnessPreference));
|
||||||
await themeService.save(newPrefs);
|
|
||||||
switcher.changeTheme(
|
switcher.changeTheme(
|
||||||
theme: themeService.get(newPrefs));
|
theme: newPrefs.themePreferences.themeData());
|
||||||
ref.invalidate(themeServiceProvider);
|
await PreferencesRepository.instance.set(newPrefs);
|
||||||
setState(() {});
|
setState(() {});
|
||||||
})),
|
})),
|
||||||
],
|
],
|
||||||
|
|
|
@ -2,12 +2,11 @@ import 'package:awesome_extensions/awesome_extensions.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
import 'package:flutter_translate/flutter_translate.dart';
|
import 'package:flutter_translate/flutter_translate.dart';
|
||||||
|
|
||||||
import '../tools/tools.dart';
|
import '../theme/theme.dart';
|
||||||
|
|
||||||
class EnterPasswordDialog extends ConsumerStatefulWidget {
|
class EnterPasswordDialog extends StatefulWidget {
|
||||||
const EnterPasswordDialog({
|
const EnterPasswordDialog({
|
||||||
this.matchPass,
|
this.matchPass,
|
||||||
this.description,
|
this.description,
|
||||||
|
@ -29,7 +28,7 @@ class EnterPasswordDialog extends ConsumerStatefulWidget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class EnterPasswordDialogState extends ConsumerState<EnterPasswordDialog> {
|
class EnterPasswordDialogState extends State<EnterPasswordDialog> {
|
||||||
final passwordController = TextEditingController();
|
final passwordController = TextEditingController();
|
||||||
final focusNode = FocusNode();
|
final focusNode = FocusNode();
|
||||||
final formKey = GlobalKey<FormState>();
|
final formKey = GlobalKey<FormState>();
|
|
@ -2,13 +2,12 @@ import 'package:awesome_extensions/awesome_extensions.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
import 'package:flutter_translate/flutter_translate.dart';
|
import 'package:flutter_translate/flutter_translate.dart';
|
||||||
import 'package:pinput/pinput.dart';
|
import 'package:pinput/pinput.dart';
|
||||||
|
|
||||||
import '../tools/tools.dart';
|
import '../theme/theme.dart';
|
||||||
|
|
||||||
class EnterPinDialog extends ConsumerStatefulWidget {
|
class EnterPinDialog extends StatefulWidget {
|
||||||
const EnterPinDialog({
|
const EnterPinDialog({
|
||||||
required this.reenter,
|
required this.reenter,
|
||||||
required this.description,
|
required this.description,
|
||||||
|
@ -30,7 +29,7 @@ class EnterPinDialog extends ConsumerStatefulWidget {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class EnterPinDialogState extends ConsumerState<EnterPinDialog> {
|
class EnterPinDialogState extends State<EnterPinDialog> {
|
||||||
final pinController = TextEditingController();
|
final pinController = TextEditingController();
|
||||||
final focusNode = FocusNode();
|
final focusNode = FocusNode();
|
||||||
final formKey = GlobalKey<FormState>();
|
final formKey = GlobalKey<FormState>();
|
|
@ -1,10 +1,13 @@
|
||||||
export 'animations.dart';
|
export 'animations.dart';
|
||||||
|
export 'enter_password.dart';
|
||||||
|
export 'enter_pin.dart';
|
||||||
export 'loggy.dart';
|
export 'loggy.dart';
|
||||||
export 'phono_byte.dart';
|
export 'phono_byte.dart';
|
||||||
export 'responsive.dart';
|
export 'responsive.dart';
|
||||||
export 'scanner_error_widget.dart';
|
export 'scanner_error_widget.dart';
|
||||||
export 'shared_preferences.dart';
|
export 'shared_preferences.dart';
|
||||||
export 'state_logger.dart';
|
export 'state_logger.dart';
|
||||||
|
export 'stream_listenable.dart';
|
||||||
export 'stream_wrapper_cubit.dart';
|
export 'stream_wrapper_cubit.dart';
|
||||||
export 'widget_helpers.dart';
|
export 'widget_helpers.dart';
|
||||||
export 'window_control.dart';
|
export 'window_control.dart';
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import 'package:awesome_extensions/awesome_extensions.dart';
|
import 'package:awesome_extensions/awesome_extensions.dart';
|
||||||
import 'package:blurry_modal_progress_hud/blurry_modal_progress_hud.dart';
|
import 'package:blurry_modal_progress_hud/blurry_modal_progress_hud.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_spinkit/flutter_spinkit.dart';
|
import 'package:flutter_spinkit/flutter_spinkit.dart';
|
||||||
import 'package:flutter_translate/flutter_translate.dart';
|
import 'package:flutter_translate/flutter_translate.dart';
|
||||||
import 'package:motion_toast/motion_toast.dart';
|
import 'package:motion_toast/motion_toast.dart';
|
||||||
|
@ -60,6 +61,22 @@ extension AsyncValueBuilderExt<T> on AsyncValue<T> {
|
||||||
asyncValueBuilder<T>(this, builder);
|
asyncValueBuilder<T>(this, builder);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class AsyncBlocBuilder<B extends StateStreamable<AsyncValue<S>>, S>
|
||||||
|
extends BlocBuilder<B, AsyncValue<S>> {
|
||||||
|
AsyncBlocBuilder({
|
||||||
|
required BlocWidgetBuilder<S> builder,
|
||||||
|
Widget Function(BuildContext)? loading,
|
||||||
|
Widget Function(BuildContext, Object, StackTrace?)? error,
|
||||||
|
super.key,
|
||||||
|
super.bloc,
|
||||||
|
super.buildWhen,
|
||||||
|
}) : super(
|
||||||
|
builder: (context, state) => state.when(
|
||||||
|
loading: () => (loading ?? waitingPage)(context),
|
||||||
|
error: (e, st) => (error ?? errorPage)(context, e, st),
|
||||||
|
data: (d) => builder(context, d)));
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> showErrorModal(
|
Future<void> showErrorModal(
|
||||||
BuildContext context, String title, String text) async {
|
BuildContext context, String title, String text) async {
|
||||||
await QuickAlert.show(
|
await QuickAlert.show(
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue