more refactor

This commit is contained in:
Christien Rioux 2024-01-27 20:10:30 -05:00
parent 7cf44ef192
commit 20047a956f
14 changed files with 113 additions and 732 deletions

View File

@ -1,5 +1,6 @@
import 'dart:async';
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
import 'package:fixnum/fixnum.dart';
import 'package:flutter/foundation.dart';
import 'package:veilid_support/veilid_support.dart';
@ -29,22 +30,19 @@ class InvitationStatus {
//////////////////////////////////////////////////
//////////////////////////////////////////////////
// Mutable state for per-account contact invitations
class ContactInvitationRepository {
ContactInvitationRepository._({
class ContactInvitationListCubit extends DHTShortArrayCubit<proto.ContactInvitation> {
ContactInvitationListCubit({
required ActiveAccountInfo activeAccountInfo,
required proto.Account account,
required DHTShortArray dhtRecord,
}) : _activeAccountInfo = activeAccountInfo,
_account = account,
_dhtRecord = dhtRecord;
void dispose() {
unawaited(close());
}
_dhtRecord = dhtRecord,
super(shortArray: dhtRecord, decodeElement: proto.ContactInvitation.fromBuffer);
xxx convert the rest of this to cubit
static Future<ContactInvitationRepository> open(
ActiveAccountInfo activeAccountInfo, proto.Account account) async {
@ -65,8 +63,10 @@ class ContactInvitationRepository {
dhtRecord: dhtRecord);
}
@override
Future<void> close() async {
await _dhtRecord.close();
await super.close();
}
// Future<void> refresh() async {

View File

@ -0,0 +1 @@
export 'contact_invitation_list_cubit.dart';

View File

@ -1,131 +0,0 @@
import 'dart:async';
import 'dart:typed_data';
import 'package:awesome_extensions/awesome_extensions.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:flutter_translate/flutter_translate.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});
@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 ConsumerState<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);
}
}

View File

@ -1,399 +0,0 @@
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_riverpod/flutter_riverpod.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 '../old_to_refactor/tools/tools.dart';
import 'views/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) {
if (barcode.corners == null) {
return;
}
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 ConsumerStatefulWidget {
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 ConsumerState<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));
}
}

View File

