mirror of
https://gitlab.com/veilid/veilidchat.git
synced 2025-02-22 07:49:54 -05:00
more refactor
This commit is contained in:
parent
7cf44ef192
commit
20047a956f
@ -1,5 +1,6 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
||||||
import 'package:fixnum/fixnum.dart';
|
import 'package:fixnum/fixnum.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:veilid_support/veilid_support.dart';
|
import 'package:veilid_support/veilid_support.dart';
|
||||||
@ -29,22 +30,19 @@ class InvitationStatus {
|
|||||||
|
|
||||||
//////////////////////////////////////////////////
|
//////////////////////////////////////////////////
|
||||||
|
|
||||||
|
|
||||||
//////////////////////////////////////////////////
|
//////////////////////////////////////////////////
|
||||||
// Mutable state for per-account contact invitations
|
// Mutable state for per-account contact invitations
|
||||||
class ContactInvitationRepository {
|
|
||||||
ContactInvitationRepository._({
|
class ContactInvitationListCubit extends DHTShortArrayCubit<proto.ContactInvitation> {
|
||||||
|
ContactInvitationListCubit({
|
||||||
required ActiveAccountInfo activeAccountInfo,
|
required ActiveAccountInfo activeAccountInfo,
|
||||||
required proto.Account account,
|
required proto.Account account,
|
||||||
required DHTShortArray dhtRecord,
|
required DHTShortArray dhtRecord,
|
||||||
}) : _activeAccountInfo = activeAccountInfo,
|
}) : _activeAccountInfo = activeAccountInfo,
|
||||||
_account = account,
|
_account = account,
|
||||||
_dhtRecord = dhtRecord;
|
_dhtRecord = dhtRecord,
|
||||||
|
super(shortArray: dhtRecord, decodeElement: proto.ContactInvitation.fromBuffer);
|
||||||
void dispose() {
|
xxx convert the rest of this to cubit
|
||||||
unawaited(close());
|
|
||||||
}
|
|
||||||
|
|
||||||
static Future<ContactInvitationRepository> open(
|
static Future<ContactInvitationRepository> open(
|
||||||
ActiveAccountInfo activeAccountInfo, proto.Account account) async {
|
ActiveAccountInfo activeAccountInfo, proto.Account account) async {
|
||||||
|
|
||||||
@ -65,8 +63,10 @@ class ContactInvitationRepository {
|
|||||||
dhtRecord: dhtRecord);
|
dhtRecord: dhtRecord);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
Future<void> close() async {
|
Future<void> close() async {
|
||||||
await _dhtRecord.close();
|
await _dhtRecord.close();
|
||||||
|
await super.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Future<void> refresh() async {
|
// Future<void> refresh() async {
|
1
lib/contact_invitation/cubits/cubits.dart
Normal file
1
lib/contact_invitation/cubits/cubits.dart
Normal file
@ -0,0 +1 @@
|
|||||||
|
export 'contact_invitation_list_cubit.dart';
|
@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
@ -9,8 +9,8 @@ import '../../tools/tools.dart';
|
|||||||
import 'home_account_invalid.dart';
|
import 'home_account_invalid.dart';
|
||||||
import 'home_account_locked.dart';
|
import 'home_account_locked.dart';
|
||||||
import 'home_account_missing.dart';
|
import 'home_account_missing.dart';
|
||||||
import 'home_account_ready.dart';
|
|
||||||
import 'home_account_ready/home_account_ready.dart';
|
import 'home_account_ready/home_account_ready.dart';
|
||||||
|
import 'home_no_active.dart';
|
||||||
|
|
||||||
class HomePage extends StatefulWidget {
|
class HomePage extends StatefulWidget {
|
||||||
const HomePage({super.key});
|
const HomePage({super.key});
|
||||||
@ -40,9 +40,11 @@ class HomePageState extends State<HomePage> with TickerProviderStateMixin {
|
|||||||
|
|
||||||
Widget buildWithLogin(BuildContext context, IList<LocalAccount> localAccounts,
|
Widget buildWithLogin(BuildContext context, IList<LocalAccount> localAccounts,
|
||||||
Typed<FixedEncodedString43>? activeUserLogin) {
|
Typed<FixedEncodedString43>? activeUserLogin) {
|
||||||
|
final activeUserLogin = context.watch<ActiveUserLoginCubit>().state;
|
||||||
|
|
||||||
if (activeUserLogin == null) {
|
if (activeUserLogin == null) {
|
||||||
// If no logged in user is active, show the loading panel
|
// If no logged in user is active, show the loading panel
|
||||||
return waitingPage(context);
|
return const HomeNoActive();
|
||||||
}
|
}
|
||||||
|
|
||||||
final accountInfo = AccountRepository.instance
|
final accountInfo = AccountRepository.instance
|
||||||
@ -72,8 +74,6 @@ class HomePageState extends State<HomePage> with TickerProviderStateMixin {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final scale = theme.extension<ScaleScheme>()!;
|
final scale = theme.extension<ScaleScheme>()!;
|
||||||
final activeUserLogin = context.watch<ActiveUserLoginCubit>().state;
|
|
||||||
final localAccounts = context.watch<LocalAccountsCubit>().state;
|
|
||||||
|
|
||||||
return SafeArea(
|
return SafeArea(
|
||||||
child: GestureDetector(
|
child: GestureDetector(
|
||||||
@ -81,7 +81,6 @@ class HomePageState extends State<HomePage> with TickerProviderStateMixin {
|
|||||||
child: DecoratedBox(
|
child: DecoratedBox(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: scale.primaryScale.activeElementBackground),
|
color: scale.primaryScale.activeElementBackground),
|
||||||
child:
|
child: buildWithLogin(context))));
|
||||||
buildWithLogin(context, localAccounts, activeUserLogin))));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
import 'package:awesome_extensions/awesome_extensions.dart';
|
import 'package:awesome_extensions/awesome_extensions.dart';
|
||||||
|
import 'package:equatable/equatable.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_animate/flutter_animate.dart';
|
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:flutter_translate/flutter_translate.dart';
|
import 'package:flutter_translate/flutter_translate.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
@ -14,23 +14,16 @@ import '../../../contact_invitation/contact_invitation.dart';
|
|||||||
import '../../../proto/proto.dart' as proto;
|
import '../../../proto/proto.dart' as proto;
|
||||||
import '../../../theme/theme.dart';
|
import '../../../theme/theme.dart';
|
||||||
import '../../../tools/tools.dart';
|
import '../../../tools/tools.dart';
|
||||||
|
import 'main_pager/main_pager.dart';
|
||||||
|
|
||||||
class HomeAccountReady extends StatefulWidget {
|
class HomeAccountReady extends StatefulWidget {
|
||||||
const HomeAccountReady(
|
const HomeAccountReady(
|
||||||
{required IList<LocalAccount> localAccounts,
|
{required ActiveAccountInfo activeAccountInfo,
|
||||||
required TypedKey activeUserLogin,
|
required Account account,
|
||||||
required ActiveAccountInfo activeAccountInfo,
|
|
||||||
required proto.Account account,
|
|
||||||
super.key})
|
super.key})
|
||||||
: _localAccounts = localAccounts,
|
: _accountReadyContext = accountReadyContext;
|
||||||
_activeUserLogin = activeUserLogin,
|
|
||||||
_activeAccountInfo = activeAccountInfo,
|
|
||||||
_account = account;
|
|
||||||
|
|
||||||
final IList<LocalAccount> _localAccounts;
|
final AccountReadyContext _accountReadyContext;
|
||||||
final TypedKey _activeUserLogin;
|
|
||||||
final ActiveAccountInfo _activeAccountInfo;
|
|
||||||
final proto.Account _account;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
HomeAccountReadyState createState() => HomeAccountReadyState();
|
HomeAccountReadyState createState() => HomeAccountReadyState();
|
||||||
@ -52,7 +45,7 @@ class HomeAccountReadyState extends State<HomeAccountReady>
|
|||||||
Future.delayed(Duration.zero, () async {
|
Future.delayed(Duration.zero, () async {
|
||||||
//
|
//
|
||||||
final cir = await ContactInvitationRepository.open(
|
final cir = await ContactInvitationRepository.open(
|
||||||
widget._activeAccountInfo, widget._account);
|
widget.activeAccountInfo, widget._accountReadyContext.account);
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
_contactInvitationRepository = cir;
|
_contactInvitationRepository = cir;
|
||||||
@ -66,15 +59,6 @@ class HomeAccountReadyState extends State<HomeAccountReady>
|
|||||||
_contactInvitationRepository?.dispose();
|
_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(
|
Widget buildUnlockAccount(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
IList<LocalAccount> localAccounts,
|
IList<LocalAccount> localAccounts,
|
||||||
@ -83,141 +67,66 @@ class HomeAccountReadyState extends State<HomeAccountReady>
|
|||||||
return const Center(child: Text('unlock account'));
|
return const Center(child: Text('unlock account'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/// We have an active, unlocked, user login
|
Widget buildUserPanel(BuildContext context) {
|
||||||
Widget buildReadyAccount(
|
|
||||||
BuildContext context,
|
|
||||||
IList<LocalAccount> localAccounts,
|
|
||||||
TypedKey activeUserLogin,
|
|
||||||
DHTRecord accountRecord) {
|
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final scale = theme.extension<ScaleScheme>()!;
|
final scale = theme.extension<ScaleScheme>()!;
|
||||||
|
|
||||||
xxx get rid of the cubit here and
|
return Column(children: <Widget>[
|
||||||
|
Row(children: [
|
||||||
return BlocProvider(
|
IconButton(
|
||||||
create: (context) => AccountRecordCubit(record: accountRecord),
|
icon: const Icon(Icons.settings),
|
||||||
child: Column(children: <Widget>[
|
color: scale.secondaryScale.text,
|
||||||
Row(children: [
|
constraints: const BoxConstraints.expand(height: 64, width: 64),
|
||||||
IconButton(
|
style: ButtonStyle(
|
||||||
icon: const Icon(Icons.settings),
|
backgroundColor:
|
||||||
color: scale.secondaryScale.text,
|
MaterialStateProperty.all(scale.secondaryScale.border),
|
||||||
constraints: const BoxConstraints.expand(height: 64, width: 64),
|
shape: MaterialStateProperty.all(const RoundedRectangleBorder(
|
||||||
style: ButtonStyle(
|
borderRadius: BorderRadius.all(Radius.circular(16))))),
|
||||||
backgroundColor:
|
tooltip: translate('app_bar.settings_tooltip'),
|
||||||
MaterialStateProperty.all(scale.secondaryScale.border),
|
onPressed: () async {
|
||||||
shape: MaterialStateProperty.all(
|
context.go('/home/settings');
|
||||||
const RoundedRectangleBorder(
|
}).paddingLTRB(0, 0, 8, 0),
|
||||||
borderRadius:
|
ProfileWidget(
|
||||||
BorderRadius.all(Radius.circular(16))))),
|
name: widget._accountReadyContext.account.profile.name,
|
||||||
tooltip: translate('app_bar.settings_tooltip'),
|
pronouns: widget._accountReadyContext.account.profile.pronouns,
|
||||||
onPressed: () async {
|
).expanded(),
|
||||||
context.go('/home/settings');
|
]).paddingAll(8),
|
||||||
}).paddingLTRB(0, 0, 8, 0),
|
MainPager().expanded()
|
||||||
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()
|
|
||||||
]));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
xxx get rid of this whole function
|
Widget buildPhone(BuildContext context) =>
|
||||||
|
Material(color: Colors.transparent, child: buildUserPanel(context));
|
||||||
|
|
||||||
Widget buildUserPanel() => Builder(builder: (context) {
|
Widget buildTabletLeftPane(BuildContext context) => Builder(
|
||||||
final activeUserLogin = context.watch<ActiveUserLoginCubit>().state;
|
builder: (context) =>
|
||||||
final localAccounts = context.watch<LocalAccountsCubit>().state;
|
Material(color: Colors.transparent, child: buildUserPanel(context)));
|
||||||
|
|
||||||
if (activeUserLogin == null) {
|
Widget buildTabletRightPane(BuildContext context) => buildChatComponent();
|
||||||
// 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();
|
|
||||||
|
|
||||||
// ignore: prefer_expression_function_bodies
|
// ignore: prefer_expression_function_bodies
|
||||||
Widget buildTablet() => Builder(builder: (context) {
|
Widget buildTablet(BuildContext context) {
|
||||||
final w = MediaQuery.of(context).size.width;
|
final w = MediaQuery.of(context).size.width;
|
||||||
final theme = Theme.of(context);
|
final theme = Theme.of(context);
|
||||||
final scale = theme.extension<ScaleScheme>()!;
|
final scale = theme.extension<ScaleScheme>()!;
|
||||||
|
|
||||||
final children = [
|
final children = [
|
||||||
ConstrainedBox(
|
ConstrainedBox(
|
||||||
constraints: const BoxConstraints(minWidth: 300, maxWidth: 300),
|
constraints: const BoxConstraints(minWidth: 300, maxWidth: 300),
|
||||||
child: ConstrainedBox(
|
child: ConstrainedBox(
|
||||||
constraints: BoxConstraints(maxWidth: w / 2),
|
constraints: BoxConstraints(maxWidth: w / 2),
|
||||||
child: buildTabletLeftPane())),
|
child: buildTabletLeftPane(context))),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: 2,
|
width: 2,
|
||||||
height: double.infinity,
|
height: double.infinity,
|
||||||
child: ColoredBox(color: scale.primaryScale.hoverBorder)),
|
child: ColoredBox(color: scale.primaryScale.hoverBorder)),
|
||||||
Expanded(child: buildTabletRightPane()),
|
Expanded(child: buildTabletRightPane(context)),
|
||||||
];
|
];
|
||||||
|
|
||||||
return Row(
|
return Row(
|
||||||
children: children,
|
children: children,
|
||||||
);
|
);
|
||||||
});
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@ -225,11 +134,13 @@ xxx get rid of this whole function
|
|||||||
return waitingPage(context);
|
return waitingPage(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
return responsiveVisibility(
|
return RepositoryProvider.value(
|
||||||
context: context,
|
value: _contactInvitationRepository,
|
||||||
phone: false,
|
child: responsiveVisibility(
|
||||||
)
|
context: context,
|
||||||
? buildTablet()
|
phone: false,
|
||||||
: buildPhone();
|
)
|
||||||
|
? buildTablet(context)
|
||||||
|
: buildPhone(context));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,34 +5,17 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_translate/flutter_translate.dart';
|
import 'package:flutter_translate/flutter_translate.dart';
|
||||||
import 'package:veilid_support/veilid_support.dart';
|
import 'package:veilid_support/veilid_support.dart';
|
||||||
|
|
||||||
|
import '../../../../account_manager/account_manager.dart';
|
||||||
import '../../../../proto/proto.dart' as proto;
|
import '../../../../proto/proto.dart' as proto;
|
||||||
import '../../../account_manager/account_manager.dart';
|
import '../../../../theme/theme.dart';
|
||||||
import '../../../contact_invitation/contact_invitation.dart';
|
|
||||||
import '../../../contacts/contacts.dart';
|
|
||||||
import '../../../theme/theme.dart';
|
|
||||||
|
|
||||||
class AccountPage extends StatefulWidget {
|
class AccountPage extends StatefulWidget {
|
||||||
const AccountPage({
|
const AccountPage({
|
||||||
required this.localAccounts,
|
|
||||||
required this.activeUserLogin,
|
|
||||||
required this.account,
|
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
final IList<LocalAccount> localAccounts;
|
|
||||||
final TypedKey activeUserLogin;
|
|
||||||
final proto.Account account;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
AccountPageState createState() => AccountPageState();
|
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> {
|
class AccountPageState extends State<AccountPage> {
|
||||||
|
@ -1,7 +1,3 @@
|
|||||||
export 'home/home_account_ready/chat_only.dart';
|
|
||||||
export 'default_app_bar.dart';
|
export 'default_app_bar.dart';
|
||||||
export 'edit_account.dart';
|
export 'home/home.dart';
|
||||||
export 'edit_contact.dart';
|
|
||||||
export 'home.dart';
|
|
||||||
export 'index.dart';
|
export 'index.dart';
|
||||||
export 'main_pager/main_pager.dart';
|
|
||||||
|
@ -5,18 +5,13 @@
|
|||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:fast_immutable_collections/fast_immutable_collections.dart';
|
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 '../../proto/proto.dart' as proto;
|
||||||
|
|
||||||
import '../../tools/tools.dart';
|
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 {
|
class Conversation {
|
||||||
Conversation._(
|
Conversation._(
|
||||||
|
@ -7,3 +7,4 @@ export 'src/dht_record_crypto.dart';
|
|||||||
export 'src/dht_record_cubit.dart';
|
export 'src/dht_record_cubit.dart';
|
||||||
export 'src/dht_record_pool.dart';
|
export 'src/dht_record_pool.dart';
|
||||||
export 'src/dht_short_array.dart';
|
export 'src/dht_short_array.dart';
|
||||||
|
export 'src/dht_short_array_cubit.dart';
|
||||||
|
@ -7,12 +7,32 @@ import '../../veilid_support.dart';
|
|||||||
|
|
||||||
class DHTShortArrayCubit<T> extends Cubit<AsyncValue<IList<T>>> {
|
class DHTShortArrayCubit<T> extends Cubit<AsyncValue<IList<T>>> {
|
||||||
DHTShortArrayCubit({
|
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 DHTShortArray shortArray,
|
||||||
required T Function(List<int> data) decodeElement,
|
required T Function(List<int> data) decodeElement,
|
||||||
}) : _shortArray = shortArray,
|
}) : _shortArray = shortArray,
|
||||||
_decodeElement = decodeElement,
|
_decodeElement = decodeElement,
|
||||||
_wantsUpdate = false,
|
_wantsUpdate = false,
|
||||||
_isUpdating = false,
|
_isUpdating = false,
|
||||||
|
_wantsCloseRecord = false,
|
||||||
super(const AsyncValue.loading()) {
|
super(const AsyncValue.loading()) {
|
||||||
// Make initial state update
|
// Make initial state update
|
||||||
_update();
|
_update();
|
||||||
@ -65,12 +85,16 @@ class DHTShortArrayCubit<T> extends Cubit<AsyncValue<IList<T>>> {
|
|||||||
Future<void> close() async {
|
Future<void> close() async {
|
||||||
await _subscription?.cancel();
|
await _subscription?.cancel();
|
||||||
_subscription = null;
|
_subscription = null;
|
||||||
|
if (_wantsCloseRecord) {
|
||||||
|
await _shortArray.close();
|
||||||
|
}
|
||||||
await super.close();
|
await super.close();
|
||||||
}
|
}
|
||||||
|
|
||||||
final DHTShortArray _shortArray;
|
late final DHTShortArray _shortArray;
|
||||||
final T Function(List<int> data) _decodeElement;
|
final T Function(List<int> data) _decodeElement;
|
||||||
StreamSubscription<void>? _subscription;
|
StreamSubscription<void>? _subscription;
|
||||||
bool _wantsUpdate;
|
bool _wantsUpdate;
|
||||||
bool _isUpdating;
|
bool _isUpdating;
|
||||||
|
bool _wantsCloseRecord;
|
||||||
}
|
}
|
||||||
|
@ -1014,7 +1014,7 @@ packages:
|
|||||||
source: hosted
|
source: hosted
|
||||||
version: "3.1.0"
|
version: "3.1.0"
|
||||||
provider:
|
provider:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: provider
|
name: provider
|
||||||
sha256: "9a96a0a19b594dbc5bf0f1f27d2bc67d5f95957359b461cd9feb44ed6ae75096"
|
sha256: "9a96a0a19b594dbc5bf0f1f27d2bc67d5f95957359b461cd9feb44ed6ae75096"
|
||||||
|
@ -58,6 +58,7 @@ dependencies:
|
|||||||
pinput: ^3.0.1
|
pinput: ^3.0.1
|
||||||
preload_page_view: ^0.2.0
|
preload_page_view: ^0.2.0
|
||||||
protobuf: ^3.0.0
|
protobuf: ^3.0.0
|
||||||
|
provider: ^6.1.1
|
||||||
qr_code_dart_scan: ^0.7.2
|
qr_code_dart_scan: ^0.7.2
|
||||||
qr_flutter: ^4.1.0
|
qr_flutter: ^4.1.0
|
||||||
quickalert: ^1.0.1
|
quickalert: ^1.0.1
|
||||||
|
Loading…
x
Reference in New Issue
Block a user