mirror of
https://github.com/ZhuJHua/moodiary.git
synced 2026-04-04 23:29:01 +08:00
feat: add code scanning to import support
This commit is contained in:
@@ -25,8 +25,8 @@ pluginManagement {
|
||||
|
||||
plugins {
|
||||
id "dev.flutter.flutter-plugin-loader" version "1.0.0"
|
||||
id "com.android.application" version '8.9.0' apply false
|
||||
id "org.jetbrains.kotlin.android" version "2.1.10" apply false
|
||||
id "com.android.application" version '8.9.1' apply false
|
||||
id "org.jetbrains.kotlin.android" version "2.1.20" apply false
|
||||
}
|
||||
|
||||
include ":app"
|
||||
|
||||
@@ -95,6 +95,9 @@ PODS:
|
||||
- Mantle/extobjc (2.2.0)
|
||||
- media_kit_video (0.0.1):
|
||||
- Flutter
|
||||
- mobile_scanner (7.0.0):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- moodiary_rust (0.0.1):
|
||||
- Flutter
|
||||
- network_info_plus (0.0.1):
|
||||
@@ -178,6 +181,7 @@ DEPENDENCIES:
|
||||
- isar_flutter_libs (from `.symlinks/plugins/isar_flutter_libs/ios`)
|
||||
- local_auth_darwin (from `.symlinks/plugins/local_auth_darwin/darwin`)
|
||||
- media_kit_video (from `.symlinks/plugins/media_kit_video/ios`)
|
||||
- mobile_scanner (from `.symlinks/plugins/mobile_scanner/darwin`)
|
||||
- moodiary_rust (from `.symlinks/plugins/moodiary_rust/ios`)
|
||||
- network_info_plus (from `.symlinks/plugins/network_info_plus/ios`)
|
||||
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
||||
@@ -246,6 +250,8 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/local_auth_darwin/darwin"
|
||||
media_kit_video:
|
||||
:path: ".symlinks/plugins/media_kit_video/ios"
|
||||
mobile_scanner:
|
||||
:path: ".symlinks/plugins/mobile_scanner/darwin"
|
||||
moodiary_rust:
|
||||
:path: ".symlinks/plugins/moodiary_rust/ios"
|
||||
network_info_plus:
|
||||
@@ -301,6 +307,7 @@ SPEC CHECKSUMS:
|
||||
local_auth_darwin: 553ce4f9b16d3fdfeafce9cf042e7c9f77c1c391
|
||||
Mantle: c5aa8794a29a022dfbbfc9799af95f477a69b62d
|
||||
media_kit_video: f3b0d035d89def15cfbbcf7dc2ae278f201e2f83
|
||||
mobile_scanner: 9157936403f5a0644ca3779a38ff8404c5434a93
|
||||
moodiary_rust: e75b3fb63e53d3ba5cfed0edf0b6df5f98c4c5f1
|
||||
network_info_plus: cf61925ab5205dce05a4f0895989afdb6aade5fc
|
||||
OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94
|
||||
|
||||
185
lib/components/base/expand_tap_area.dart
Normal file
185
lib/components/base/expand_tap_area.dart
Normal file
@@ -0,0 +1,185 @@
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
|
||||
Color debugPaintExpandAreaColor = const Color(
|
||||
0xFFFF0000,
|
||||
).withValues(alpha: 0.03);
|
||||
|
||||
Color debugPaintClipAreaColor = const Color(0xFF0000FF).withValues(alpha: 0.02);
|
||||
|
||||
class ExpandTapWidget extends SingleChildRenderObjectWidget {
|
||||
const ExpandTapWidget({
|
||||
super.key,
|
||||
super.child,
|
||||
required this.onTap,
|
||||
required this.tapPadding,
|
||||
});
|
||||
|
||||
final VoidCallback onTap;
|
||||
final EdgeInsets tapPadding;
|
||||
|
||||
@override
|
||||
RenderObject createRenderObject(BuildContext context) =>
|
||||
_ExpandTapRenderBox(onTap: onTap, tapPadding: tapPadding);
|
||||
|
||||
@override
|
||||
void updateRenderObject(BuildContext context, RenderBox renderObject) {
|
||||
renderObject as _ExpandTapRenderBox;
|
||||
if (renderObject.tapPadding != tapPadding) {
|
||||
renderObject.tapPadding = tapPadding;
|
||||
}
|
||||
if (renderObject.onTap != onTap) {
|
||||
renderObject.onTap = onTap;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class _TmpGestureArenaMember extends GestureArenaMember {
|
||||
_TmpGestureArenaMember({required this.onTap});
|
||||
|
||||
final VoidCallback onTap;
|
||||
|
||||
@override
|
||||
void acceptGesture(int key) {
|
||||
onTap();
|
||||
}
|
||||
|
||||
@override
|
||||
void rejectGesture(int key) {}
|
||||
}
|
||||
|
||||
class _ExpandTapRenderBox extends RenderBox
|
||||
with RenderObjectWithChildMixin<RenderBox> {
|
||||
_ExpandTapRenderBox({
|
||||
required VoidCallback onTap,
|
||||
required EdgeInsets tapPadding,
|
||||
}) : _onTap = onTap,
|
||||
_tapPadding = tapPadding;
|
||||
|
||||
VoidCallback _onTap;
|
||||
EdgeInsets _tapPadding;
|
||||
|
||||
set onTap(VoidCallback value) {
|
||||
if (_onTap != value) {
|
||||
_onTap = value;
|
||||
}
|
||||
}
|
||||
|
||||
set tapPadding(EdgeInsets value) {
|
||||
if (_tapPadding == value) return;
|
||||
_tapPadding = value;
|
||||
markNeedsPaint();
|
||||
}
|
||||
|
||||
EdgeInsets get tapPadding => _tapPadding;
|
||||
|
||||
VoidCallback get onTap => _onTap;
|
||||
|
||||
@override
|
||||
void performLayout() {
|
||||
child!.layout(constraints, parentUsesSize: true);
|
||||
size = child!.size;
|
||||
if (size.isEmpty) {
|
||||
_tapPadding = EdgeInsets.zero;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void paint(PaintingContext context, Offset offset) {
|
||||
if (child != null) {
|
||||
final BoxParentData childParentData = child!.parentData! as BoxParentData;
|
||||
context.paintChild(child!, childParentData.offset + offset);
|
||||
}
|
||||
assert(() {
|
||||
debugPaintExpandArea(context, offset);
|
||||
return true;
|
||||
}());
|
||||
}
|
||||
|
||||
void debugPaintExpandArea(PaintingContext context, Offset offset) {
|
||||
if (size.isEmpty) return;
|
||||
|
||||
final RenderBox parentBox = parent as RenderBox;
|
||||
|
||||
Offset parentPosition = Offset.zero;
|
||||
parentPosition = offset - localToGlobal(Offset.zero, ancestor: parentBox);
|
||||
|
||||
final Size parentSize = parentBox.size;
|
||||
final Rect parentRect = Rect.fromLTWH(
|
||||
parentPosition.dx,
|
||||
parentPosition.dy,
|
||||
parentSize.width,
|
||||
parentSize.height,
|
||||
);
|
||||
final BoxParentData childParentData = child!.parentData! as BoxParentData;
|
||||
final Offset paintOffset =
|
||||
childParentData.offset + offset - tapPadding.topLeft;
|
||||
final Rect paintRect = Rect.fromLTWH(
|
||||
paintOffset.dx,
|
||||
paintOffset.dy,
|
||||
size.width + tapPadding.horizontal,
|
||||
size.height + tapPadding.vertical,
|
||||
);
|
||||
final Paint paint =
|
||||
Paint()
|
||||
..style = PaintingStyle.fill
|
||||
..strokeWidth = 1.0
|
||||
..color = debugPaintExpandAreaColor;
|
||||
|
||||
final Paint paint2 =
|
||||
Paint()
|
||||
..style = PaintingStyle.fill
|
||||
..strokeWidth = 1.0
|
||||
..color = debugPaintClipAreaColor;
|
||||
context.canvas.drawRect(paintRect, paint);
|
||||
context.canvas.drawRect(paintRect.intersect(parentRect), paint2);
|
||||
}
|
||||
|
||||
@override
|
||||
void handleEvent(PointerEvent event, BoxHitTestEntry entry) {
|
||||
if (event is PointerDownEvent) {
|
||||
final _TmpGestureArenaMember member = _TmpGestureArenaMember(
|
||||
onTap: onTap,
|
||||
);
|
||||
GestureBinding.instance.gestureArena.add(event.pointer, member);
|
||||
} else if (event is PointerUpEvent) {
|
||||
GestureBinding.instance.gestureArena.sweep(event.pointer);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool hitTestSelf(Offset position) => true;
|
||||
|
||||
@override
|
||||
bool hitTestChildren(BoxHitTestResult result, {Offset? position}) {
|
||||
visitChildren((child) {
|
||||
if (child is RenderBox) {
|
||||
final BoxParentData parentData = child.parentData! as BoxParentData;
|
||||
if (child.hitTest(result, position: position! - parentData.offset)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
@override
|
||||
bool hitTest(BoxHitTestResult result, {Offset? position}) {
|
||||
final Rect expandRect = Rect.fromLTWH(
|
||||
0 - tapPadding.left,
|
||||
0 - tapPadding.top,
|
||||
size.width + tapPadding.right + tapPadding.left,
|
||||
size.height + tapPadding.top + tapPadding.bottom,
|
||||
);
|
||||
if (expandRect.contains(position!)) {
|
||||
final bool hitTarget =
|
||||
hitTestChildren(result, position: position) || hitTestSelf(position);
|
||||
if (hitTarget) {
|
||||
result.add(BoxHitTestEntry(this, position));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
64
lib/components/base/popup.dart
Normal file
64
lib/components/base/popup.dart
Normal file
@@ -0,0 +1,64 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:moodiary/common/values/border.dart';
|
||||
|
||||
class _TrianglePainter extends CustomPainter {
|
||||
final Color color;
|
||||
|
||||
final Size size;
|
||||
|
||||
_TrianglePainter({required this.color, required this.size});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final paint =
|
||||
Paint()
|
||||
..color = color
|
||||
..style = PaintingStyle.fill;
|
||||
|
||||
final path =
|
||||
Path()
|
||||
..moveTo(0, size.height)
|
||||
..lineTo(size.width, size.height)
|
||||
..lineTo(size.width / 2, 0)
|
||||
..close();
|
||||
|
||||
canvas.drawPath(path, paint);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant _TrianglePainter oldDelegate) => false;
|
||||
}
|
||||
|
||||
void showPopupWidget({
|
||||
required BuildContext targetContext,
|
||||
required Widget child,
|
||||
}) {
|
||||
SmartDialog.showAttach(
|
||||
targetContext: targetContext,
|
||||
maskColor: Colors.transparent,
|
||||
builder: (context) {
|
||||
return Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
CustomPaint(
|
||||
size: const Size(12, 6),
|
||||
painter: _TrianglePainter(
|
||||
color: context.theme.colorScheme.surfaceContainer,
|
||||
size: const Size(12, 6),
|
||||
),
|
||||
),
|
||||
Container(
|
||||
padding: const EdgeInsets.all(6.0),
|
||||
decoration: BoxDecoration(
|
||||
color: context.theme.colorScheme.surfaceContainer,
|
||||
borderRadius: AppBorderRadius.mediumBorderRadius,
|
||||
),
|
||||
child: child,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
134
lib/components/base/qr/qr_code.dart
Normal file
134
lib/components/base/qr/qr_code.dart
Normal file
@@ -0,0 +1,134 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:moodiary/utils/aes_util.dart';
|
||||
import 'package:qr_flutter/qr_flutter.dart';
|
||||
|
||||
class EncryptQrCode extends StatefulWidget {
|
||||
final String data;
|
||||
final double size;
|
||||
final Duration validDuration;
|
||||
final String? prefix;
|
||||
|
||||
const EncryptQrCode({
|
||||
super.key,
|
||||
required this.data,
|
||||
this.size = 64,
|
||||
this.validDuration = const Duration(minutes: 2),
|
||||
this.prefix,
|
||||
});
|
||||
|
||||
@override
|
||||
State<EncryptQrCode> createState() => _EncryptQrCodeState();
|
||||
}
|
||||
|
||||
class _EncryptQrCodeState extends State<EncryptQrCode> {
|
||||
Uint8List? encryptedData;
|
||||
late int expireAt; // Unix 时间戳
|
||||
bool isExpired = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_encryptData();
|
||||
}
|
||||
|
||||
Future<void> _encryptData() async {
|
||||
final now = DateTime.now().millisecondsSinceEpoch ~/ 1000;
|
||||
expireAt = now + widget.validDuration.inSeconds;
|
||||
|
||||
final aesData = await AesUtil.encryptWithTimeWindow(
|
||||
data: '${widget.prefix}${widget.data}',
|
||||
validDuration: widget.validDuration,
|
||||
);
|
||||
|
||||
setState(() {
|
||||
isExpired = false;
|
||||
encryptedData = aesData;
|
||||
});
|
||||
|
||||
_startExpirationChecker();
|
||||
}
|
||||
|
||||
void _startExpirationChecker() {
|
||||
Timer.periodic(const Duration(seconds: 1), (timer) {
|
||||
final now = DateTime.now().millisecondsSinceEpoch ~/ 1000;
|
||||
if (now >= expireAt) {
|
||||
setState(() {
|
||||
isExpired = true;
|
||||
});
|
||||
timer.cancel();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Widget _buildQrChild() {
|
||||
if (encryptedData == null) {
|
||||
return Center(
|
||||
key: const ValueKey('loading'),
|
||||
child: CircularProgressIndicator(
|
||||
color: context.theme.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
if (isExpired) {
|
||||
return GestureDetector(
|
||||
key: const ValueKey('expired'),
|
||||
onTap: _encryptData,
|
||||
child: Center(
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.error_rounded,
|
||||
color: context.theme.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'已过期',
|
||||
style: context.textTheme.labelMedium?.copyWith(
|
||||
color: context.theme.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return QrImageView(
|
||||
key: const ValueKey('qr'),
|
||||
data: base64Encode(encryptedData!),
|
||||
size: widget.size,
|
||||
backgroundColor: Colors.transparent,
|
||||
dataModuleStyle: QrDataModuleStyle(
|
||||
color: context.theme.colorScheme.onSurface,
|
||||
dataModuleShape: QrDataModuleShape.circle,
|
||||
),
|
||||
eyeStyle: QrEyeStyle(
|
||||
color: context.theme.colorScheme.onSurface,
|
||||
eyeShape: QrEyeShape.circle,
|
||||
),
|
||||
gapless: false,
|
||||
padding: EdgeInsets.zero,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return SizedBox(
|
||||
width: widget.size,
|
||||
height: widget.size,
|
||||
child: AnimatedSwitcher(
|
||||
duration: Durations.medium2,
|
||||
switchInCurve: Curves.easeIn,
|
||||
switchOutCurve: Curves.easeOut,
|
||||
child: _buildQrChild(),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
334
lib/components/base/qr/qr_scanner.dart
Normal file
334
lib/components/base/qr/qr_scanner.dart
Normal file
@@ -0,0 +1,334 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:dartx/dartx.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:mobile_scanner/mobile_scanner.dart';
|
||||
import 'package:moodiary/l10n/l10n.dart';
|
||||
import 'package:moodiary/router/app_pages.dart';
|
||||
import 'package:moodiary/utils/aes_util.dart';
|
||||
import 'package:moodiary/utils/log_util.dart';
|
||||
import 'package:moodiary/utils/notice_util.dart';
|
||||
import 'package:throttling/throttling.dart';
|
||||
|
||||
Future<String?> showQrScanner({
|
||||
required BuildContext context,
|
||||
Duration? validDuration,
|
||||
String? prefix,
|
||||
}) async {
|
||||
return Navigator.push<String?>(
|
||||
context,
|
||||
MoodiaryFadeInPageRoute(
|
||||
builder: (context) {
|
||||
return QrScanner(validDuration: validDuration, prefix: prefix);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
class QrScanner extends StatefulWidget {
|
||||
final Duration? validDuration;
|
||||
|
||||
final String? prefix;
|
||||
|
||||
const QrScanner({super.key, this.validDuration, this.prefix});
|
||||
|
||||
@override
|
||||
State<QrScanner> createState() => _QrScannerState();
|
||||
}
|
||||
|
||||
class _QrScannerState extends State<QrScanner>
|
||||
with SingleTickerProviderStateMixin {
|
||||
late final AnimationController _animationController = AnimationController(
|
||||
vsync: this,
|
||||
duration: Durations.long4,
|
||||
)..repeat(reverse: true);
|
||||
|
||||
late final Animation<double> _curvedAnimation = CurvedAnimation(
|
||||
parent: _animationController,
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
|
||||
late final MobileScannerController _scannerController =
|
||||
MobileScannerController(
|
||||
invertImage: true,
|
||||
autoStart: false,
|
||||
cameraResolution: const Size.square(640),
|
||||
formats: [BarcodeFormat.qrCode],
|
||||
);
|
||||
|
||||
late final Throttling _throttling = Throttling();
|
||||
|
||||
late final AppLifecycleListener _appLifecycleListener;
|
||||
|
||||
StreamSubscription<Object?>? _subscription;
|
||||
|
||||
late final ValueNotifier<TorchState> _torchState = ValueNotifier(
|
||||
TorchState.unavailable,
|
||||
);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_subscription = _scannerController.barcodes.listen(_handleBarcode);
|
||||
|
||||
_appLifecycleListener = AppLifecycleListener(
|
||||
onStateChange: (state) {
|
||||
if (!_scannerController.value.hasCameraPermission) {
|
||||
return;
|
||||
}
|
||||
switch (state) {
|
||||
case AppLifecycleState.detached:
|
||||
case AppLifecycleState.hidden:
|
||||
case AppLifecycleState.paused:
|
||||
return;
|
||||
case AppLifecycleState.resumed:
|
||||
_subscription = _scannerController.barcodes.listen(_handleBarcode);
|
||||
unawaited(_scannerController.start());
|
||||
case AppLifecycleState.inactive:
|
||||
unawaited(_subscription?.cancel());
|
||||
_subscription = null;
|
||||
unawaited(_scannerController.stop());
|
||||
}
|
||||
},
|
||||
);
|
||||
_scannerController.addListener(() {
|
||||
if (_scannerController.value.torchState != _torchState.value) {
|
||||
_torchState.value = _scannerController.value.torchState;
|
||||
}
|
||||
});
|
||||
unawaited(_scannerController.start());
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() async {
|
||||
unawaited(_subscription?.cancel());
|
||||
_subscription = null;
|
||||
_animationController.dispose();
|
||||
_throttling.close();
|
||||
_appLifecycleListener.dispose();
|
||||
super.dispose();
|
||||
await _scannerController.dispose();
|
||||
}
|
||||
|
||||
void _handleBarcode(BarcodeCapture value) {
|
||||
_throttling.throttle(() async {
|
||||
final raw = value.barcodes.firstOrNull?.rawValue;
|
||||
if (raw == null) return;
|
||||
|
||||
try {
|
||||
if (widget.validDuration != null) {
|
||||
final resBytes = base64Decode(raw);
|
||||
final decrypted = await AesUtil.decryptWithTimeWindow(
|
||||
encryptedData: resBytes,
|
||||
validDuration: widget.validDuration!,
|
||||
);
|
||||
if (decrypted.isNullOrBlank) {
|
||||
_handleInvalidQr();
|
||||
return;
|
||||
}
|
||||
if (widget.prefix.isNotNullOrBlank) {
|
||||
if (!decrypted!.startsWith(widget.prefix!)) {
|
||||
_handleInvalidQr();
|
||||
return;
|
||||
}
|
||||
final realData = decrypted.substring(widget.prefix!.length);
|
||||
if (mounted) Navigator.pop(context, realData);
|
||||
} else {
|
||||
if (mounted) Navigator.pop(context, decrypted);
|
||||
}
|
||||
} else {
|
||||
if (raw.isNullOrBlank) {
|
||||
_handleInvalidQr();
|
||||
} else {
|
||||
if (mounted) Navigator.pop(context, raw);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
logger.d(e);
|
||||
_handleInvalidQr();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void _handleInvalidQr() {
|
||||
if (mounted) {
|
||||
toast.info(message: context.l10n.qrCodeInvalid);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final scanWindow = Rect.fromCenter(
|
||||
center: MediaQuery.sizeOf(context).center(Offset.zero),
|
||||
width: 200,
|
||||
height: 200,
|
||||
);
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
backgroundColor: Colors.transparent,
|
||||
leading: BackButton(
|
||||
color: Colors.white,
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
),
|
||||
extendBodyBehindAppBar: true,
|
||||
body: Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
MobileScanner(
|
||||
scanWindow: scanWindow,
|
||||
controller: _scannerController,
|
||||
overlayBuilder: (context, constraints) {
|
||||
return AnimatedBuilder(
|
||||
animation: _curvedAnimation,
|
||||
builder: (context, child) {
|
||||
return Stack(
|
||||
children: [
|
||||
CustomPaint(
|
||||
size: constraints.biggest,
|
||||
painter: _ScannerOverlayPainter(
|
||||
centerSize: _curvedAnimation.value * 20 + 200,
|
||||
),
|
||||
),
|
||||
Center(
|
||||
child: CustomPaint(
|
||||
size: Size.square(_curvedAnimation.value * 20 + 200),
|
||||
painter: _CornerBorderPainter(
|
||||
color: Colors.white,
|
||||
strokeWidth: 4,
|
||||
cornerLength: _curvedAnimation.value * 2 + 20,
|
||||
radius: 12,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
Positioned(
|
||||
bottom: MediaQuery.sizeOf(context).height / 2 - 300,
|
||||
child: ValueListenableBuilder(
|
||||
valueListenable: _torchState,
|
||||
builder: (context, value, child) {
|
||||
if (value == TorchState.unavailable) {
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
return IconButton(
|
||||
onPressed: () async {
|
||||
await _scannerController.toggleTorch();
|
||||
},
|
||||
iconSize: 56,
|
||||
icon: switch (_torchState.value) {
|
||||
TorchState.auto => const Icon(
|
||||
Icons.flash_auto_rounded,
|
||||
color: Colors.white,
|
||||
),
|
||||
TorchState.off => const Icon(
|
||||
Icons.flashlight_off_rounded,
|
||||
color: Colors.white54,
|
||||
),
|
||||
TorchState.on => const Icon(
|
||||
Icons.flashlight_on_rounded,
|
||||
color: Colors.white,
|
||||
),
|
||||
TorchState.unavailable => throw UnimplementedError(),
|
||||
},
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _CornerBorderPainter extends CustomPainter {
|
||||
final Color color;
|
||||
final double strokeWidth;
|
||||
final double cornerLength;
|
||||
final double radius;
|
||||
|
||||
_CornerBorderPainter({
|
||||
required this.color,
|
||||
this.strokeWidth = 2,
|
||||
this.cornerLength = 20,
|
||||
this.radius = 0,
|
||||
});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final paint =
|
||||
Paint()
|
||||
..color = color
|
||||
..strokeWidth = strokeWidth
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeCap = StrokeCap.round;
|
||||
|
||||
final path = Path();
|
||||
|
||||
final double w = size.width;
|
||||
final double h = size.height;
|
||||
final r = radius;
|
||||
|
||||
path.moveTo(0, r + cornerLength);
|
||||
path.lineTo(0, r);
|
||||
path.quadraticBezierTo(0, 0, r, 0);
|
||||
path.lineTo(r + cornerLength, 0);
|
||||
|
||||
path.moveTo(w - r - cornerLength, 0);
|
||||
path.lineTo(w - r, 0);
|
||||
path.quadraticBezierTo(w, 0, w, r);
|
||||
path.lineTo(w, r + cornerLength);
|
||||
|
||||
path.moveTo(w, h - r - cornerLength);
|
||||
path.lineTo(w, h - r);
|
||||
path.quadraticBezierTo(w, h, w - r, h);
|
||||
path.lineTo(w - r - cornerLength, h);
|
||||
|
||||
path.moveTo(r + cornerLength, h);
|
||||
path.lineTo(r, h);
|
||||
path.quadraticBezierTo(0, h, 0, h - r);
|
||||
path.lineTo(0, h - r - cornerLength);
|
||||
|
||||
canvas.drawPath(path, paint);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
|
||||
}
|
||||
|
||||
class _ScannerOverlayPainter extends CustomPainter {
|
||||
final double centerSize;
|
||||
|
||||
_ScannerOverlayPainter({required this.centerSize});
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
final paint =
|
||||
Paint()
|
||||
..color = Colors.black54
|
||||
..style = PaintingStyle.fill;
|
||||
|
||||
final outer = Path()..addRect(Rect.fromLTWH(0, 0, size.width, size.height));
|
||||
|
||||
final scanSize = centerSize;
|
||||
final left = (size.width - scanSize) / 2;
|
||||
final top = (size.height - scanSize) / 2;
|
||||
final scanRect = Rect.fromLTWH(left, top, scanSize, scanSize);
|
||||
|
||||
final hole = Path()..addRRect(RRect.fromRectXY(scanRect, 12, 12));
|
||||
final overlay = Path.combine(PathOperation.difference, outer, hole);
|
||||
|
||||
canvas.drawPath(overlay, paint);
|
||||
}
|
||||
|
||||
@override
|
||||
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
|
||||
}
|
||||
169
lib/components/base/tile/qr_tile.dart
Normal file
169
lib/components/base/tile/qr_tile.dart
Normal file
@@ -0,0 +1,169 @@
|
||||
import 'package:adaptive_dialog/adaptive_dialog.dart';
|
||||
import 'package:dartx/dartx.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:moodiary/common/values/border.dart';
|
||||
import 'package:moodiary/components/base/popup.dart';
|
||||
import 'package:moodiary/components/base/qr/qr_code.dart';
|
||||
import 'package:moodiary/components/base/qr/qr_scanner.dart';
|
||||
import 'package:moodiary/components/base/tile/setting_tile.dart';
|
||||
import 'package:moodiary/l10n/l10n.dart';
|
||||
import 'package:moodiary/utils/notice_util.dart';
|
||||
|
||||
class QrInputTile extends StatelessWidget {
|
||||
final String title;
|
||||
final String? subtitle;
|
||||
|
||||
final String? prefix;
|
||||
|
||||
final String value;
|
||||
final bool withStyle;
|
||||
|
||||
final void Function(String)? onValue;
|
||||
|
||||
final Widget? leading;
|
||||
|
||||
final VoidCallback? onScan;
|
||||
final VoidCallback? onInput;
|
||||
|
||||
const QrInputTile({
|
||||
super.key,
|
||||
required this.title,
|
||||
this.subtitle,
|
||||
required this.value,
|
||||
this.onValue,
|
||||
this.withStyle = true,
|
||||
this.onScan,
|
||||
this.onInput,
|
||||
this.leading,
|
||||
this.prefix,
|
||||
});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
const validDuration = Duration(minutes: 2);
|
||||
return AdaptiveListTile(
|
||||
title: Text(title),
|
||||
leading: leading,
|
||||
subtitle:
|
||||
subtitle ??
|
||||
(value.isNotNullOrBlank
|
||||
? Text(context.l10n.hasOption)
|
||||
: Text(context.l10n.noOption)),
|
||||
tileColor:
|
||||
withStyle ? context.theme.colorScheme.surfaceContainerLow : null,
|
||||
shape:
|
||||
withStyle
|
||||
? const RoundedRectangleBorder(
|
||||
borderRadius: AppBorderRadius.mediumBorderRadius,
|
||||
)
|
||||
: null,
|
||||
trailing: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Builder(
|
||||
builder: (context) {
|
||||
return IconButton.filled(
|
||||
tooltip: context.l10n.genQrCodeTooltip,
|
||||
onPressed: () {
|
||||
if (value.isBlank) {
|
||||
toast.info(message: context.l10n.genQrCodeError1(title));
|
||||
return;
|
||||
}
|
||||
showPopupWidget(
|
||||
targetContext: context,
|
||||
child: EncryptQrCode(
|
||||
data: value,
|
||||
size: 96,
|
||||
prefix: prefix,
|
||||
validDuration: validDuration,
|
||||
),
|
||||
);
|
||||
},
|
||||
icon: Icon(
|
||||
Icons.qr_code_rounded,
|
||||
color: context.theme.colorScheme.onPrimary,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
IconButton.filled(
|
||||
tooltip: context.l10n.inputTooltip,
|
||||
onPressed: () async {
|
||||
final choice = await showDialog<String?>(
|
||||
context: context,
|
||||
builder: (context) {
|
||||
return SimpleDialog(
|
||||
title: Text(context.l10n.inputMethodTitle),
|
||||
children: [
|
||||
SimpleDialogOption(
|
||||
child: Row(
|
||||
spacing: 8.0,
|
||||
children: [
|
||||
const Icon(Icons.qr_code_scanner_rounded),
|
||||
Text(context.l10n.inputMethodScanQrCode),
|
||||
],
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.pop(context, 'qr');
|
||||
},
|
||||
),
|
||||
SimpleDialogOption(
|
||||
child: Row(
|
||||
spacing: 8.0,
|
||||
children: [
|
||||
const Icon(Icons.keyboard_rounded),
|
||||
Text(context.l10n.inputMethodHandelInput),
|
||||
],
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.pop(context, 'input');
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
if (choice == null) return;
|
||||
if (choice == 'qr' && context.mounted) {
|
||||
if (onScan != null) {
|
||||
onScan?.call();
|
||||
return;
|
||||
}
|
||||
final res = await showQrScanner(
|
||||
context: context,
|
||||
validDuration: validDuration,
|
||||
prefix: prefix,
|
||||
);
|
||||
if (res != null) {
|
||||
onValue?.call(res);
|
||||
}
|
||||
}
|
||||
if (choice == 'input' && context.mounted) {
|
||||
if (onInput != null) {
|
||||
onInput?.call();
|
||||
return;
|
||||
}
|
||||
final res = await showTextInputDialog(
|
||||
context: context,
|
||||
textFields: [DialogTextField(initialText: value)],
|
||||
title: title,
|
||||
message: context.l10n.getKeyFromConsole,
|
||||
style: AdaptiveStyle.material,
|
||||
);
|
||||
if (res != null && res.isNotEmpty) {
|
||||
final value = res[0];
|
||||
onValue?.call(value);
|
||||
}
|
||||
}
|
||||
},
|
||||
icon: Icon(
|
||||
Icons.input_rounded,
|
||||
color: context.theme.colorScheme.onPrimary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -40,6 +40,8 @@ class AdaptiveListTile extends StatelessWidget {
|
||||
this.isFirst,
|
||||
this.isLast,
|
||||
this.contentPadding,
|
||||
this.tileColor,
|
||||
this.shape,
|
||||
});
|
||||
|
||||
final dynamic title;
|
||||
@@ -58,6 +60,10 @@ class AdaptiveListTile extends StatelessWidget {
|
||||
|
||||
final EdgeInsets? contentPadding;
|
||||
|
||||
final Color? tileColor;
|
||||
|
||||
final ShapeBorder? shape;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
assert(
|
||||
@@ -77,16 +83,19 @@ class AdaptiveListTile extends StatelessWidget {
|
||||
realSubtitle = (subtitle as Text).data;
|
||||
}
|
||||
return ListTile(
|
||||
tileColor: tileColor,
|
||||
title:
|
||||
(realTitle is String)
|
||||
? AdaptiveText(realTitle, isTileTitle: true)
|
||||
: realTitle,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.vertical(
|
||||
top: isFirst == true ? const Radius.circular(12) : Radius.zero,
|
||||
bottom: isLast == true ? const Radius.circular(12) : Radius.zero,
|
||||
),
|
||||
),
|
||||
shape:
|
||||
shape ??
|
||||
RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.vertical(
|
||||
top: isFirst == true ? const Radius.circular(12) : Radius.zero,
|
||||
bottom: isLast == true ? const Radius.circular(12) : Radius.zero,
|
||||
),
|
||||
),
|
||||
contentPadding: contentPadding,
|
||||
subtitle:
|
||||
(realSubtitle is String)
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:moodiary/components/tile/setting_tile.dart';
|
||||
import 'package:moodiary/components/base/tile/setting_tile.dart';
|
||||
import 'package:moodiary/l10n/l10n.dart';
|
||||
import 'package:moodiary/utils/file_util.dart';
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:moodiary/components/tile/setting_tile.dart';
|
||||
import 'package:moodiary/components/base/tile/setting_tile.dart';
|
||||
import 'package:moodiary/l10n/l10n.dart';
|
||||
|
||||
import 'local_send_server_logic.dart';
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// import 'package:flutter/material.dart';
|
||||
// import 'package:intl/intl.dart';
|
||||
// import 'package:moodiary/common/values/icons.dart';
|
||||
// import 'package:moodiary/components/tile/setting_tile.dart';
|
||||
// import 'package:moodiary/components/base/tile/setting_tile.dart';
|
||||
// import 'package:moodiary/main.dart';
|
||||
// import 'package:moodiary/utils/function_extensions.dart';
|
||||
// import 'package:get/get.dart';
|
||||
|
||||
@@ -3,7 +3,7 @@ import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:moodiary/common/values/webdav.dart';
|
||||
import 'package:moodiary/components/base/loading.dart';
|
||||
import 'package:moodiary/components/tile/setting_tile.dart';
|
||||
import 'package:moodiary/components/base/tile/setting_tile.dart';
|
||||
import 'package:moodiary/l10n/l10n.dart';
|
||||
import 'package:moodiary/utils/webdav_util.dart';
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:moodiary/common/values/border.dart';
|
||||
import 'package:moodiary/common/values/webdav.dart';
|
||||
import 'package:moodiary/components/tile/setting_tile.dart';
|
||||
import 'package:moodiary/components/base/tile/setting_tile.dart';
|
||||
import 'package:moodiary/l10n/l10n.dart';
|
||||
|
||||
import 'web_dav_logic.dart';
|
||||
|
||||
@@ -1753,6 +1753,84 @@ abstract class AppLocalizations {
|
||||
/// In zh, this message translates to:
|
||||
/// **'加载中'**
|
||||
String get toastLoading;
|
||||
|
||||
/// No description provided for @genQrCodeError1.
|
||||
///
|
||||
/// In zh, this message translates to:
|
||||
/// **'请先配置 {name}'**
|
||||
String genQrCodeError1(Object name);
|
||||
|
||||
/// No description provided for @genQrCodeTooltip.
|
||||
///
|
||||
/// In zh, this message translates to:
|
||||
/// **'生成二维码'**
|
||||
String get genQrCodeTooltip;
|
||||
|
||||
/// No description provided for @qrCodeInvalid.
|
||||
///
|
||||
/// In zh, this message translates to:
|
||||
/// **'二维码无效'**
|
||||
String get qrCodeInvalid;
|
||||
|
||||
/// No description provided for @inputTooltip.
|
||||
///
|
||||
/// In zh, this message translates to:
|
||||
/// **'输入'**
|
||||
String get inputTooltip;
|
||||
|
||||
/// No description provided for @inputMethodTitle.
|
||||
///
|
||||
/// In zh, this message translates to:
|
||||
/// **'输入方式'**
|
||||
String get inputMethodTitle;
|
||||
|
||||
/// No description provided for @inputMethodScanQrCode.
|
||||
///
|
||||
/// In zh, this message translates to:
|
||||
/// **'扫描二维码'**
|
||||
String get inputMethodScanQrCode;
|
||||
|
||||
/// No description provided for @inputMethodHandelInput.
|
||||
///
|
||||
/// In zh, this message translates to:
|
||||
/// **'手动输入'**
|
||||
String get inputMethodHandelInput;
|
||||
|
||||
/// No description provided for @getKeyFromConsole.
|
||||
///
|
||||
/// In zh, this message translates to:
|
||||
/// **'请从对应控制台获取密钥'**
|
||||
String get getKeyFromConsole;
|
||||
|
||||
/// No description provided for @hasOption.
|
||||
///
|
||||
/// In zh, this message translates to:
|
||||
/// **'已配置'**
|
||||
String get hasOption;
|
||||
|
||||
/// No description provided for @noOption.
|
||||
///
|
||||
/// In zh, this message translates to:
|
||||
/// **'未配置'**
|
||||
String get noOption;
|
||||
|
||||
/// No description provided for @labQweather.
|
||||
///
|
||||
/// In zh, this message translates to:
|
||||
/// **'和风天气'**
|
||||
String get labQweather;
|
||||
|
||||
/// No description provided for @labTianditu.
|
||||
///
|
||||
/// In zh, this message translates to:
|
||||
/// **'天地图'**
|
||||
String get labTianditu;
|
||||
|
||||
/// No description provided for @labTencentCloud.
|
||||
///
|
||||
/// In zh, this message translates to:
|
||||
/// **'腾讯云'**
|
||||
String get labTencentCloud;
|
||||
}
|
||||
|
||||
class _AppLocalizationsDelegate
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
// ignore: unused_import
|
||||
import 'package:intl/intl.dart' as intl;
|
||||
|
||||
import 'app_localizations.dart';
|
||||
|
||||
// ignore_for_file: type=lint
|
||||
@@ -885,4 +884,46 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get toastLoading => 'Loading';
|
||||
|
||||
@override
|
||||
String genQrCodeError1(Object name) {
|
||||
return 'Please configure $name first';
|
||||
}
|
||||
|
||||
@override
|
||||
String get genQrCodeTooltip => 'Generate QR code';
|
||||
|
||||
@override
|
||||
String get qrCodeInvalid => 'QR code invalid';
|
||||
|
||||
@override
|
||||
String get inputTooltip => 'Input';
|
||||
|
||||
@override
|
||||
String get inputMethodTitle => 'Input method';
|
||||
|
||||
@override
|
||||
String get inputMethodScanQrCode => 'Scan QR code';
|
||||
|
||||
@override
|
||||
String get inputMethodHandelInput => 'Manual input';
|
||||
|
||||
@override
|
||||
String get getKeyFromConsole =>
|
||||
'Please get the key from the corresponding console';
|
||||
|
||||
@override
|
||||
String get hasOption => 'Configured';
|
||||
|
||||
@override
|
||||
String get noOption => 'Not configured';
|
||||
|
||||
@override
|
||||
String get labQweather => 'Qweather';
|
||||
|
||||
@override
|
||||
String get labTianditu => 'Tianditu';
|
||||
|
||||
@override
|
||||
String get labTencentCloud => 'Tencent Cloud';
|
||||
}
|
||||
|
||||
@@ -852,4 +852,45 @@ class AppLocalizationsZh extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get toastLoading => '加载中';
|
||||
|
||||
@override
|
||||
String genQrCodeError1(Object name) {
|
||||
return '请先配置 $name';
|
||||
}
|
||||
|
||||
@override
|
||||
String get genQrCodeTooltip => '生成二维码';
|
||||
|
||||
@override
|
||||
String get qrCodeInvalid => '二维码无效';
|
||||
|
||||
@override
|
||||
String get inputTooltip => '输入';
|
||||
|
||||
@override
|
||||
String get inputMethodTitle => '输入方式';
|
||||
|
||||
@override
|
||||
String get inputMethodScanQrCode => '扫描二维码';
|
||||
|
||||
@override
|
||||
String get inputMethodHandelInput => '手动输入';
|
||||
|
||||
@override
|
||||
String get getKeyFromConsole => '请从对应控制台获取密钥';
|
||||
|
||||
@override
|
||||
String get hasOption => '已配置';
|
||||
|
||||
@override
|
||||
String get noOption => '未配置';
|
||||
|
||||
@override
|
||||
String get labQweather => '和风天气';
|
||||
|
||||
@override
|
||||
String get labTianditu => '天地图';
|
||||
|
||||
@override
|
||||
String get labTencentCloud => '腾讯云';
|
||||
}
|
||||
|
||||
@@ -275,5 +275,18 @@
|
||||
"mediaVideoCount": "{count, plural, =1 {# Video} other {# Videos}}",
|
||||
"toastSuccess": "Success",
|
||||
"toastError": "Error",
|
||||
"toastLoading": "Loading"
|
||||
"toastLoading": "Loading",
|
||||
"genQrCodeError1": "Please configure {name} first",
|
||||
"genQrCodeTooltip": "Generate QR code",
|
||||
"qrCodeInvalid": "QR code invalid",
|
||||
"inputTooltip": "Input",
|
||||
"inputMethodTitle": "Input method",
|
||||
"inputMethodScanQrCode": "Scan QR code",
|
||||
"inputMethodHandelInput": "Manual input",
|
||||
"getKeyFromConsole": "Please get the key from the corresponding console",
|
||||
"hasOption": "Configured",
|
||||
"noOption": "Not configured",
|
||||
"labQweather": "Qweather",
|
||||
"labTianditu": "Tianditu",
|
||||
"labTencentCloud": "Tencent Cloud"
|
||||
}
|
||||
@@ -275,5 +275,18 @@
|
||||
"mediaVideoCount": "{count} 段视频",
|
||||
"toastSuccess": "成功",
|
||||
"toastError": "出错了",
|
||||
"toastLoading": "加载中"
|
||||
"toastLoading": "加载中",
|
||||
"genQrCodeError1": "请先配置 {name}",
|
||||
"genQrCodeTooltip": "生成二维码",
|
||||
"qrCodeInvalid": "二维码无效",
|
||||
"inputTooltip": "输入",
|
||||
"inputMethodTitle": "输入方式",
|
||||
"inputMethodScanQrCode": "扫描二维码",
|
||||
"inputMethodHandelInput": "手动输入",
|
||||
"getKeyFromConsole": "请从对应控制台获取密钥",
|
||||
"hasOption": "已配置",
|
||||
"noOption": "未配置",
|
||||
"labQweather": "和风天气",
|
||||
"labTianditu": "天地图",
|
||||
"labTencentCloud": "腾讯云"
|
||||
}
|
||||
@@ -90,4 +90,9 @@ class AboutLogic extends GetxController with GetSingleTickerProviderStateMixin {
|
||||
void toSponsor() {
|
||||
Get.toNamed(AppRoutes.sponsorPage);
|
||||
}
|
||||
|
||||
Future<void> toIcp() async {
|
||||
final uri = Uri.parse('https://beian.miit.gov.cn/');
|
||||
await launchUrl(uri, mode: LaunchMode.platformDefault);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,9 @@ import 'package:flutter/material.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:moodiary/components/base/button.dart';
|
||||
import 'package:moodiary/components/base/expand_tap_area.dart';
|
||||
import 'package:moodiary/components/base/text.dart';
|
||||
import 'package:moodiary/components/tile/setting_tile.dart';
|
||||
import 'package:moodiary/components/base/tile/setting_tile.dart';
|
||||
import 'package:moodiary/gen/assets.gen.dart';
|
||||
import 'package:moodiary/l10n/l10n.dart';
|
||||
import 'package:moodiary/utils/update_util.dart';
|
||||
@@ -91,6 +92,57 @@ class AboutPage extends StatelessWidget {
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildInfo() {
|
||||
return Column(
|
||||
spacing: 16.0,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
spacing: 4.0,
|
||||
children: [
|
||||
const FaIcon(
|
||||
FontAwesomeIcons.flutter,
|
||||
size: 16,
|
||||
color: Colors.lightBlue,
|
||||
),
|
||||
const SizedBox(height: 12, child: VerticalDivider(thickness: 2)),
|
||||
FaIcon(
|
||||
FontAwesomeIcons.dartLang,
|
||||
size: 16,
|
||||
color: context.theme.colorScheme.onSurface.withValues(
|
||||
alpha: 0.8,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12, child: VerticalDivider(thickness: 2)),
|
||||
FaIcon(
|
||||
FontAwesomeIcons.rust,
|
||||
size: 16,
|
||||
color: context.theme.colorScheme.onSurface.withValues(
|
||||
alpha: 0.8,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12, child: VerticalDivider(thickness: 2)),
|
||||
const FaIcon(
|
||||
FontAwesomeIcons.solidHeart,
|
||||
size: 16,
|
||||
color: Colors.pinkAccent,
|
||||
),
|
||||
],
|
||||
),
|
||||
ExpandTapWidget(
|
||||
tapPadding: const EdgeInsets.all(4.0),
|
||||
onTap: logic.toIcp,
|
||||
child: Text(
|
||||
'赣ICP备2022010939号-4A',
|
||||
style: context.textTheme.labelMedium?.copyWith(
|
||||
color: context.theme.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
return Stack(
|
||||
children: [
|
||||
Scaffold(
|
||||
@@ -98,114 +150,76 @@ class AboutPage extends StatelessWidget {
|
||||
title: Text(context.l10n.aboutTitle),
|
||||
leading: const PageBackButton(),
|
||||
),
|
||||
body: ListView(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
physics: const ClampingScrollPhysics(),
|
||||
children: [
|
||||
buildLogoTitle(),
|
||||
const SizedBox(height: 16.0),
|
||||
Card.outlined(
|
||||
color: context.theme.colorScheme.surfaceContainerLow,
|
||||
child: Column(
|
||||
children: [
|
||||
AdaptiveListTile(
|
||||
leading: const Icon(Icons.update_rounded),
|
||||
title: Text(context.l10n.aboutUpdate),
|
||||
isFirst: true,
|
||||
trailing: const Icon(Icons.chevron_right_rounded),
|
||||
onTap: () async {
|
||||
await UpdateUtil.checkShouldUpdate(
|
||||
state.appVersion,
|
||||
handle: true,
|
||||
);
|
||||
},
|
||||
),
|
||||
AdaptiveListTile(
|
||||
leading: const Icon(Icons.source_rounded),
|
||||
title: Text(context.l10n.aboutSource),
|
||||
trailing: const Icon(Icons.chevron_right_rounded),
|
||||
onTap: () async {
|
||||
await logic.toSource();
|
||||
},
|
||||
),
|
||||
AdaptiveListTile(
|
||||
leading: const Icon(Icons.file_copy_rounded),
|
||||
title: Text(context.l10n.aboutUserAgreement),
|
||||
trailing: const Icon(Icons.chevron_right_rounded),
|
||||
onTap: () {
|
||||
logic.toAgreement();
|
||||
},
|
||||
),
|
||||
AdaptiveListTile(
|
||||
leading: const Icon(Icons.privacy_tip_rounded),
|
||||
title: Text(context.l10n.aboutPrivacyPolicy),
|
||||
trailing: const Icon(Icons.chevron_right_rounded),
|
||||
onTap: () {
|
||||
logic.toPrivacy();
|
||||
},
|
||||
),
|
||||
AdaptiveListTile(
|
||||
leading: const Icon(Icons.bug_report_rounded),
|
||||
title: Text(context.l10n.aboutBugReport),
|
||||
trailing: const Icon(Icons.chevron_right_rounded),
|
||||
onTap: () async {
|
||||
await logic.toReportPage();
|
||||
},
|
||||
),
|
||||
AdaptiveListTile(
|
||||
leading: const Icon(Icons.attach_money_rounded),
|
||||
title: Text(context.l10n.aboutDonate),
|
||||
isLast: true,
|
||||
trailing: const Icon(Icons.chevron_right_rounded),
|
||||
onTap: logic.toSponsor,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16.0),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
spacing: 4.0,
|
||||
body: SingleChildScrollView(
|
||||
scrollDirection: Axis.vertical,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
spacing: 32.0,
|
||||
children: [
|
||||
const FaIcon(
|
||||
FontAwesomeIcons.flutter,
|
||||
size: 16,
|
||||
color: Colors.lightBlue,
|
||||
),
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
child: VerticalDivider(thickness: 2),
|
||||
),
|
||||
FaIcon(
|
||||
FontAwesomeIcons.dartLang,
|
||||
size: 16,
|
||||
color: context.theme.colorScheme.onSurface.withValues(
|
||||
alpha: 0.8,
|
||||
buildLogoTitle(),
|
||||
Card.outlined(
|
||||
color: context.theme.colorScheme.surfaceContainerLow,
|
||||
child: Column(
|
||||
children: [
|
||||
AdaptiveListTile(
|
||||
leading: const Icon(Icons.update_rounded),
|
||||
title: Text(context.l10n.aboutUpdate),
|
||||
isFirst: true,
|
||||
trailing: const Icon(Icons.chevron_right_rounded),
|
||||
onTap: () async {
|
||||
await UpdateUtil.checkShouldUpdate(
|
||||
state.appVersion,
|
||||
handle: true,
|
||||
);
|
||||
},
|
||||
),
|
||||
AdaptiveListTile(
|
||||
leading: const Icon(Icons.source_rounded),
|
||||
title: Text(context.l10n.aboutSource),
|
||||
trailing: const Icon(Icons.chevron_right_rounded),
|
||||
onTap: () async {
|
||||
await logic.toSource();
|
||||
},
|
||||
),
|
||||
AdaptiveListTile(
|
||||
leading: const Icon(Icons.file_copy_rounded),
|
||||
title: Text(context.l10n.aboutUserAgreement),
|
||||
trailing: const Icon(Icons.chevron_right_rounded),
|
||||
onTap: () {
|
||||
logic.toAgreement();
|
||||
},
|
||||
),
|
||||
AdaptiveListTile(
|
||||
leading: const Icon(Icons.privacy_tip_rounded),
|
||||
title: Text(context.l10n.aboutPrivacyPolicy),
|
||||
trailing: const Icon(Icons.chevron_right_rounded),
|
||||
onTap: () {
|
||||
logic.toPrivacy();
|
||||
},
|
||||
),
|
||||
AdaptiveListTile(
|
||||
leading: const Icon(Icons.bug_report_rounded),
|
||||
title: Text(context.l10n.aboutBugReport),
|
||||
trailing: const Icon(Icons.chevron_right_rounded),
|
||||
onTap: () async {
|
||||
await logic.toReportPage();
|
||||
},
|
||||
),
|
||||
AdaptiveListTile(
|
||||
leading: const Icon(Icons.attach_money_rounded),
|
||||
title: Text(context.l10n.aboutDonate),
|
||||
isLast: true,
|
||||
trailing: const Icon(Icons.chevron_right_rounded),
|
||||
onTap: logic.toSponsor,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
child: VerticalDivider(thickness: 2),
|
||||
),
|
||||
FaIcon(
|
||||
FontAwesomeIcons.rust,
|
||||
size: 16,
|
||||
color: context.theme.colorScheme.onSurface.withValues(
|
||||
alpha: 0.8,
|
||||
),
|
||||
),
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
child: VerticalDivider(thickness: 2),
|
||||
),
|
||||
const FaIcon(
|
||||
FontAwesomeIcons.solidHeart,
|
||||
size: 16,
|
||||
color: Colors.pinkAccent,
|
||||
),
|
||||
buildInfo(),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
Align(
|
||||
|
||||
@@ -2,8 +2,8 @@ import 'package:adaptive_dialog/adaptive_dialog.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:moodiary/components/base/button.dart';
|
||||
import 'package:moodiary/components/base/tile/setting_tile.dart';
|
||||
import 'package:moodiary/components/local_send/local_send_view.dart';
|
||||
import 'package:moodiary/components/tile/setting_tile.dart';
|
||||
import 'package:moodiary/components/web_dav/web_dav_view.dart';
|
||||
import 'package:moodiary/l10n/l10n.dart';
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:moodiary/components/base/button.dart';
|
||||
import 'package:moodiary/components/base/loading.dart';
|
||||
import 'package:moodiary/components/tile/setting_tile.dart';
|
||||
import 'package:moodiary/components/base/tile/setting_tile.dart';
|
||||
import 'package:moodiary/l10n/l10n.dart';
|
||||
|
||||
import 'category_manager_logic.dart';
|
||||
|
||||
@@ -2,7 +2,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:moodiary/components/base/button.dart';
|
||||
import 'package:moodiary/components/base/clipper.dart';
|
||||
import 'package:moodiary/components/tile/setting_tile.dart';
|
||||
import 'package:moodiary/components/base/tile/setting_tile.dart';
|
||||
import 'package:moodiary/l10n/l10n.dart';
|
||||
|
||||
import 'diary_setting_logic.dart';
|
||||
|
||||
@@ -15,6 +15,7 @@ import 'package:moodiary/common/values/colors.dart';
|
||||
import 'package:moodiary/common/values/diary_type.dart';
|
||||
import 'package:moodiary/components/base/button.dart';
|
||||
import 'package:moodiary/components/base/sheet.dart';
|
||||
import 'package:moodiary/components/base/tile/setting_tile.dart';
|
||||
import 'package:moodiary/components/category_add/category_add_view.dart';
|
||||
import 'package:moodiary/components/expand_button/expand_button_view.dart';
|
||||
import 'package:moodiary/components/lottie_modal/lottie_modal.dart';
|
||||
@@ -26,7 +27,6 @@ import 'package:moodiary/components/quill_embed/image_embed.dart';
|
||||
import 'package:moodiary/components/quill_embed/text_indent.dart';
|
||||
import 'package:moodiary/components/quill_embed/video_embed.dart';
|
||||
import 'package:moodiary/components/record_sheet/record_sheet_view.dart';
|
||||
import 'package:moodiary/components/tile/setting_tile.dart';
|
||||
import 'package:moodiary/l10n/l10n.dart';
|
||||
import 'package:moodiary/utils/theme_util.dart';
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import 'package:moodiary/common/values/border.dart';
|
||||
import 'package:moodiary/components/base/button.dart';
|
||||
import 'package:moodiary/components/base/loading.dart';
|
||||
import 'package:moodiary/components/base/text.dart';
|
||||
import 'package:moodiary/components/tile/setting_tile.dart';
|
||||
import 'package:moodiary/components/base/tile/setting_tile.dart';
|
||||
import 'package:moodiary/l10n/l10n.dart';
|
||||
|
||||
import 'font_logic.dart';
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:dartx/dartx.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:moodiary/components/dashboard/dashboard_logic.dart';
|
||||
@@ -26,13 +25,12 @@ class SettingLogic extends GetxController {
|
||||
@override
|
||||
void onReady() {
|
||||
unawaited(getDataUsage());
|
||||
unawaited(checkHasUserKey());
|
||||
unawaited(checkUserKey());
|
||||
super.onReady();
|
||||
}
|
||||
|
||||
Future<void> checkHasUserKey() async {
|
||||
state.hasUserKey.value =
|
||||
(await SecureStorageUtil.getValue('userKey')) != null;
|
||||
Future<void> checkUserKey() async {
|
||||
state.userKey.value = (await SecureStorageUtil.getValue('userKey')) ?? '';
|
||||
}
|
||||
|
||||
//获取当前占用储存空间
|
||||
@@ -135,17 +133,23 @@ class SettingLogic extends GetxController {
|
||||
Bind.find<DiaryLogic>().updateTitle();
|
||||
}
|
||||
|
||||
Future<void> setUserKey({required String key}) async {
|
||||
if (key.isNullOrBlank) {
|
||||
toast.info(message: '密钥不能为空');
|
||||
return;
|
||||
Future<bool> setUserKey({required String key}) async {
|
||||
try {
|
||||
await SecureStorageUtil.setValue('userKey', key);
|
||||
state.userKey.value = key;
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
await SecureStorageUtil.setValue('userKey', key);
|
||||
state.hasUserKey.value = true;
|
||||
}
|
||||
|
||||
Future<void> removeUserKey() async {
|
||||
await SecureStorageUtil.remove('userKey');
|
||||
state.hasUserKey.value = false;
|
||||
Future<bool> removeUserKey() async {
|
||||
try {
|
||||
await SecureStorageUtil.remove('userKey');
|
||||
state.userKey.value = '';
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,7 +22,7 @@ class SettingState {
|
||||
|
||||
RxBool backendPrivacy = PrefUtil.getValue<bool>('backendPrivacy')!.obs;
|
||||
|
||||
RxBool hasUserKey = false.obs;
|
||||
RxString userKey = ''.obs;
|
||||
|
||||
Rx<Language> language =
|
||||
Language.values
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:adaptive_dialog/adaptive_dialog.dart';
|
||||
import 'package:dartx/dartx.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:get/get.dart';
|
||||
@@ -8,14 +9,16 @@ import 'package:moodiary/common/values/language.dart';
|
||||
import 'package:moodiary/components/base/clipper.dart';
|
||||
import 'package:moodiary/components/base/sheet.dart';
|
||||
import 'package:moodiary/components/base/text.dart';
|
||||
import 'package:moodiary/components/base/tile/qr_tile.dart';
|
||||
import 'package:moodiary/components/base/tile/setting_tile.dart';
|
||||
import 'package:moodiary/components/color_sheet/color_sheet_view.dart';
|
||||
import 'package:moodiary/components/dashboard/dashboard_view.dart';
|
||||
import 'package:moodiary/components/language_dialog/language_dialog_view.dart';
|
||||
import 'package:moodiary/components/remove_password/remove_password_view.dart';
|
||||
import 'package:moodiary/components/set_password/set_password_view.dart';
|
||||
import 'package:moodiary/components/theme_mode_dialog/theme_mode_dialog_view.dart';
|
||||
import 'package:moodiary/components/tile/setting_tile.dart';
|
||||
import 'package:moodiary/l10n/l10n.dart';
|
||||
import 'package:moodiary/utils/notice_util.dart';
|
||||
|
||||
import 'setting_logic.dart';
|
||||
|
||||
@@ -339,44 +342,55 @@ class SettingPage extends StatelessWidget {
|
||||
);
|
||||
},
|
||||
),
|
||||
AdaptiveListTile(
|
||||
title: context.l10n.settingUserKey,
|
||||
subtitle: context.l10n.settingUserKeyDes,
|
||||
onTap: () async {
|
||||
if (state.hasUserKey.value) {
|
||||
final res = await showOkCancelAlertDialog(
|
||||
context: context,
|
||||
title: context.l10n.settingUserKeyReset,
|
||||
message: context.l10n.settingUserKeyResetDes,
|
||||
);
|
||||
if (res == OkCancelResult.ok) {
|
||||
logic.removeUserKey();
|
||||
Obx(() {
|
||||
return QrInputTile(
|
||||
title: context.l10n.settingUserKey,
|
||||
value: state.userKey.value,
|
||||
prefix: 'userKey',
|
||||
onValue: (value) async {
|
||||
final res = await logic.setUserKey(key: value);
|
||||
if (res) {
|
||||
toast.success();
|
||||
} else {
|
||||
toast.error();
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
final res = await showTextInputDialog(
|
||||
title: context.l10n.settingUserKeySet,
|
||||
message: context.l10n.settingUserKeySetDes,
|
||||
context: context,
|
||||
textFields: [const DialogTextField()],
|
||||
);
|
||||
if (res != null) {
|
||||
logic.setUserKey(key: res.first);
|
||||
},
|
||||
onInput: () async {
|
||||
if (state.userKey.value.isNotNullOrBlank) {
|
||||
final res = await showOkCancelAlertDialog(
|
||||
context: context,
|
||||
title: context.l10n.settingUserKeyReset,
|
||||
message: context.l10n.settingUserKeyResetDes,
|
||||
);
|
||||
if (res == OkCancelResult.ok) {
|
||||
final res_ = await logic.removeUserKey();
|
||||
if (res_) {
|
||||
toast.success();
|
||||
} else {
|
||||
toast.error();
|
||||
}
|
||||
}
|
||||
return;
|
||||
} else {
|
||||
final res = await showTextInputDialog(
|
||||
title: context.l10n.settingUserKeySet,
|
||||
message: context.l10n.settingUserKeySetDes,
|
||||
context: context,
|
||||
textFields: [const DialogTextField()],
|
||||
);
|
||||
if (res != null) {
|
||||
final res_ = await logic.setUserKey(key: res.first);
|
||||
if (res_) {
|
||||
toast.success();
|
||||
} else {
|
||||
toast.error();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
trailing: Obx(() {
|
||||
return Text(
|
||||
state.hasUserKey.value
|
||||
? context.l10n.settingUserKeyHasSet
|
||||
: context.l10n.settingUserKeyNotSet,
|
||||
style: context.textTheme.bodySmall!.copyWith(
|
||||
color: context.theme.colorScheme.primary,
|
||||
),
|
||||
);
|
||||
}),
|
||||
leading: const Icon(Icons.key_rounded),
|
||||
),
|
||||
},
|
||||
leading: const Icon(Icons.key_rounded),
|
||||
);
|
||||
}),
|
||||
GetBuilder<SettingLogic>(
|
||||
id: 'Lock',
|
||||
builder: (_) {
|
||||
|
||||
@@ -9,20 +9,48 @@ import 'package:moodiary/utils/notice_util.dart';
|
||||
import 'package:share_plus/share_plus.dart';
|
||||
|
||||
class LaboratoryLogic extends GetxController {
|
||||
Future<void> setTencentID({required String id, required String key}) async {
|
||||
await PrefUtil.setValue<String>('tencentId', id);
|
||||
await PrefUtil.setValue<String>('tencentKey', key);
|
||||
update();
|
||||
Future<bool> setTencentID({required String id}) async {
|
||||
try {
|
||||
await PrefUtil.setValue<String>('tencentId', id);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
} finally {
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> setQweatherKey({required String key}) async {
|
||||
await PrefUtil.setValue<String>('qweatherKey', key);
|
||||
update();
|
||||
Future<bool> setTencentKey({required String key}) async {
|
||||
try {
|
||||
await PrefUtil.setValue<String>('tencentKey', key);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
} finally {
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> setTiandituKey({required String key}) async {
|
||||
await PrefUtil.setValue<String>('tiandituKey', key);
|
||||
update();
|
||||
Future<bool> setQweatherKey({required String key}) async {
|
||||
try {
|
||||
await PrefUtil.setValue<String>('qweatherKey', key);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
} finally {
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
Future<bool> setTiandituKey({required String key}) async {
|
||||
try {
|
||||
await PrefUtil.setValue<String>('tiandituKey', key);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
} finally {
|
||||
update();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> exportErrorLog() async {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:adaptive_dialog/adaptive_dialog.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:gap/gap.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:moodiary/components/base/tile/qr_tile.dart';
|
||||
import 'package:moodiary/l10n/l10n.dart';
|
||||
import 'package:moodiary/persistence/pref.dart';
|
||||
import 'package:moodiary/utils/notice_util.dart';
|
||||
@@ -20,102 +20,71 @@ class LaboratoryPage extends StatelessWidget {
|
||||
body: GetBuilder<LaboratoryLogic>(
|
||||
builder: (_) {
|
||||
return ListView(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
children: [
|
||||
ListTile(
|
||||
title: const Text('腾讯云密钥'),
|
||||
isThreeLine: true,
|
||||
subtitle: SelectionArea(
|
||||
child: Text(
|
||||
'ID:${PrefUtil.getValue<String>('tencentId') ?? ''}\nKey:${PrefUtil.getValue<String>('tencentKey') ?? ''}',
|
||||
),
|
||||
),
|
||||
trailing: IconButton(
|
||||
onPressed: () async {
|
||||
final res = await showTextInputDialog(
|
||||
context: context,
|
||||
textFields: [
|
||||
DialogTextField(
|
||||
hintText: 'ID',
|
||||
initialText:
|
||||
PrefUtil.getValue<String>('tencentId') ?? '',
|
||||
),
|
||||
DialogTextField(
|
||||
hintText: 'KEY',
|
||||
initialText:
|
||||
PrefUtil.getValue<String>('tencentKey') ?? '',
|
||||
),
|
||||
],
|
||||
title: '腾讯云密钥',
|
||||
message: '在腾讯云控制台获取密钥',
|
||||
style: AdaptiveStyle.material,
|
||||
);
|
||||
if (res != null) {
|
||||
logic.setTencentID(id: res[0], key: res[1]);
|
||||
}
|
||||
},
|
||||
icon: const FaIcon(FontAwesomeIcons.wrench),
|
||||
),
|
||||
QrInputTile(
|
||||
title: '${context.l10n.labTencentCloud} ID',
|
||||
value: PrefUtil.getValue<String>('tencentId') ?? '',
|
||||
prefix: 'tencentId',
|
||||
onValue: (value) async {
|
||||
final res = await logic.setTencentID(id: value);
|
||||
if (res) {
|
||||
toast.success();
|
||||
} else {
|
||||
toast.error();
|
||||
}
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
title: const Text('和风天气密钥'),
|
||||
subtitle: SelectionArea(
|
||||
child: Text(PrefUtil.getValue<String>('qweatherKey') ?? ''),
|
||||
),
|
||||
trailing: IconButton(
|
||||
onPressed: () async {
|
||||
final res = await showTextInputDialog(
|
||||
context: context,
|
||||
style: AdaptiveStyle.material,
|
||||
title: '和风天气密钥',
|
||||
message: '在和风天气控制台获取密钥',
|
||||
textFields: [
|
||||
DialogTextField(
|
||||
hintText: 'KEY',
|
||||
initialText:
|
||||
PrefUtil.getValue<String>('qweatherKey') ?? '',
|
||||
),
|
||||
],
|
||||
);
|
||||
if (res != null) {
|
||||
logic.setQweatherKey(key: res[0]);
|
||||
}
|
||||
},
|
||||
icon: const FaIcon(FontAwesomeIcons.wrench),
|
||||
),
|
||||
const Gap(12),
|
||||
QrInputTile(
|
||||
title: '${context.l10n.labTencentCloud} Key',
|
||||
value: PrefUtil.getValue<String>('tencentKey') ?? '',
|
||||
prefix: 'tencentKey',
|
||||
onValue: (value) async {
|
||||
final res = await logic.setTencentKey(key: value);
|
||||
if (res) {
|
||||
toast.success();
|
||||
} else {
|
||||
toast.error();
|
||||
}
|
||||
},
|
||||
),
|
||||
ListTile(
|
||||
title: const Text('天地图密钥'),
|
||||
subtitle: SelectionArea(
|
||||
child: Text(PrefUtil.getValue<String>('tiandituKey') ?? ''),
|
||||
),
|
||||
trailing: IconButton(
|
||||
onPressed: () async {
|
||||
final res = await showTextInputDialog(
|
||||
context: context,
|
||||
textFields: [
|
||||
DialogTextField(
|
||||
hintText: 'KEY',
|
||||
initialText:
|
||||
PrefUtil.getValue<String>('tiandituKey') ?? '',
|
||||
),
|
||||
],
|
||||
title: '天地图密钥',
|
||||
message: '在天地图控制台获取密钥',
|
||||
style: AdaptiveStyle.material,
|
||||
);
|
||||
if (res != null) {
|
||||
logic.setTiandituKey(key: res[0]);
|
||||
}
|
||||
},
|
||||
icon: const FaIcon(FontAwesomeIcons.wrench),
|
||||
),
|
||||
const Gap(12),
|
||||
QrInputTile(
|
||||
title: '${context.l10n.labQweather} Key',
|
||||
value: PrefUtil.getValue<String>('qweatherKey') ?? '',
|
||||
prefix: 'qweatherKey',
|
||||
onValue: (value) async {
|
||||
final res = await logic.setQweatherKey(key: value);
|
||||
if (res) {
|
||||
toast.success();
|
||||
} else {
|
||||
toast.error();
|
||||
}
|
||||
},
|
||||
),
|
||||
const Gap(12),
|
||||
QrInputTile(
|
||||
title: '${context.l10n.labTianditu} Key',
|
||||
value: PrefUtil.getValue<String>('tiandituKey') ?? '',
|
||||
prefix: 'tiandituKey',
|
||||
onValue: (value) async {
|
||||
final res = await logic.setTiandituKey(key: value);
|
||||
if (res) {
|
||||
toast.success();
|
||||
} else {
|
||||
toast.error();
|
||||
}
|
||||
},
|
||||
),
|
||||
const Gap(12),
|
||||
ListTile(
|
||||
onTap: () {
|
||||
onTap: () async {
|
||||
logic.exportErrorLog();
|
||||
},
|
||||
title: const Text('导出日志文件'),
|
||||
),
|
||||
const Gap(12),
|
||||
ListTile(
|
||||
onTap: () async {
|
||||
final res = await logic.aesTest();
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:moodiary/components/base/button.dart';
|
||||
import 'package:moodiary/components/tile/setting_tile.dart';
|
||||
import 'package:moodiary/components/base/tile/setting_tile.dart';
|
||||
import 'package:moodiary/l10n/l10n.dart';
|
||||
|
||||
import 'recycle_logic.dart';
|
||||
|
||||
@@ -207,6 +207,52 @@ class MoodiaryGetPage extends GetPage {
|
||||
);
|
||||
}
|
||||
|
||||
class MoodiaryFadeInPageRoute<T> extends PageRoute<T>
|
||||
with MaterialRouteTransitionMixin<T> {
|
||||
MoodiaryFadeInPageRoute({
|
||||
required this.builder,
|
||||
super.settings,
|
||||
super.requestFocus,
|
||||
this.maintainState = true,
|
||||
super.fullscreenDialog,
|
||||
super.allowSnapshotting = true,
|
||||
super.barrierDismissible = false,
|
||||
}) {
|
||||
assert(opaque);
|
||||
}
|
||||
|
||||
final WidgetBuilder builder;
|
||||
|
||||
@override
|
||||
Widget buildContent(BuildContext context) {
|
||||
final body = PageAdaptiveBackground(
|
||||
isHome: settings.name == AppRoutes.homePage,
|
||||
child: builder(context),
|
||||
);
|
||||
if (Platform.isAndroid || Platform.isIOS) {
|
||||
return body;
|
||||
} else {
|
||||
return Column(children: [const WindowsBar(), Expanded(child: body)]);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
final bool maintainState;
|
||||
|
||||
@override
|
||||
Widget buildTransitions(
|
||||
BuildContext context,
|
||||
Animation<double> animation,
|
||||
Animation<double> secondaryAnimation,
|
||||
Widget child,
|
||||
) {
|
||||
return FadeTransition(opacity: animation, child: child);
|
||||
}
|
||||
|
||||
@override
|
||||
String get debugLabel => '${super.debugLabel}(${settings.name})';
|
||||
}
|
||||
|
||||
class _MoodiaryPageTransition implements CustomTransition {
|
||||
final bool? useFade;
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:dartx/dartx.dart';
|
||||
import 'package:moodiary/src/rust/api/aes.dart';
|
||||
|
||||
class AesUtil {
|
||||
@@ -40,4 +41,55 @@ class AesUtil {
|
||||
);
|
||||
return utf8.decode(decrypted);
|
||||
}
|
||||
|
||||
/// 基于时间窗口加密
|
||||
static Future<Uint8List> encryptWithTimeWindow({
|
||||
required String data,
|
||||
required Duration validDuration,
|
||||
}) async {
|
||||
final timeSlot = _currentTimeSlot(validDuration);
|
||||
final dynamicKey = timeSlot.toString().md5;
|
||||
final salt = _dailySalt();
|
||||
|
||||
final aesKey = await deriveKey(salt: salt, userKey: dynamicKey);
|
||||
return await encrypt(key: aesKey, data: data);
|
||||
}
|
||||
|
||||
/// 基于时间窗口解密
|
||||
static Future<String?> decryptWithTimeWindow({
|
||||
required Uint8List encryptedData,
|
||||
required Duration validDuration,
|
||||
int toleranceSlots = 1,
|
||||
}) async {
|
||||
final currentSlot = _currentTimeSlot(validDuration);
|
||||
final salt = _dailySalt();
|
||||
|
||||
for (int offset = 0; offset <= toleranceSlots; offset++) {
|
||||
for (final slot in [currentSlot - offset, currentSlot + offset]) {
|
||||
final dynamicKey = slot.toString().md5;
|
||||
final aesKey = await deriveKey(salt: salt, userKey: dynamicKey);
|
||||
try {
|
||||
final result = await decrypt(
|
||||
key: aesKey,
|
||||
encryptedData: encryptedData,
|
||||
);
|
||||
return result;
|
||||
} catch (_) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static int _currentTimeSlot(Duration duration) {
|
||||
final now = DateTime.now().millisecondsSinceEpoch;
|
||||
return now ~/ duration.inMilliseconds;
|
||||
}
|
||||
|
||||
static String _dailySalt() {
|
||||
final date = DateTime.now();
|
||||
return '${date.year}-${date.month}-${date.day}'.md5;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ class NoticeUtil {
|
||||
usePenetrate: true,
|
||||
displayTime: const Duration(seconds: 2),
|
||||
backType: SmartBackType.ignore,
|
||||
debounce: true,
|
||||
maskColor: Colors.transparent,
|
||||
builder: (context) {
|
||||
return _build(
|
||||
@@ -70,6 +71,7 @@ class NoticeUtil {
|
||||
maskColor: Colors.transparent,
|
||||
backType: SmartBackType.ignore,
|
||||
usePenetrate: true,
|
||||
debounce: true,
|
||||
builder: (context) {
|
||||
return _build(
|
||||
context: context,
|
||||
@@ -93,6 +95,7 @@ class NoticeUtil {
|
||||
usePenetrate: true,
|
||||
backType: SmartBackType.ignore,
|
||||
maskColor: Colors.transparent,
|
||||
debounce: true,
|
||||
builder: (context) {
|
||||
return _build(
|
||||
context: context,
|
||||
|
||||
@@ -610,3 +610,25 @@ class ThemeUtil {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
extension ColorExt on Color {
|
||||
Brightness get brightness {
|
||||
final double relativeLuminance = computeLuminance();
|
||||
|
||||
const double kThreshold = 0.15;
|
||||
if ((relativeLuminance + 0.05) * (relativeLuminance + 0.05) > kThreshold) {
|
||||
return Brightness.light;
|
||||
}
|
||||
return Brightness.dark;
|
||||
}
|
||||
}
|
||||
|
||||
extension ColorExt2 on BuildContext {
|
||||
Color adaptiveColor(Color color) {
|
||||
if (!isDarkMode) return color;
|
||||
|
||||
final hsl = HSLColor.fromColor(color);
|
||||
final inverted = hsl.withLightness(1.0 - hsl.lightness);
|
||||
return inverted.toColor();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,7 @@ import local_auth_darwin
|
||||
import macos_ui
|
||||
import macos_window_utils
|
||||
import media_kit_video
|
||||
import mobile_scanner
|
||||
import network_info_plus
|
||||
import package_info_plus
|
||||
import path_provider_foundation
|
||||
@@ -59,6 +60,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
MacOSUiPlugin.register(with: registry.registrar(forPlugin: "MacOSUiPlugin"))
|
||||
MacOSWindowUtilsPlugin.register(with: registry.registrar(forPlugin: "MacOSWindowUtilsPlugin"))
|
||||
MediaKitVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitVideoPlugin"))
|
||||
MobileScannerPlugin.register(with: registry.registrar(forPlugin: "MobileScannerPlugin"))
|
||||
NetworkInfoPlusPlugin.register(with: registry.registrar(forPlugin: "NetworkInfoPlusPlugin"))
|
||||
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
|
||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||
|
||||
@@ -32,6 +32,7 @@ PODS:
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- geolocator_apple (1.2.0):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- isar_flutter_libs (1.0.0):
|
||||
- FlutterMacOS
|
||||
@@ -42,12 +43,11 @@ PODS:
|
||||
- FlutterMacOS
|
||||
- macos_window_utils (1.0.0):
|
||||
- FlutterMacOS
|
||||
- media_kit_libs_macos_video (1.0.4):
|
||||
- FlutterMacOS
|
||||
- media_kit_native_event_loop (1.0.0):
|
||||
- FlutterMacOS
|
||||
- media_kit_video (0.0.1):
|
||||
- FlutterMacOS
|
||||
- mobile_scanner (7.0.0):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- moodiary_rust (0.0.1):
|
||||
- FlutterMacOS
|
||||
- network_info_plus (0.0.1):
|
||||
@@ -60,11 +60,7 @@ PODS:
|
||||
- FlutterMacOS
|
||||
- quill_native_bridge_macos (0.0.1):
|
||||
- FlutterMacOS
|
||||
- record_darwin (1.0.0):
|
||||
- FlutterMacOS
|
||||
- rive_common (0.0.1):
|
||||
- FlutterMacOS
|
||||
- screen_brightness_macos (0.1.0):
|
||||
- record_macos (1.0.0):
|
||||
- FlutterMacOS
|
||||
- share_plus (0.0.1):
|
||||
- FlutterMacOS
|
||||
@@ -81,6 +77,8 @@ PODS:
|
||||
- video_player_avfoundation (0.0.1):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- volume_controller (0.0.1):
|
||||
- FlutterMacOS
|
||||
- wakelock_plus (0.0.1):
|
||||
- FlutterMacOS
|
||||
|
||||
@@ -100,28 +98,26 @@ DEPENDENCIES:
|
||||
- flutter_secure_storage_macos (from `Flutter/ephemeral/.symlinks/plugins/flutter_secure_storage_macos/macos`)
|
||||
- FlutterMacOS (from `Flutter/ephemeral`)
|
||||
- gal (from `Flutter/ephemeral/.symlinks/plugins/gal/darwin`)
|
||||
- geolocator_apple (from `Flutter/ephemeral/.symlinks/plugins/geolocator_apple/macos`)
|
||||
- geolocator_apple (from `Flutter/ephemeral/.symlinks/plugins/geolocator_apple/darwin`)
|
||||
- isar_flutter_libs (from `Flutter/ephemeral/.symlinks/plugins/isar_flutter_libs/macos`)
|
||||
- local_auth_darwin (from `Flutter/ephemeral/.symlinks/plugins/local_auth_darwin/darwin`)
|
||||
- macos_ui (from `Flutter/ephemeral/.symlinks/plugins/macos_ui/macos`)
|
||||
- macos_window_utils (from `Flutter/ephemeral/.symlinks/plugins/macos_window_utils/macos`)
|
||||
- media_kit_libs_macos_video (from `Flutter/ephemeral/.symlinks/plugins/media_kit_libs_macos_video/macos`)
|
||||
- media_kit_native_event_loop (from `Flutter/ephemeral/.symlinks/plugins/media_kit_native_event_loop/macos`)
|
||||
- media_kit_video (from `Flutter/ephemeral/.symlinks/plugins/media_kit_video/macos`)
|
||||
- mobile_scanner (from `Flutter/ephemeral/.symlinks/plugins/mobile_scanner/darwin`)
|
||||
- moodiary_rust (from `Flutter/ephemeral/.symlinks/plugins/moodiary_rust/macos`)
|
||||
- network_info_plus (from `Flutter/ephemeral/.symlinks/plugins/network_info_plus/macos`)
|
||||
- package_info_plus (from `Flutter/ephemeral/.symlinks/plugins/package_info_plus/macos`)
|
||||
- path_provider_foundation (from `Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin`)
|
||||
- quill_native_bridge_macos (from `Flutter/ephemeral/.symlinks/plugins/quill_native_bridge_macos/macos`)
|
||||
- record_darwin (from `Flutter/ephemeral/.symlinks/plugins/record_darwin/macos`)
|
||||
- rive_common (from `Flutter/ephemeral/.symlinks/plugins/rive_common/macos`)
|
||||
- screen_brightness_macos (from `Flutter/ephemeral/.symlinks/plugins/screen_brightness_macos/macos`)
|
||||
- record_macos (from `Flutter/ephemeral/.symlinks/plugins/record_macos/macos`)
|
||||
- share_plus (from `Flutter/ephemeral/.symlinks/plugins/share_plus/macos`)
|
||||
- shared_preferences_foundation (from `Flutter/ephemeral/.symlinks/plugins/shared_preferences_foundation/darwin`)
|
||||
- sqflite_darwin (from `Flutter/ephemeral/.symlinks/plugins/sqflite_darwin/darwin`)
|
||||
- tflite_flutter (from `Flutter/ephemeral/.symlinks/plugins/tflite_flutter/macos`)
|
||||
- url_launcher_macos (from `Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos`)
|
||||
- video_player_avfoundation (from `Flutter/ephemeral/.symlinks/plugins/video_player_avfoundation/darwin`)
|
||||
- volume_controller (from `Flutter/ephemeral/.symlinks/plugins/volume_controller/macos`)
|
||||
- wakelock_plus (from `Flutter/ephemeral/.symlinks/plugins/wakelock_plus/macos`)
|
||||
|
||||
SPEC REPOS:
|
||||
@@ -160,7 +156,7 @@ EXTERNAL SOURCES:
|
||||
gal:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/gal/darwin
|
||||
geolocator_apple:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/geolocator_apple/macos
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/geolocator_apple/darwin
|
||||
isar_flutter_libs:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/isar_flutter_libs/macos
|
||||
local_auth_darwin:
|
||||
@@ -169,12 +165,10 @@ EXTERNAL SOURCES:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/macos_ui/macos
|
||||
macos_window_utils:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/macos_window_utils/macos
|
||||
media_kit_libs_macos_video:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/media_kit_libs_macos_video/macos
|
||||
media_kit_native_event_loop:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/media_kit_native_event_loop/macos
|
||||
media_kit_video:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/media_kit_video/macos
|
||||
mobile_scanner:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/mobile_scanner/darwin
|
||||
moodiary_rust:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/moodiary_rust/macos
|
||||
network_info_plus:
|
||||
@@ -185,12 +179,8 @@ EXTERNAL SOURCES:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin
|
||||
quill_native_bridge_macos:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/quill_native_bridge_macos/macos
|
||||
record_darwin:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/record_darwin/macos
|
||||
rive_common:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/rive_common/macos
|
||||
screen_brightness_macos:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/screen_brightness_macos/macos
|
||||
record_macos:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/record_macos/macos
|
||||
share_plus:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/share_plus/macos
|
||||
shared_preferences_foundation:
|
||||
@@ -203,6 +193,8 @@ EXTERNAL SOURCES:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/url_launcher_macos/macos
|
||||
video_player_avfoundation:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/video_player_avfoundation/darwin
|
||||
volume_controller:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/volume_controller/macos
|
||||
wakelock_plus:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/wakelock_plus/macos
|
||||
|
||||
@@ -222,29 +214,27 @@ SPEC CHECKSUMS:
|
||||
flutter_secure_storage_macos: 7f45e30f838cf2659862a4e4e3ee1c347c2b3b54
|
||||
FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24
|
||||
gal: baecd024ebfd13c441269ca7404792a7152fde89
|
||||
geolocator_apple: ccfb79d5250de3a295f5093cd03e76aa8836a416
|
||||
geolocator_apple: ab36aa0e8b7d7a2d7639b3b4e48308394e8cef5e
|
||||
isar_flutter_libs: a65381780401f81ad6bf3f2e7cd0de5698fb98c4
|
||||
local_auth_darwin: 553ce4f9b16d3fdfeafce9cf042e7c9f77c1c391
|
||||
macos_ui: 2047a8e6536a80491ef10684c53ca500e04f1bcf
|
||||
macos_window_utils: 3bca8603c2a1cf2257351dfe6bbccc9accf739fd
|
||||
media_kit_libs_macos_video: 85a23e549b5f480e72cae3e5634b5514bc692f65
|
||||
media_kit_native_event_loop: a80d071c835c612fd80173e79390a50ec409f1b1
|
||||
media_kit_video: fa6564e3799a0a28bff39442334817088b7ca758
|
||||
media_kit_video: 28d7d27611c7769a2464eb7621c37386a61dcc9a
|
||||
mobile_scanner: 9157936403f5a0644ca3779a38ff8404c5434a93
|
||||
moodiary_rust: 2f0ea7e60816f68d22e387a10b460860168eced5
|
||||
network_info_plus: 21d1cd6a015ccb2fdff06a1fbfa88d54b4e92f61
|
||||
OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94
|
||||
package_info_plus: f0052d280d17aa382b932f399edf32507174e870
|
||||
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
|
||||
quill_native_bridge_macos: 2b005cb56902bb740e0cd9620aa399dfac6b4882
|
||||
record_darwin: 30509266ae213af8afdb09a8ae7467cb64c1377e
|
||||
rive_common: ea79040f86acf053a2d5a75a2506175ee39796a5
|
||||
screen_brightness_macos: 2a3ee243f8051c340381e8e51bcedced8360f421
|
||||
record_macos: 295d70bd5fb47145df78df7b80e6697cd18403c0
|
||||
share_plus: 510bf0af1a42cd602274b4629920c9649c52f4cc
|
||||
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
|
||||
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
|
||||
tflite_flutter: d1496f2e968aa5a142fb282da8f5d754fcee5613
|
||||
url_launcher_macos: 0fba8ddabfc33ce0a9afe7c5fef5aab3d8d2d673
|
||||
video_player_avfoundation: 2cef49524dd1f16c5300b9cd6efd9611ce03639b
|
||||
volume_controller: 5c068e6d085c80dadd33fc2c918d2114b775b3dd
|
||||
wakelock_plus: 21ddc249ac4b8d018838dbdabd65c5976c308497
|
||||
|
||||
PODFILE CHECKSUM: b5ff078e9cf81bae88fdc8e0ce3668e57b68e9b6
|
||||
|
||||
48
pubspec.lock
48
pubspec.lock
@@ -1226,6 +1226,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.1"
|
||||
gap:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: gap
|
||||
sha256: f19387d4e32f849394758b91377f9153a1b41d79513ef7668c088c77dbc6955d
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.1"
|
||||
geolocator:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -1779,6 +1787,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.5.7"
|
||||
mobile_scanner:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: mobile_scanner
|
||||
sha256: "8676156e140c315068cf9b74ceb42b11b239a4895b00914d1d2ca056f70bc917"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.0-beta.9"
|
||||
modal_bottom_sheet:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -2090,6 +2106,22 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.5.0"
|
||||
qr:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: qr
|
||||
sha256: "5a1d2586170e172b8a8c8470bbbffd5eb0cd38a66c0d77155ea138d3af3a4445"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
qr_flutter:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: qr_flutter
|
||||
sha256: "5095f0fc6e3f71d08adef8feccc8cea4f12eec18a2e31c2e8d82cb6019f4b097"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.1.0"
|
||||
quill_native_bridge:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -2575,6 +2607,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.1"
|
||||
substring_highlight:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: substring_highlight
|
||||
sha256: "96c61e8316098831f6bee87d2386617e4be6aaf87fbc89402dc049d371b67efb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.33"
|
||||
supabase:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -2639,6 +2679,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.11.0"
|
||||
throttling:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: throttling
|
||||
sha256: e48a4c681b1838b8bf99c1a4f822efe43bb69132f9a56091cd5b7d931c862255
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
time:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -122,6 +122,11 @@ dependencies:
|
||||
audioplayers_android_exo: 0.1.2
|
||||
scrollview_observer: 1.26.0
|
||||
flutter_smart_dialog: 4.9.8+7
|
||||
substring_highlight: 1.0.33
|
||||
qr_flutter: 4.1.0
|
||||
mobile_scanner: 7.0.0-beta.9
|
||||
throttling: 2.0.1
|
||||
gap: 3.0.1
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
||||
Reference in New Issue
Block a user