@ -9,8 +9,8 @@ import '../../tools/tools.dart';
import 'home_account_invalid.dart';
import 'home_account_locked.dart';
import 'home_account_missing.dart';
import 'home_account_ready.dart';
import 'home_account_ready/home_account_ready.dart';
import 'home_no_active.dart';
class HomePage extends StatefulWidget {
const HomePage({super.key});
@ -40,9 +40,11 @@ class HomePageState extends State<HomePage> with TickerProviderStateMixin {
Widget buildWithLogin(BuildContext context, IList<LocalAccount> localAccounts,
Typed<FixedEncodedString43>? activeUserLogin) {
final activeUserLogin = context.watch<ActiveUserLoginCubit>().state;
if (activeUserLogin == null) {
// If no logged in user is active, show the loading panel
return waitingPage(context);
return const HomeNoActive();
}
final accountInfo = AccountRepository.instance
@ -72,8 +74,6 @@ class HomePageState extends State<HomePage> with TickerProviderStateMixin {
Widget build(BuildContext context) {
final theme = Theme.of(context);
final scale = theme.extension<ScaleScheme>()!;
final activeUserLogin = context.watch<ActiveUserLoginCubit>().state;
final localAccounts = context.watch<LocalAccountsCubit>().state;
return SafeArea(
child: GestureDetector(
@ -81,7 +81,6 @@ class HomePageState extends State<HomePage> with TickerProviderStateMixin {
child: DecoratedBox(
decoration: BoxDecoration(
color: scale.primaryScale.activeElementBackground),
child:
buildWithLogin(context, localAccounts, activeUserLogin))));
child: buildWithLogin(context))));
}
}

View File

@ -1,9 +1,9 @@
import 'dart:async';
import 'package:awesome_extensions/awesome_extensions.dart';
import 'package:equatable/equatable.dart';
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_translate/flutter_translate.dart';
import 'package:go_router/go_router.dart';
@ -14,23 +14,16 @@ import '../../../contact_invitation/contact_invitation.dart';
import '../../../proto/proto.dart' as proto;
import '../../../theme/theme.dart';
import '../../../tools/tools.dart';
import 'main_pager/main_pager.dart';
class HomeAccountReady extends StatefulWidget {
const HomeAccountReady(
{required IList<LocalAccount> localAccounts,
required TypedKey activeUserLogin,
required ActiveAccountInfo activeAccountInfo,
required proto.Account account,
{required ActiveAccountInfo activeAccountInfo,
required Account account,
super.key})
: _localAccounts = localAccounts,
_activeUserLogin = activeUserLogin,
_activeAccountInfo = activeAccountInfo,
_account = account;
: _accountReadyContext = accountReadyContext;
final IList<LocalAccount> _localAccounts;
final TypedKey _activeUserLogin;
final ActiveAccountInfo _activeAccountInfo;
final proto.Account _account;
final AccountReadyContext _accountReadyContext;
@override
HomeAccountReadyState createState() => HomeAccountReadyState();
@ -52,7 +45,7 @@ class HomeAccountReadyState extends State<HomeAccountReady>
Future.delayed(Duration.zero, () async {
//
final cir = await ContactInvitationRepository.open(
widget._activeAccountInfo, widget._account);
widget.activeAccountInfo, widget._accountReadyContext.account);
setState(() {
_contactInvitationRepository = cir;
@ -66,15 +59,6 @@ class HomeAccountReadyState extends State<HomeAccountReady>
_contactInvitationRepository?.dispose();
}
// ignore: prefer_expression_function_bodies
Widget buildAccountList() {
return const Column(children: [
Center(child: Text('Small Profile')),
Center(child: Text('Contact invitations')),
Center(child: Text('Contacts'))
]);
}
Widget buildUnlockAccount(
BuildContext context,
IList<LocalAccount> localAccounts,
@ -83,141 +67,66 @@ class HomeAccountReadyState extends State<HomeAccountReady>
return const Center(child: Text('unlock account'));
}
/// We have an active, unlocked, user login
Widget buildReadyAccount(
BuildContext context,
IList<LocalAccount> localAccounts,
TypedKey activeUserLogin,
DHTRecord accountRecord) {
Widget buildUserPanel(BuildContext context) {
final theme = Theme.of(context);
final scale = theme.extension<ScaleScheme>()!;
xxx get rid of the cubit here and
return BlocProvider(
create: (context) => AccountRecordCubit(record: accountRecord),
child: Column(children: <Widget>[
Row(children: [
IconButton(
icon: const Icon(Icons.settings),
color: scale.secondaryScale.text,
constraints: const BoxConstraints.expand(height: 64, width: 64),
style: ButtonStyle(
backgroundColor:
MaterialStateProperty.all(scale.secondaryScale.border),
shape: MaterialStateProperty.all(
const RoundedRectangleBorder(
borderRadius:
BorderRadius.all(Radius.circular(16))))),
tooltip: translate('app_bar.settings_tooltip'),
onPressed: () async {
context.go('/home/settings');
}).paddingLTRB(0, 0, 8, 0),
context
.watch<AccountRecordCubit>()
.state
.builder((context, account) => ProfileWidget(
name: account.profile.name,
pronouns: account.profile.pronouns,
))
.expanded(),
]).paddingAll(8),
context
.watch<AccountRecordCubit>()
.state
.builder((context, account) => MainPager(
localAccounts: localAccounts,
activeUserLogin: activeUserLogin,
account: account))
.expanded()
]));
return Column(children: <Widget>[
Row(children: [
IconButton(
icon: const Icon(Icons.settings),
color: scale.secondaryScale.text,
constraints: const BoxConstraints.expand(height: 64, width: 64),
style: ButtonStyle(
backgroundColor:
MaterialStateProperty.all(scale.secondaryScale.border),
shape: MaterialStateProperty.all(const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(16))))),
tooltip: translate('app_bar.settings_tooltip'),
onPressed: () async {
context.go('/home/settings');
}).paddingLTRB(0, 0, 8, 0),
ProfileWidget(
name: widget._accountReadyContext.account.profile.name,
pronouns: widget._accountReadyContext.account.profile.pronouns,
).expanded(),
]).paddingAll(8),
MainPager().expanded()
]);
}
xxx get rid of this whole function
Widget buildPhone(BuildContext context) =>
Material(color: Colors.transparent, child: buildUserPanel(context));
Widget buildUserPanel() => Builder(builder: (context) {
final activeUserLogin = context.watch<ActiveUserLoginCubit>().state;
final localAccounts = context.watch<LocalAccountsCubit>().state;
Widget buildTabletLeftPane(BuildContext context) => Builder(
builder: (context) =>
Material(color: Colors.transparent, child: buildUserPanel(context)));
if (activeUserLogin == null) {
// If no logged in user is active, show the loading panel
return waitingPage(context);
}
final account = AccountRepository.instance
.getAccountInfo(accountMasterRecordKey: activeUserLogin)!;
switch (account.status) {
case AccountInfoStatus.noAccount:
Future.delayed(0.ms, () async {
await showErrorModal(
context,
translate('home.missing_account_title'),
translate('home.missing_account_text'));
// Delete account
await AccountRepository.instance
.deleteLocalAccount(activeUserLogin);
// Switch to no active user login
await AccountRepository.instance.switchToAccount(null);
});
return waitingPage(context);
case AccountInfoStatus.accountInvalid:
Future.delayed(0.ms, () async {
await showErrorModal(
context,
translate('home.invalid_account_title'),
translate('home.invalid_account_text'));
// Delete account
await AccountRepository.instance
.deleteLocalAccount(activeUserLogin);
// Switch to no active user login
await AccountRepository.instance.switchToAccount(null);
});
return waitingPage(context);
case AccountInfoStatus.accountLocked:
// Show unlock widget
return buildUnlockAccount(context, localAccounts);
case AccountInfoStatus.accountReady:
return buildReadyAccount(
context,
localAccounts,
activeUserLogin,
account.activeAccountInfo!.accountRecord,
);
}
});
Widget buildPhone() =>
Material(color: Colors.transparent, child: buildUserPanel());
Widget buildTabletLeftPane() =>
Material(color: Colors.transparent, child: buildUserPanel());
Widget buildTabletRightPane() => buildChatComponent();
Widget buildTabletRightPane(BuildContext context) => buildChatComponent();
// ignore: prefer_expression_function_bodies
Widget buildTablet() => Builder(builder: (context) {
final w = MediaQuery.of(context).size.width;
final theme = Theme.of(context);
final scale = theme.extension<ScaleScheme>()!;
Widget buildTablet(BuildContext context) {
final w = MediaQuery.of(context).size.width;
final theme = Theme.of(context);
final scale = theme.extension<ScaleScheme>()!;
final children = [
ConstrainedBox(
constraints: const BoxConstraints(minWidth: 300, maxWidth: 300),
child: ConstrainedBox(
constraints: BoxConstraints(maxWidth: w / 2),
child: buildTabletLeftPane())),
SizedBox(
width: 2,
height: double.infinity,
child: ColoredBox(color: scale.primaryScale.hoverBorder)),
Expanded(child: buildTabletRightPane()),
];
final children = [
ConstrainedBox(
constraints: const BoxConstraints(minWidth: 300, maxWidth: 300),
child: ConstrainedBox(
constraints: BoxConstraints(maxWidth: w / 2),
child: buildTabletLeftPane(context))),
SizedBox(
width: 2,
height: double.infinity,
child: ColoredBox(color: scale.primaryScale.hoverBorder)),
Expanded(child: buildTabletRightPane(context)),
];
return Row(
children: children,
);
});
return Row(
children: children,
);
}
@override
Widget build(BuildContext context) {
@ -225,11 +134,13 @@ xxx get rid of this whole function
return waitingPage(context);
}
return responsiveVisibility(
context: context,
phone: false,
)
? buildTablet()
: buildPhone();
return RepositoryProvider.value(
value: _contactInvitationRepository,
child: responsiveVisibility(
context: context,
phone: false,
)
? buildTablet(context)
: buildPhone(context));
}
}

View File

@ -5,34 +5,17 @@ import 'package:flutter/material.dart';
import 'package:flutter_translate/flutter_translate.dart';
import 'package:veilid_support/veilid_support.dart';
import '../../../../account_manager/account_manager.dart';
import '../../../../proto/proto.dart' as proto;
import '../../../account_manager/account_manager.dart';
import '../../../contact_invitation/contact_invitation.dart';
import '../../../contacts/contacts.dart';
import '../../../theme/theme.dart';
import '../../../../theme/theme.dart';
class AccountPage extends StatefulWidget {
const AccountPage({
required this.localAccounts,
required this.activeUserLogin,
required this.account,
super.key,
});
final IList<LocalAccount> localAccounts;
final TypedKey activeUserLogin;
final proto.Account account;
@override
AccountPageState createState() => AccountPageState();
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties
..add(IterableProperty<LocalAccount>('localAccounts', localAccounts))
..add(DiagnosticsProperty<TypedKey>('activeUserLogin', activeUserLogin))
..add(DiagnosticsProperty<proto.Account>('account', account));
}
}
class AccountPageState extends State<AccountPage> {

View File

@ -1,7 +1,3 @@
export 'home/home_account_ready/chat_only.dart';
export 'default_app_bar.dart';
export 'edit_account.dart';
export 'edit_contact.dart';
export 'home.dart';
export 'home/home.dart';
export 'index.dart';
export 'main_pager/main_pager.dart';

View File

@ -5,18 +5,13 @@
import 'dart:convert';
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:veilid_support/veilid_support.dart';
import '../../account_manager/account_manager.dart';
import '../../proto/proto.dart' as proto;
import '../../tools/tools.dart';
import '../../init.dart';
import '../../../packages/veilid_support/veilid_support.dart';
import 'account.dart';
import 'chat.dart';
import 'contact.dart';
part 'conversation.g.dart';
import 'chat.dart';
class Conversation {
Conversation._(

View File

@ -7,3 +7,4 @@ export 'src/dht_record_crypto.dart';
export 'src/dht_record_cubit.dart';
export 'src/dht_record_pool.dart';
export 'src/dht_short_array.dart';
export 'src/dht_short_array_cubit.dart';

View File

@ -7,12 +7,32 @@ import '../../veilid_support.dart';
class DHTShortArrayCubit<T> extends Cubit<AsyncValue<IList<T>>> {
DHTShortArrayCubit({
required Future<DHTShortArray> Function() open,
required T Function(List<int> data) decodeElement,
}) : _decodeElement = decodeElement,
_wantsUpdate = false,
_isUpdating = false,
_wantsCloseRecord = false,
super(const AsyncValue.loading()) {
Future.delayed(Duration.zero, () async {
// Open DHT record
_shortArray = await open();
_wantsCloseRecord = true;
// Make initial state update
_update();
_subscription = await _shortArray.listen(_update);
});
}
DHTShortArrayCubit.value({
required DHTShortArray shortArray,
required T Function(List<int> data) decodeElement,
}) : _shortArray = shortArray,
_decodeElement = decodeElement,
_wantsUpdate = false,
_isUpdating = false,
_wantsCloseRecord = false,
super(const AsyncValue.loading()) {
// Make initial state update
_update();
@ -65,12 +85,16 @@ class DHTShortArrayCubit<T> extends Cubit<AsyncValue<IList<T>>> {
Future<void> close() async {
await _subscription?.cancel();
_subscription = null;
if (_wantsCloseRecord) {
await _shortArray.close();
}
await super.close();
}
final DHTShortArray _shortArray;
late final DHTShortArray _shortArray;
final T Function(List<int> data) _decodeElement;
StreamSubscription<void>? _subscription;
bool _wantsUpdate;
bool _isUpdating;
bool _wantsCloseRecord;
}

View File

@ -1014,7 +1014,7 @@ packages:
source: hosted
version: "3.1.0"
provider:
dependency: transitive
dependency: "direct main"
description:
name: provider
sha256: "9a96a0a19b594dbc5bf0f1f27d2bc67d5f95957359b461cd9feb44ed6ae75096"

View File

@ -58,6 +58,7 @@ dependencies:
pinput: ^3.0.1
preload_page_view: ^0.2.0
protobuf: ^3.0.0
provider: ^6.1.1
qr_code_dart_scan: ^0.7.2
qr_flutter: ^4.1.0
quickalert: ^1.0.1