mirror of
https://gitlab.com/veilid/veilidchat.git
synced 2025-02-02 02:54:59 -05:00
more refactor
This commit is contained in:
parent
b83aa3a64b
commit
c7b541c643
@ -1,3 +1,4 @@
|
||||
export 'cubit/cubit.dart';
|
||||
export 'models/models.dart';
|
||||
export 'repository/repository.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_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../proto/proto.dart' as proto;
|
||||
import '../providers/account.dart';
|
||||
import '../providers/chat.dart';
|
||||
import '../providers/conversation.dart';
|
||||
import '../tools/tools.dart';
|
||||
import '../veilid_init.dart';
|
||||
import '../veilid_support/veilid_support.dart';
|
||||
import '../../old_to_refactor/proto/proto.dart' as proto;
|
||||
import '../../old_to_refactor/providers/account.dart';
|
||||
import '../../old_to_refactor/providers/chat.dart';
|
||||
import '../../old_to_refactor/providers/conversation.dart';
|
||||
import '../../old_to_refactor/tools/tools.dart';
|
||||
import '../../old_to_refactor/veilid_init.dart';
|
||||
import '../../old_to_refactor/veilid_support/veilid_support.dart';
|
||||
|
||||
class ChatComponent extends ConsumerStatefulWidget {
|
||||
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_riverpod/flutter_riverpod.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});
|
||||
|
||||
@override
|
||||
// ignore: prefer_expression_function_bodies
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
final textTheme = theme.textTheme;
|
||||
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_translate/flutter_translate.dart';
|
||||
|
||||
import '../tools/tools.dart';
|
||||
import '../veilid_support/veilid_support.dart';
|
||||
import 'invite_dialog.dart';
|
||||
import '../old_to_refactor/tools/tools.dart';
|
||||
import '../old_to_refactor/veilid_support/veilid_support.dart';
|
||||
import 'views/invite_dialog.dart';
|
||||
|
||||
class PasteInviteDialog extends ConsumerStatefulWidget {
|
||||
const PasteInviteDialog({super.key});
|
@ -13,8 +13,8 @@ import 'package:mobile_scanner/mobile_scanner.dart';
|
||||
import 'package:pasteboard/pasteboard.dart';
|
||||
import 'package:zxing2/qrcode.dart';
|
||||
|
||||
import '../tools/tools.dart';
|
||||
import 'invite_dialog.dart';
|
||||
import '../old_to_refactor/tools/tools.dart';
|
||||
import 'views/invite_dialog.dart';
|
||||
|
||||
class BarcodeOverlay extends CustomPainter {
|
||||
BarcodeOverlay({
|
@ -6,14 +6,13 @@ import 'package:basic_utils/basic_utils.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_translate/flutter_translate.dart';
|
||||
import 'package:qr_flutter/qr_flutter.dart';
|
||||
import 'package:veilid_support/veilid_support.dart';
|
||||
|
||||
import '../tools/tools.dart';
|
||||
import '../veilid_support/veilid_support.dart';
|
||||
import '../../tools/tools.dart';
|
||||
|
||||
class ContactInvitationDisplayDialog extends ConsumerStatefulWidget {
|
||||
class ContactInvitationDisplayDialog extends StatefulWidget {
|
||||
const ContactInvitationDisplayDialog({
|
||||
required this.name,
|
||||
required this.message,
|
||||
@ -40,7 +39,7 @@ class ContactInvitationDisplayDialog extends ConsumerStatefulWidget {
|
||||
}
|
||||
|
||||
class ContactInvitationDisplayDialogState
|
||||
extends ConsumerState<ContactInvitationDisplayDialog> {
|
||||
extends State<ContactInvitationDisplayDialog> {
|
||||
final focusNode = FocusNode();
|
||||
final formKey = GlobalKey<FormState>();
|
||||
late final AutoDisposeFutureProvider<Uint8List?> _generateFutureProvider;
|
@ -1,15 +1,12 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_slidable/flutter_slidable.dart';
|
||||
import 'package:flutter_translate/flutter_translate.dart';
|
||||
import '../proto/proto.dart' as proto;
|
||||
import '../providers/account.dart';
|
||||
import '../providers/contact_invite.dart';
|
||||
import '../tools/tools.dart';
|
||||
import '../../proto/proto.dart' as proto;
|
||||
import '../../theme/theme.dart';
|
||||
import 'contact_invitation_display.dart';
|
||||
|
||||
class ContactInvitationItemWidget extends ConsumerWidget {
|
||||
class ContactInvitationItemWidget extends StatelessWidget {
|
||||
const ContactInvitationItemWidget(
|
||||
{required this.contactInvitationRecord, super.key});
|
||||
|
||||
@ -24,7 +21,7 @@ class ContactInvitationItemWidget extends ConsumerWidget {
|
||||
|
||||
@override
|
||||
// ignore: prefer_expression_function_bodies
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
//final textTheme = theme.textTheme;
|
||||
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:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../proto/proto.dart' as proto;
|
||||
import '../tools/tools.dart';
|
||||
import '../../proto/proto.dart' as proto;
|
||||
import '../../theme/theme.dart';
|
||||
import 'contact_invitation_item_widget.dart';
|
||||
|
||||
class ContactInvitationListWidget extends ConsumerStatefulWidget {
|
||||
class ContactInvitationListWidget extends StatefulWidget {
|
||||
const ContactInvitationListWidget({
|
||||
required this.contactInvitationRecordList,
|
||||
super.key,
|
||||
@ -28,7 +27,7 @@ class ContactInvitationListWidget extends ConsumerStatefulWidget {
|
||||
}
|
||||
|
||||
class ContactInvitationListWidgetState
|
||||
extends ConsumerState<ContactInvitationListWidget> {
|
||||
extends State<ContactInvitationListWidget> {
|
||||
final ScrollController _scrollController = ScrollController();
|
||||
|
||||
@override
|
@ -3,19 +3,12 @@ import 'dart:async';
|
||||
import 'package:awesome_extensions/awesome_extensions.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_translate/flutter_translate.dart';
|
||||
|
||||
import '../entities/local_account.dart';
|
||||
import '../providers/account.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';
|
||||
import '../../account_manager/account_manager.dart';
|
||||
import '../../tools/tools.dart';
|
||||
|
||||
class InviteDialog extends ConsumerStatefulWidget {
|
||||
class InviteDialog extends StatefulWidget {
|
||||
const InviteDialog(
|
||||
{required this.onValidationCancelled,
|
||||
required this.onValidationSuccess,
|
||||
@ -58,7 +51,7 @@ class InviteDialog extends ConsumerStatefulWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class InviteDialogState extends ConsumerState<InviteDialog> {
|
||||
class InviteDialogState extends State<InviteDialog> {
|
||||
ValidContactInvitation? _validInvitation;
|
||||
bool _isValidating = 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/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_translate/flutter_translate.dart';
|
||||
import 'package:veilid_support/veilid_support.dart';
|
||||
|
||||
import '../entities/local_account.dart';
|
||||
import '../providers/account.dart';
|
||||
import '../providers/contact_invite.dart';
|
||||
import '../tools/tools.dart';
|
||||
import '../veilid_support/veilid_support.dart';
|
||||
import '../../account_manager/account_manager.dart';
|
||||
import '../../tools/tools.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});
|
||||
|
||||
@override
|
||||
@ -31,7 +26,7 @@ class SendInviteDialog extends ConsumerStatefulWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class SendInviteDialogState extends ConsumerState<SendInviteDialog> {
|
||||
class SendInviteDialogState extends State<SendInviteDialog> {
|
||||
final _messageTextController = TextEditingController(
|
||||
text: translate('send_invite_dialog.connect_with_me'));
|
||||
|
||||
@ -135,7 +130,8 @@ class SendInviteDialogState extends ConsumerState<SendInviteDialog> {
|
||||
final navigator = Navigator.of(context);
|
||||
|
||||
// Start generation
|
||||
final activeAccountInfo = await ref.read(fetchActiveAccountProvider.future);
|
||||
final activeAccountInfo =
|
||||
await AccountRepository.instance.fetchActiveAccountInfo();
|
||||
if (activeAccountInfo == null) {
|
||||
navigator.pop();
|
||||
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/material.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_translate/flutter_translate.dart';
|
||||
import '../proto/proto.dart' as proto;
|
||||
import '../pages/main_pager/main_pager.dart';
|
||||
import '../providers/account.dart';
|
||||
import '../providers/chat.dart';
|
||||
import '../providers/contact.dart';
|
||||
import '../theme/theme.dart';
|
||||
import '../../proto/proto.dart' as proto;
|
||||
import '../../theme/theme.dart';
|
||||
|
||||
class ContactItemWidget extends ConsumerWidget {
|
||||
class ContactItemWidget extends StatelessWidget {
|
||||
const ContactItemWidget({required this.contact, super.key});
|
||||
|
||||
final proto.Contact contact;
|
||||
|
||||
@override
|
||||
// ignore: prefer_expression_function_bodies
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
Widget build(
|
||||
BuildContext context,
|
||||
) {
|
||||
final theme = Theme.of(context);
|
||||
//final textTheme = theme.textTheme;
|
||||
final scale = theme.extension<ScaleScheme>()!;
|
@ -1,15 +1,16 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.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});
|
||||
|
||||
@override
|
||||
// ignore: prefer_expression_function_bodies
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
Widget build(
|
||||
BuildContext context,
|
||||
) {
|
||||
final theme = Theme.of(context);
|
||||
final textTheme = theme.textTheme;
|
||||
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_riverpod/flutter_riverpod.dart';
|
||||
|
||||
import '../providers/window_control.dart';
|
||||
import 'home.dart';
|
||||
import '../chat/chat.dart';
|
||||
import '../tools/tools.dart';
|
||||
|
||||
class ChatOnlyPage extends StatefulWidget {
|
||||
const ChatOnlyPage({super.key});
|
||||
@ -11,7 +10,7 @@ class ChatOnlyPage extends StatefulWidget {
|
||||
ChatOnlyPageState createState() => ChatOnlyPageState();
|
||||
}
|
||||
|
||||
class ChatOnlyPageState extends ConsumerState<ChatOnlyPage>
|
||||
class ChatOnlyPageState extends State<ChatOnlyPage>
|
||||
with TickerProviderStateMixin {
|
||||
final _unfocusNode = FocusNode();
|
||||
|
||||
@ -21,7 +20,7 @@ class ChatOnlyPageState extends ConsumerState<ChatOnlyPage>
|
||||
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||
setState(() {});
|
||||
await ref.read(windowControlProvider.notifier).changeWindowSetup(
|
||||
await changeWindowSetup(
|
||||
TitleBarStyle.normal, OrientationCapability.normal);
|
||||
});
|
||||
}
|
||||
@ -33,13 +32,9 @@ class ChatOnlyPageState extends ConsumerState<ChatOnlyPage>
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
ref.watch(windowControlProvider);
|
||||
|
||||
return SafeArea(
|
||||
child: GestureDetector(
|
||||
onTap: () => FocusScope.of(context).requestFocus(_unfocusNode),
|
||||
child: HomePage.buildChatComponent(context, ref),
|
||||
));
|
||||
}
|
||||
Widget build(BuildContext context) => SafeArea(
|
||||
child: GestureDetector(
|
||||
onTap: () => FocusScope.of(context).requestFocus(_unfocusNode),
|
||||
child: buildChatComponent(),
|
||||
));
|
||||
}
|
||||
|
@ -1,28 +1,30 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
class ContactsPage extends ConsumerWidget {
|
||||
class ContactsPage extends StatelessWidget {
|
||||
const ContactsPage({super.key});
|
||||
static const path = '/contacts';
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) => const Scaffold(
|
||||
body: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text('Contacts Page'),
|
||||
// ElevatedButton(
|
||||
// onPressed: () async {
|
||||
// ref.watch(authNotifierProvider.notifier).login(
|
||||
// "myEmail",
|
||||
// "myPassword",
|
||||
// );
|
||||
// },
|
||||
// child: const Text("Login"),
|
||||
// ),
|
||||
],
|
||||
Widget build(
|
||||
BuildContext context,
|
||||
) =>
|
||||
const Scaffold(
|
||||
body: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Text('Contacts Page'),
|
||||
// ElevatedButton(
|
||||
// onPressed: () async {
|
||||
// ref.watch(authNotifierProvider.notifier).login(
|
||||
// "myEmail",
|
||||
// "myPassword",
|
||||
// );
|
||||
// },
|
||||
// child: const Text("Login"),
|
||||
// ),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
);
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ import 'package:veilid_support/veilid_support.dart';
|
||||
|
||||
import '../../proto/proto.dart' as proto;
|
||||
import '../account_manager/account_manager.dart';
|
||||
import '../account_manager/models/models.dart';
|
||||
import '../chat/chat.dart';
|
||||
import '../theme/theme.dart';
|
||||
import '../tools/tools.dart';
|
||||
import 'main_pager/main_pager.dart';
|
||||
@ -19,38 +19,6 @@ class HomePage extends StatefulWidget {
|
||||
|
||||
@override
|
||||
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 {
|
||||
@ -196,7 +164,7 @@ class HomePageState extends State<HomePage> with TickerProviderStateMixin {
|
||||
Widget buildTabletLeftPane() =>
|
||||
Material(color: Colors.transparent, child: buildUserPanel());
|
||||
|
||||
Widget buildTabletRightPane() => HomePage.buildChatComponent();
|
||||
Widget buildTabletRightPane() => buildChatComponent();
|
||||
|
||||
// ignore: prefer_expression_function_bodies
|
||||
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:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.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 '../../providers/contact.dart';
|
||||
import '../../providers/contact_invite.dart';
|
||||
import '../../../theme/theme.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 '../../contacts/contacts.dart';
|
||||
|
||||
class AccountPage extends ConsumerStatefulWidget {
|
||||
class AccountPage extends StatefulWidget {
|
||||
const AccountPage({
|
||||
required this.localAccounts,
|
||||
required this.activeUserLogin,
|
||||
@ -41,7 +36,7 @@ class AccountPage extends ConsumerStatefulWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class AccountPageState extends ConsumerState<AccountPage> {
|
||||
class AccountPageState extends State<AccountPage> {
|
||||
final _unfocusNode = FocusNode();
|
||||
|
||||
@override
|
||||
|
@ -1,8 +1,7 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
|
||||
class BottomSheetActionButton extends ConsumerStatefulWidget {
|
||||
class BottomSheetActionButton extends StatefulWidget {
|
||||
const BottomSheetActionButton(
|
||||
{required this.bottomSheetBuilder,
|
||||
required this.builder,
|
||||
@ -32,8 +31,7 @@ class BottomSheetActionButton extends ConsumerStatefulWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class BottomSheetActionButtonState
|
||||
extends ConsumerState<BottomSheetActionButton> {
|
||||
class BottomSheetActionButtonState extends State<BottomSheetActionButton> {
|
||||
bool _showFab = true;
|
||||
|
||||
@override
|
@ -1,28 +1,19 @@
|
||||
import 'package:awesome_extensions/awesome_extensions.dart';
|
||||
import 'package:fast_immutable_collections/fast_immutable_collections.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 '../../providers/account.dart';
|
||||
import '../../providers/chat.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';
|
||||
import '../../account_manager/account_manager.dart';
|
||||
import '../../tools/tools.dart';
|
||||
|
||||
class ChatsPage extends ConsumerStatefulWidget {
|
||||
class ChatsPage extends StatefulWidget {
|
||||
const ChatsPage({super.key});
|
||||
|
||||
@override
|
||||
ChatsPageState createState() => ChatsPageState();
|
||||
}
|
||||
|
||||
class ChatsPageState extends ConsumerState<ChatsPage> {
|
||||
class ChatsPageState extends State<ChatsPage> {
|
||||
final _unfocusNode = FocusNode();
|
||||
|
||||
@override
|
||||
|
@ -1,27 +1,23 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:awesome_extensions/awesome_extensions.dart';
|
||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_animate/flutter_animate.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_translate/flutter_translate.dart';
|
||||
import 'package:preload_page_view/preload_page_view.dart';
|
||||
import 'package:stylish_bottom_bar/model/bar_items.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 '../../../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 'bottom_sheet_action_button.dart';
|
||||
import 'chats.dart';
|
||||
|
||||
class MainPager extends StatefulWidget {
|
||||
@ -50,8 +46,7 @@ class MainPager extends StatefulWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class MainPagerState extends ConsumerState<MainPager>
|
||||
with TickerProviderStateMixin {
|
||||
class MainPagerState extends State<MainPager> with TickerProviderStateMixin {
|
||||
//////////////////////////////////////////////////////////////////
|
||||
|
||||
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
|
||||
Widget _onNewChatBottomSheetBuilder(BuildContext context) {
|
||||
return const SizedBox(
|
||||
@ -221,7 +158,7 @@ class MainPagerState extends ConsumerState<MainPager>
|
||||
Widget _bottomSheetBuilder(BuildContext context) {
|
||||
if (_currentPage == 0) {
|
||||
// New contact invitation
|
||||
return _newContactInvitationBottomSheetBuilder(context);
|
||||
return newContactInvitationBottomSheetBuilder(context);
|
||||
} else if (_currentPage == 1) {
|
||||
// New chat
|
||||
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,8 +1,6 @@
|
||||
import '../tools/tools.dart';
|
||||
import 'settings.dart';
|
||||
|
||||
xxx convert to non-asyncvalue based wrapper since there's always a default here
|
||||
|
||||
class PreferencesCubit extends StreamWrapperCubit<Preferences> {
|
||||
PreferencesCubit(PreferencesRepository repository)
|
||||
: super(repository.stream, defaultState: repository.value);
|
||||
|
@ -1,17 +1,13 @@
|
||||
import 'package:animated_theme_switcher/animated_theme_switcher.dart';
|
||||
import 'package:awesome_extensions/awesome_extensions.dart';
|
||||
import 'package:flutter/foundation.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_translate/flutter_translate.dart';
|
||||
import 'package:veilid_support/veilid_support.dart';
|
||||
|
||||
import '../layout/default_app_bar.dart';
|
||||
import '../theme/theme.dart';
|
||||
import '../tools/tools.dart';
|
||||
import '../veilid_processor/veilid_processor.dart';
|
||||
import 'preferences_cubit.dart';
|
||||
import 'preferences_repository.dart';
|
||||
import 'settings.dart';
|
||||
|
||||
class SettingsPage extends StatefulWidget {
|
||||
@ -68,8 +64,8 @@ class SettingsPageState extends State<SettingsPage> {
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) => BlocBuilder<PreferencesCubit,
|
||||
AsyncValue<Preferences>>(
|
||||
Widget build(BuildContext context) => AsyncBlocBuilder<PreferencesCubit,
|
||||
Preferences>(
|
||||
builder: (context, state) => ThemeSwitchingArea(
|
||||
child: Scaffold(
|
||||
// resizeToAvoidBottomInset: false,
|
||||
@ -94,14 +90,16 @@ class SettingsPageState extends State<SettingsPage> {
|
||||
label:
|
||||
Text(translate('settings_page.color_theme'))),
|
||||
items: _getThemeDropdownItems(),
|
||||
initialValue: themePreferences.colorPreference,
|
||||
initialValue: state.themePreferences.colorPreference,
|
||||
onChanged: (value) async {
|
||||
final newPrefs = themePreferences.copyWith(
|
||||
colorPreference: value as ColorPreference);
|
||||
await themeService.save(newPrefs);
|
||||
final newPrefs = state.copyWith(
|
||||
themePreferences: state.themePreferences
|
||||
.copyWith(
|
||||
colorPreference:
|
||||
value as ColorPreference));
|
||||
switcher.changeTheme(
|
||||
theme: themeService.get(newPrefs));
|
||||
ref.invalidate(themeServiceProvider);
|
||||
theme: newPrefs.themePreferences.themeData());
|
||||
await PreferencesRepository.instance.set(newPrefs);
|
||||
setState(() {});
|
||||
})),
|
||||
ThemeSwitcher.withTheme(
|
||||
@ -111,15 +109,17 @@ class SettingsPageState extends State<SettingsPage> {
|
||||
label: Text(
|
||||
translate('settings_page.brightness_mode'))),
|
||||
items: _getBrightnessDropdownItems(),
|
||||
initialValue: themePreferences.brightnessPreference,
|
||||
initialValue:
|
||||
state.themePreferences.brightnessPreference,
|
||||
onChanged: (value) async {
|
||||
final newPrefs = themePreferences.copyWith(
|
||||
brightnessPreference:
|
||||
value as BrightnessPreference);
|
||||
await themeService.save(newPrefs);
|
||||
final newPrefs = state.copyWith(
|
||||
themePreferences: state.themePreferences
|
||||
.copyWith(
|
||||
brightnessPreference:
|
||||
value as BrightnessPreference));
|
||||
switcher.changeTheme(
|
||||
theme: themeService.get(newPrefs));
|
||||
ref.invalidate(themeServiceProvider);
|
||||
theme: newPrefs.themePreferences.themeData());
|
||||
await PreferencesRepository.instance.set(newPrefs);
|
||||
setState(() {});
|
||||
})),
|
||||
],
|
||||
|
@ -2,12 +2,11 @@ import 'package:awesome_extensions/awesome_extensions.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.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({
|
||||
this.matchPass,
|
||||
this.description,
|
||||
@ -29,7 +28,7 @@ class EnterPasswordDialog extends ConsumerStatefulWidget {
|
||||
}
|
||||
}
|
||||
|
||||
class EnterPasswordDialogState extends ConsumerState<EnterPasswordDialog> {
|
||||
class EnterPasswordDialogState extends State<EnterPasswordDialog> {
|
||||
final passwordController = TextEditingController();
|
||||
final focusNode = FocusNode();
|
||||
final formKey = GlobalKey<FormState>();
|
@ -2,13 +2,12 @@ import 'package:awesome_extensions/awesome_extensions.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:flutter_translate/flutter_translate.dart';
|
||||
import 'package:pinput/pinput.dart';
|
||||
|
||||
import '../tools/tools.dart';
|
||||
import '../theme/theme.dart';
|
||||
|
||||
class EnterPinDialog extends ConsumerStatefulWidget {
|
||||
class EnterPinDialog extends StatefulWidget {
|
||||
const EnterPinDialog({
|
||||
required this.reenter,
|
||||
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 focusNode = FocusNode();
|
||||
final formKey = GlobalKey<FormState>();
|
@ -1,10 +1,13 @@
|
||||
export 'animations.dart';
|
||||
export 'enter_password.dart';
|
||||
export 'enter_pin.dart';
|
||||
export 'loggy.dart';
|
||||
export 'phono_byte.dart';
|
||||
export 'responsive.dart';
|
||||
export 'scanner_error_widget.dart';
|
||||
export 'shared_preferences.dart';
|
||||
export 'state_logger.dart';
|
||||
export 'stream_listenable.dart';
|
||||
export 'stream_wrapper_cubit.dart';
|
||||
export 'widget_helpers.dart';
|
||||
export 'window_control.dart';
|
||||
|
@ -1,6 +1,7 @@
|
||||
import 'package:awesome_extensions/awesome_extensions.dart';
|
||||
import 'package:blurry_modal_progress_hud/blurry_modal_progress_hud.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:flutter_spinkit/flutter_spinkit.dart';
|
||||
import 'package:flutter_translate/flutter_translate.dart';
|
||||
import 'package:motion_toast/motion_toast.dart';
|
||||
@ -60,6 +61,22 @@ extension AsyncValueBuilderExt<T> on AsyncValue<T> {
|
||||
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(
|
||||
BuildContext context, String title, String text) async {
|
||||
await QuickAlert.show(
|
||||
|
Loading…
x
Reference in New Issue
Block a user