mirror of
https://github.com/ZhuJHua/moodiary.git
synced 2026-04-05 16:31:45 +08:00
feat(local_send): add local send support
This commit is contained in:
@@ -1,9 +1,7 @@
|
||||
package cn.yooss.mood_diary
|
||||
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.core.view.WindowCompat
|
||||
import com.github.gzuliyujiang.oaid.DeviceID
|
||||
import io.flutter.embedding.android.FlutterFragmentActivity
|
||||
import io.flutter.embedding.engine.FlutterEngine
|
||||
@@ -11,20 +9,13 @@ import io.flutter.plugin.common.MethodChannel
|
||||
|
||||
|
||||
class MainActivity : FlutterFragmentActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
enableEdgeToEdge()
|
||||
super.onCreate(savedInstanceState)
|
||||
}
|
||||
|
||||
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
|
||||
super.configureFlutterEngine(flutterEngine)
|
||||
MethodChannel(
|
||||
flutterEngine.dartExecutor.binaryMessenger, "view_channel"
|
||||
).setMethodCallHandler { call, result ->
|
||||
if (call.method == "setSystemUIVisibility") {
|
||||
setSystemUIVisibility()
|
||||
result.success(null)
|
||||
} else {
|
||||
result.notImplemented()
|
||||
}
|
||||
}
|
||||
MethodChannel(
|
||||
flutterEngine.dartExecutor.binaryMessenger, "oaid_channel"
|
||||
).setMethodCallHandler { call, result ->
|
||||
@@ -47,9 +38,4 @@ class MainActivity : FlutterFragmentActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun setSystemUIVisibility() {
|
||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||
enableEdgeToEdge()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# Uncomment this line to define a global platform for your project
|
||||
# platform :ios, '12.0'
|
||||
platform :ios, '18.2'
|
||||
|
||||
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||
|
||||
@@ -85,6 +85,8 @@ PODS:
|
||||
- Flutter
|
||||
- media_kit_video (0.0.1):
|
||||
- Flutter
|
||||
- network_info_plus (0.0.1):
|
||||
- Flutter
|
||||
- ObjectBox (4.0.1)
|
||||
- objectbox_flutter_libs (0.0.1):
|
||||
- Flutter
|
||||
@@ -100,7 +102,6 @@ PODS:
|
||||
- Flutter
|
||||
- record_darwin (1.0.0):
|
||||
- Flutter
|
||||
- FlutterMacOS
|
||||
- screen_brightness_ios (0.1.0):
|
||||
- Flutter
|
||||
- SDWebImage (5.19.7):
|
||||
@@ -166,6 +167,7 @@ DEPENDENCIES:
|
||||
- media_kit_libs_ios_video (from `.symlinks/plugins/media_kit_libs_ios_video/ios`)
|
||||
- media_kit_native_event_loop (from `.symlinks/plugins/media_kit_native_event_loop/ios`)
|
||||
- media_kit_video (from `.symlinks/plugins/media_kit_video/ios`)
|
||||
- network_info_plus (from `.symlinks/plugins/network_info_plus/ios`)
|
||||
- objectbox_flutter_libs (from `.symlinks/plugins/objectbox_flutter_libs/ios`)
|
||||
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
|
||||
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
|
||||
@@ -230,6 +232,8 @@ EXTERNAL SOURCES:
|
||||
:path: ".symlinks/plugins/media_kit_native_event_loop/ios"
|
||||
media_kit_video:
|
||||
:path: ".symlinks/plugins/media_kit_video/ios"
|
||||
network_info_plus:
|
||||
:path: ".symlinks/plugins/network_info_plus/ios"
|
||||
objectbox_flutter_libs:
|
||||
:path: ".symlinks/plugins/objectbox_flutter_libs/ios"
|
||||
package_info_plus:
|
||||
@@ -281,13 +285,14 @@ SPEC CHECKSUMS:
|
||||
media_kit_libs_ios_video: a5fe24bc7875ccd6378a0978c13185e1344651c1
|
||||
media_kit_native_event_loop: e6b2ab20cf0746eb1c33be961fcf79667304fa2a
|
||||
media_kit_video: 5da63f157170e5bf303bf85453b7ef6971218a2e
|
||||
network_info_plus: 6613d9d7cdeb0e6f366ed4dbe4b3c51c52d567a9
|
||||
ObjectBox: 0bc4bb75eea85f6af06b369148b334c2056bbc29
|
||||
objectbox_flutter_libs: 2ce0da386c780878687c736b528ceaf371573efb
|
||||
package_info_plus: c0502532a26c7662a62a356cebe2692ec5fe4ec4
|
||||
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
|
||||
permission_handler_apple: 9878588469a2b0d0fc1e048d9f43605f92e6cec2
|
||||
quill_native_bridge_ios: 277bdf5bf0fa5d7a12999556b415a5c63dd76ec4
|
||||
record_darwin: df0a677188e5fed18472550298e675f19ddaffbe
|
||||
record_darwin: 3b1a8e7d5c0cbf45ad6165b4d83a6ca643d929c3
|
||||
screen_brightness_ios: 715ca807df953bf676d339f11464e438143ee625
|
||||
SDWebImage: 8a6b7b160b4d710e2a22b6900e25301075c34cb3
|
||||
SDWebImageWebPCoder: e38c0a70396191361d60c092933e22c20d5b1380
|
||||
@@ -303,6 +308,6 @@ SPEC CHECKSUMS:
|
||||
volume_controller: 531ddf792994285c9b17f9d8a7e4dcdd29b3eae9
|
||||
wakelock_plus: 78ec7c5b202cab7761af8e2b2b3d0671be6c4ae1
|
||||
|
||||
PODFILE CHECKSUM: 83893f9a2d98085e99381d8f1dc69dcf7bd5b437
|
||||
PODFILE CHECKSUM: 9752b5340b4d3f9618318fcd530d907790e2a9f5
|
||||
|
||||
COCOAPODS: 1.15.2
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
|
||||
306627225710E691EF0E0A35 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2A1A8341695BDC0741989253 /* Pods_Runner.framework */; };
|
||||
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
|
||||
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
||||
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
|
||||
@@ -15,7 +16,6 @@
|
||||
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
||||
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
||||
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
||||
B89FBBDC71186C5741E699DA /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2A1A8341695BDC0741989253 /* Pods_Runner.framework */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@@ -68,11 +68,11 @@
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
97C146EB1CF9000F007C117D /* Frameworks */ = {
|
||||
523FF08A9B798D86BA8B9DD9 /* Frameworks */ = {
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
B89FBBDC71186C5741E699DA /* Pods_Runner.framework in Frameworks */,
|
||||
306627225710E691EF0E0A35 /* Pods_Runner.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -193,12 +193,12 @@
|
||||
3190980419AFCAD7AC11C68D /* [CP] Check Pods Manifest.lock */,
|
||||
9740EEB61CF901F6004384FC /* Run Script */,
|
||||
97C146EA1CF9000F007C117D /* Sources */,
|
||||
97C146EB1CF9000F007C117D /* Frameworks */,
|
||||
97C146EC1CF9000F007C117D /* Resources */,
|
||||
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
||||
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
||||
DDBAC86E6B3F372170C284A0 /* [CP] Embed Pods Frameworks */,
|
||||
B482CF705AC6985E445540F2 /* [CP] Copy Pods Resources */,
|
||||
523FF08A9B798D86BA8B9DD9 /* Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
@@ -510,6 +510,7 @@
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = 3XA29H789G;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = cn.yooss.moodDiary.RunnerTests;
|
||||
@@ -528,6 +529,7 @@
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = 3XA29H789G;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = cn.yooss.moodDiary.RunnerTests;
|
||||
@@ -544,6 +546,7 @@
|
||||
BUNDLE_LOADER = "$(TEST_HOST)";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = 3XA29H789G;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
MARKETING_VERSION = 1.0;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = cn.yooss.moodDiary.RunnerTests;
|
||||
|
||||
@@ -53,7 +53,8 @@ class Api {
|
||||
|
||||
Future<List<String>?> updatePosition() async {
|
||||
Position? position;
|
||||
if (await Utils().permissionUtil.checkPermission(Permission.location)) {
|
||||
if (await Utils().permissionUtil.checkPermission(Permission.location) &&
|
||||
await Geolocator.isLocationServiceEnabled()) {
|
||||
position = await Geolocator.getLastKnownPosition(forceAndroidLocationManager: true);
|
||||
position ??= await Geolocator.getCurrentPosition(locationSettings: AndroidSettings(forceLocationManager: true));
|
||||
}
|
||||
|
||||
@@ -113,6 +113,48 @@ class Diary {
|
||||
}
|
||||
|
||||
Diary();
|
||||
|
||||
// 将 Diary 对象转换为 JSON
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'categoryId': categoryId,
|
||||
'title': title,
|
||||
'content': content,
|
||||
'contentText': contentText,
|
||||
'time': time.toIso8601String(),
|
||||
'show': show,
|
||||
'mood': mood,
|
||||
'weather': weather,
|
||||
'imageName': imageName,
|
||||
'audioName': audioName,
|
||||
'videoName': videoName,
|
||||
'tags': tags,
|
||||
'position': position,
|
||||
'imageColor': imageColor,
|
||||
'aspect': aspect,
|
||||
};
|
||||
}
|
||||
|
||||
factory Diary.fromJson(Map<String, dynamic> json) {
|
||||
return Diary()
|
||||
..id = json['id'] as String
|
||||
..categoryId = json['categoryId'] as String?
|
||||
..title = json['title'] as String
|
||||
..content = json['content'] as String
|
||||
..contentText = json['contentText'] as String
|
||||
..time = DateTime.parse(json['time'] as String)
|
||||
..show = json['show'] as bool
|
||||
..mood = (json['mood'] as num).toDouble()
|
||||
..weather = List<String>.from(json['weather'] as List)
|
||||
..imageName = List<String>.from(json['imageName'] as List)
|
||||
..audioName = List<String>.from(json['audioName'] as List)
|
||||
..videoName = List<String>.from(json['videoName'] as List)
|
||||
..tags = List<String>.from(json['tags'] as List)
|
||||
..position = List<String>.from(json['position'] as List)
|
||||
..imageColor = json['imageColor'] as int?
|
||||
..aspect = (json['aspect'] as num?)?.toDouble();
|
||||
}
|
||||
}
|
||||
|
||||
int fastHash(String string) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:mood_diary/common/values/icons.dart';
|
||||
import 'package:mood_diary/components/mood_icon/mood_icon_view.dart';
|
||||
@@ -28,7 +29,7 @@ class DashboardComponent extends StatelessWidget {
|
||||
Column(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||
children: [
|
||||
Icon(
|
||||
FaIcon(
|
||||
icon,
|
||||
color: colorScheme.secondary,
|
||||
size: 32,
|
||||
|
||||
@@ -17,15 +17,13 @@ class DiaryTabViewComponent extends StatelessWidget {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final logicTag = categoryId ?? 'default';
|
||||
final logic =
|
||||
Get.put(DiaryTabViewLogic(categoryId: categoryId), tag: logicTag);
|
||||
final logic = Get.put(DiaryTabViewLogic(categoryId: categoryId), tag: logicTag);
|
||||
final state = Bind.find<DiaryTabViewLogic>(tag: logicTag).state;
|
||||
final i18n = AppLocalizations.of(context)!;
|
||||
|
||||
Widget buildGrid() {
|
||||
return SliverWaterfallFlow(
|
||||
gridDelegate: const SliverWaterfallFlowDelegateWithMaxCrossAxisExtent(
|
||||
maxCrossAxisExtent: 250),
|
||||
gridDelegate: const SliverWaterfallFlowDelegateWithMaxCrossAxisExtent(maxCrossAxisExtent: 250),
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) {
|
||||
return LargeDiaryCardComponent(
|
||||
@@ -68,29 +66,28 @@ class DiaryTabViewComponent extends StatelessWidget {
|
||||
builder: (_) {
|
||||
return buildPlaceHolder();
|
||||
}),
|
||||
CustomScrollView(
|
||||
cacheExtent: 2000,
|
||||
slivers: [
|
||||
SliverOverlapInjector(
|
||||
handle:
|
||||
NestedScrollView.sliverOverlapAbsorberHandleFor(context)),
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.all(4.0),
|
||||
sliver: GetBuilder<DiaryTabViewLogic>(
|
||||
id: 'TabView',
|
||||
tag: logicTag,
|
||||
builder: (_) {
|
||||
return SliverAnimatedSwitcher(
|
||||
GetBuilder<DiaryTabViewLogic>(
|
||||
id: 'TabView',
|
||||
tag: logicTag,
|
||||
builder: (_) {
|
||||
return CustomScrollView(
|
||||
primary: true,
|
||||
cacheExtent: 2000,
|
||||
slivers: [
|
||||
SliverOverlapInjector(handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context)),
|
||||
SliverPadding(
|
||||
padding: const EdgeInsets.all(4.0),
|
||||
sliver: SliverAnimatedSwitcher(
|
||||
duration: const Duration(milliseconds: 400),
|
||||
child: switch (logic.diaryLogic.state.viewModeType) {
|
||||
ViewModeType.list => buildList(),
|
||||
ViewModeType.grid => buildGrid(),
|
||||
},
|
||||
);
|
||||
}),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,172 @@
|
||||
import 'dart:async';
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:dio/dio.dart' as dio;
|
||||
import 'package:get/get.dart';
|
||||
import 'package:mood_diary/common/models/isar/diary.dart';
|
||||
import 'package:mood_diary/utils/utils.dart';
|
||||
|
||||
import 'local_send_client_state.dart';
|
||||
|
||||
class LocalSendClientLogic extends GetxController {
|
||||
final LocalSendClientState state = LocalSendClientState();
|
||||
|
||||
late RawDatagramSocket socket;
|
||||
Timer? timer;
|
||||
|
||||
@override
|
||||
void onReady() async {
|
||||
super.onReady();
|
||||
socket = await RawDatagramSocket.bind(InternetAddress.anyIPv4, 0);
|
||||
socket.broadcastEnabled = true;
|
||||
await startFindServer();
|
||||
}
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
socket.close();
|
||||
timer?.cancel();
|
||||
super.onClose();
|
||||
}
|
||||
|
||||
void _sendBroadcast() {
|
||||
const message = 'Looking for server';
|
||||
socket.send(message.codeUnits, InternetAddress('255.255.255.255'), state.scanPort);
|
||||
Utils().logUtil.printInfo('Broadcast sent');
|
||||
}
|
||||
|
||||
// 尝试在 30 秒内找到服务器
|
||||
Future<bool> startFindServer() async {
|
||||
state.isFindingServer = true;
|
||||
update();
|
||||
|
||||
final found = await _findServer(timeout: const Duration(seconds: 30));
|
||||
|
||||
if (found) {
|
||||
Utils().noticeUtil.showToast('找到服务器');
|
||||
} else {
|
||||
state.isFindingServer = false;
|
||||
update();
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
// 重新开始查找服务器
|
||||
Future<void> restartFindServer() async {
|
||||
// 确保之前的监听已停止
|
||||
timer?.cancel();
|
||||
socket.close();
|
||||
|
||||
// 重新初始化 socket 和监听
|
||||
socket = await RawDatagramSocket.bind(InternetAddress.anyIPv4, 0);
|
||||
socket.broadcastEnabled = true;
|
||||
|
||||
await startFindServer();
|
||||
}
|
||||
|
||||
Future<bool> _findServer({required Duration timeout}) async {
|
||||
final completer = Completer<bool>();
|
||||
|
||||
// 启动 30 秒超时定时器
|
||||
Future.delayed(timeout, () {
|
||||
if (!completer.isCompleted) {
|
||||
timer?.cancel();
|
||||
completer.complete(false);
|
||||
}
|
||||
});
|
||||
|
||||
// 轮询发送广播消息
|
||||
timer = Timer.periodic(state.broadcastInterval, (timer) {
|
||||
_sendBroadcast();
|
||||
});
|
||||
|
||||
// 监听服务器响应
|
||||
socket.listen((RawSocketEvent event) async {
|
||||
if (event == RawSocketEvent.read) {
|
||||
final datagram = socket.receive();
|
||||
if (datagram != null) {
|
||||
final serverResponse = String.fromCharCodes(datagram.data);
|
||||
Utils().logUtil.printInfo('Found server: $serverResponse');
|
||||
|
||||
final serverInfo = serverResponse.split(':');
|
||||
state.serverIp = serverInfo[0];
|
||||
state.serverPort = int.parse(serverInfo[1]);
|
||||
state.isFindingServer = false;
|
||||
update();
|
||||
|
||||
timer?.cancel();
|
||||
socket.close();
|
||||
|
||||
if (!completer.isCompleted) {
|
||||
completer.complete(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 初次发送广播
|
||||
_sendBroadcast();
|
||||
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
// 向服务器发送数据并监听进度
|
||||
Future<void> sendData(Diary diary) async {
|
||||
// 创建 FormData 并同步添加 JSON 和文件
|
||||
dio.FormData formData = dio.FormData();
|
||||
|
||||
// 添加 JSON 数据
|
||||
formData.fields.add(MapEntry('diary', jsonEncode(diary.toJson())));
|
||||
|
||||
// 同步添加图片文件
|
||||
for (var imageName in diary.imageName) {
|
||||
final filePath = Utils().fileUtil.getRealPath('image', imageName);
|
||||
formData.files.add(MapEntry(
|
||||
'image',
|
||||
await dio.MultipartFile.fromFile(filePath, filename: imageName),
|
||||
));
|
||||
}
|
||||
|
||||
// 同步添加视频文件
|
||||
for (var videoName in diary.videoName) {
|
||||
final filePath = Utils().fileUtil.getRealPath('video', videoName);
|
||||
formData.files.add(MapEntry(
|
||||
'video',
|
||||
await dio.MultipartFile.fromFile(filePath, filename: videoName),
|
||||
));
|
||||
}
|
||||
|
||||
// 同步添加缩略图文件
|
||||
for (var videoName in diary.videoName) {
|
||||
final filePath = Utils().fileUtil.getRealPath('thumbnail', videoName);
|
||||
formData.files.add(MapEntry(
|
||||
'thumbnail',
|
||||
await dio.MultipartFile.fromFile(filePath, filename: 'thumbnail-${videoName.substring(6, 42)}.jpeg'),
|
||||
));
|
||||
}
|
||||
|
||||
// 同步添加音频文件
|
||||
for (var audioName in diary.audioName) {
|
||||
final filePath = Utils().fileUtil.getRealPath('audio', audioName);
|
||||
formData.files.add(MapEntry(
|
||||
'audio',
|
||||
await dio.MultipartFile.fromFile(filePath, filename: audioName),
|
||||
));
|
||||
}
|
||||
|
||||
final startTime = DateTime.now();
|
||||
|
||||
// 发送请求并监听进度
|
||||
await Utils().httpUtil.dio.post(
|
||||
'http://${state.serverIp}:${state.serverPort}',
|
||||
data: formData,
|
||||
onSendProgress: (int sent, int total) {
|
||||
final currentTime = DateTime.now();
|
||||
final timeElapsed = currentTime.difference(startTime).inMilliseconds / 1000;
|
||||
state.speed.value = sent / timeElapsed;
|
||||
state.progress.value = sent / total;
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class LocalSendClientState {
|
||||
int? serverPort;
|
||||
String? serverIp;
|
||||
|
||||
int scanPort = 50001;
|
||||
Duration broadcastInterval = const Duration(seconds: 3);
|
||||
|
||||
RxDouble progress = .0.obs;
|
||||
RxDouble speed = .0.obs;
|
||||
|
||||
bool isFindingServer = false;
|
||||
|
||||
LocalSendClientState();
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:mood_diary/common/models/isar/diary.dart';
|
||||
import 'package:mood_diary/utils/utils.dart';
|
||||
|
||||
import 'local_send_client_logic.dart';
|
||||
import 'local_send_client_state.dart';
|
||||
|
||||
class LocalSendClientComponent extends StatelessWidget {
|
||||
const LocalSendClientComponent({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final LocalSendClientLogic logic = Get.put(LocalSendClientLogic());
|
||||
final LocalSendClientState state = Bind.find<LocalSendClientLogic>().state;
|
||||
|
||||
return GetBuilder<LocalSendClientLogic>(
|
||||
assignId: true,
|
||||
builder: (_) {
|
||||
if (state.isFindingServer) {
|
||||
return const ListTile(
|
||||
title: Text('查找服务器'),
|
||||
subtitle: LinearProgressIndicator(),
|
||||
);
|
||||
} else if (state.serverIp == null) {
|
||||
return ListTile(
|
||||
title: const Text('未找到服务器'),
|
||||
leading: const FaIcon(FontAwesomeIcons.triangleExclamation),
|
||||
trailing: FilledButton(
|
||||
onPressed: () {
|
||||
logic.restartFindServer();
|
||||
},
|
||||
child: const FaIcon(FontAwesomeIcons.repeat)),
|
||||
);
|
||||
} else {
|
||||
return ListTile(
|
||||
title: Text(state.serverIp!),
|
||||
subtitle: Obx(() {
|
||||
return Text(
|
||||
'${Utils().fileUtil.bytesToUnits(state.speed.value.toInt())['size']}${Utils().fileUtil.bytesToUnits(state.speed.value.toInt())['unit']}/s ${state.progress.value * 100}%');
|
||||
}),
|
||||
leading: const FaIcon(FontAwesomeIcons.server),
|
||||
trailing: FilledButton(
|
||||
onPressed: () async {
|
||||
await logic
|
||||
.sendData((await Utils().isarUtil.getDiaryByID(fastHash('01931742-bd6c-738a-a482-cc8a398b5c0c')))!);
|
||||
},
|
||||
child: const FaIcon(FontAwesomeIcons.solidPaperPlane),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
42
lib/components/local_send/local_send_logic.dart
Normal file
42
lib/components/local_send/local_send_logic.dart
Normal file
@@ -0,0 +1,42 @@
|
||||
import 'package:get/get.dart';
|
||||
import 'package:network_info_plus/network_info_plus.dart';
|
||||
|
||||
import 'local_send_state.dart';
|
||||
|
||||
class LocalSendLogic extends GetxController {
|
||||
final LocalSendState state = LocalSendState();
|
||||
final networkInfo = NetworkInfo();
|
||||
|
||||
@override
|
||||
void onReady() async {
|
||||
await getWifiInfo();
|
||||
super.onReady();
|
||||
}
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
super.onClose();
|
||||
}
|
||||
|
||||
Future<void> getWifiInfo() async {
|
||||
state.deviceIpAddress = (await networkInfo.getWifiIP()) ?? '无法获取';
|
||||
state.wifiSSID = (await networkInfo.getWifiName()) ?? '无法获取';
|
||||
update(['WifiInfo']);
|
||||
}
|
||||
|
||||
// // client
|
||||
// Future<void> findServer() async {
|
||||
// state.findingServer.value = true;
|
||||
// var serverInfo = await localSendClient.findServer();
|
||||
// if (serverInfo != null) {
|
||||
// state.serverIp.value = serverInfo['ip'];
|
||||
// state.serverPort.value = serverInfo['port'];
|
||||
// state.findingServer.value = false;
|
||||
// }
|
||||
// }
|
||||
|
||||
void changeType(String value) {
|
||||
state.type = value;
|
||||
update(['SegmentButton', 'Panel']);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:get/get.dart';
|
||||
import 'package:mood_diary/common/models/isar/diary.dart';
|
||||
import 'package:mood_diary/utils/utils.dart';
|
||||
import 'package:network_info_plus/network_info_plus.dart';
|
||||
import 'package:shelf/shelf.dart' as shelf;
|
||||
import 'package:shelf/shelf_io.dart';
|
||||
import 'package:shelf_multipart/shelf_multipart.dart';
|
||||
|
||||
import 'local_send_server_state.dart';
|
||||
|
||||
class LocalSendServerLogic extends GetxController {
|
||||
final LocalSendServerState state = LocalSendServerState();
|
||||
late RawDatagramSocket socket;
|
||||
HttpServer? httpServer;
|
||||
|
||||
late final networkInfo = NetworkInfo();
|
||||
|
||||
@override
|
||||
void onReady() async {
|
||||
socket = await RawDatagramSocket.bind(InternetAddress.anyIPv4, state.scanPort);
|
||||
state.serverIp = await networkInfo.getWifiIP();
|
||||
update();
|
||||
if (state.serverIp != null) {
|
||||
await startBroadcastListener();
|
||||
await startServer();
|
||||
}
|
||||
super.onReady();
|
||||
}
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
socket.close();
|
||||
httpServer?.close(force: true);
|
||||
super.onClose();
|
||||
}
|
||||
|
||||
// 启动UDP广播监听
|
||||
Future<void> startBroadcastListener() async {
|
||||
Utils().logUtil.printInfo('Listening for broadcast on port ${state.scanPort}');
|
||||
socket.listen((RawSocketEvent event) {
|
||||
if (event == RawSocketEvent.read) {
|
||||
final datagram = socket.receive();
|
||||
if (datagram != null) {
|
||||
final message = String.fromCharCodes(datagram.data);
|
||||
Utils().logUtil.printInfo('Received broadcast: $message from ${datagram.address.address}');
|
||||
final response = '${state.serverIp}:${state.transferPort}';
|
||||
socket.send(response.codeUnits, datagram.address, datagram.port);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 启动HTTP服务器
|
||||
Future<void> startServer() async {
|
||||
final handler = const shelf.Pipeline().addMiddleware(shelf.logRequests()).addHandler(_handleRequest);
|
||||
httpServer = await serve(handler, state.serverIp!, state.transferPort);
|
||||
Utils().logUtil.printInfo('Server started on http://${state.serverIp}:${state.transferPort}');
|
||||
}
|
||||
|
||||
Future<shelf.Response> _handleRequest(shelf.Request request) async {
|
||||
late Diary diary;
|
||||
List<File> images = [];
|
||||
List<File> videos = [];
|
||||
List<File> thumbnails = [];
|
||||
List<File> audios = [];
|
||||
|
||||
// 处理表单数据
|
||||
if (request.formData() case var form?) {
|
||||
await for (final formData in form.formData) {
|
||||
final name = formData.name;
|
||||
// 读取日记 JSON 数据
|
||||
if (name == 'diary') {
|
||||
diary = Diary.fromJson(jsonDecode(await formData.part.readString()));
|
||||
} else if (name == 'image' || name == 'video' || name == 'thumbnail' || name == 'audio') {
|
||||
if (formData.filename != null) {
|
||||
final tempFile = File(Utils().fileUtil.getCachePath(formData.filename!));
|
||||
final sink = tempFile.openWrite();
|
||||
await formData.part.pipe(sink);
|
||||
await sink.close();
|
||||
|
||||
// 分类文件
|
||||
if (name == 'image') images.add(tempFile);
|
||||
if (name == 'video') videos.add(tempFile);
|
||||
if (name == 'thumbnail') thumbnails.add(tempFile);
|
||||
if (name == 'audio') audios.add(tempFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return shelf.Response.ok('Data and files received successfully');
|
||||
}
|
||||
|
||||
// 辅助函数,用于按块保存文件并调用进度更新回调
|
||||
Future<File> _saveFileWithProgress(Stream<List<int>> stream, String filename, String? mimeType, int contentLength,
|
||||
void Function(int chunkLength) onProgress) async {
|
||||
final file = File('/path/to/save/$filename');
|
||||
final sink = file.openWrite();
|
||||
await for (final chunk in stream) {
|
||||
sink.add(chunk);
|
||||
onProgress(chunk.length); // 调用进度更新回调
|
||||
}
|
||||
await sink.close();
|
||||
return file;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class LocalSendServerState {
|
||||
String? serverIp;
|
||||
|
||||
int scanPort = 50001;
|
||||
int transferPort = 54321;
|
||||
|
||||
RxDouble progress = .0.obs;
|
||||
|
||||
RxDouble speed = .0.obs;
|
||||
|
||||
LocalSendServerState();
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import 'local_send_server_logic.dart';
|
||||
import 'local_send_server_state.dart';
|
||||
|
||||
class LocalSendServerComponent extends StatelessWidget {
|
||||
const LocalSendServerComponent({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final LocalSendServerLogic logic = Get.put(LocalSendServerLogic());
|
||||
final LocalSendServerState state = Bind.find<LocalSendServerLogic>().state;
|
||||
|
||||
return GetBuilder<LocalSendServerLogic>(
|
||||
assignId: true,
|
||||
builder: (_) {
|
||||
return ListTile(
|
||||
title: const Text('服务器已启动在'),
|
||||
subtitle: Text('${state.serverIp}:${state.transferPort}'),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
19
lib/components/local_send/local_send_state.dart
Normal file
19
lib/components/local_send/local_send_state.dart
Normal file
@@ -0,0 +1,19 @@
|
||||
import 'package:get/get.dart';
|
||||
|
||||
class LocalSendState {
|
||||
String wifiSSID = '';
|
||||
|
||||
String deviceIpAddress = '';
|
||||
|
||||
int transferPort = 54321;
|
||||
int scanPort = 50001;
|
||||
|
||||
RxBool findingServer = false.obs;
|
||||
|
||||
RxString serverIp = ''.obs;
|
||||
RxnInt serverPort = RxnInt();
|
||||
|
||||
String type = 'send';
|
||||
|
||||
LocalSendState();
|
||||
}
|
||||
126
lib/components/local_send/local_send_view.dart
Normal file
126
lib/components/local_send/local_send_view.dart
Normal file
@@ -0,0 +1,126 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:mood_diary/components/local_send/local_send_client/local_send_client_view.dart';
|
||||
import 'package:mood_diary/components/local_send/local_send_server/local_send_server_view.dart';
|
||||
|
||||
import 'local_send_logic.dart';
|
||||
import 'local_send_state.dart';
|
||||
|
||||
class LocalSendComponent extends StatelessWidget {
|
||||
const LocalSendComponent({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final LocalSendLogic logic = Get.put(LocalSendLogic());
|
||||
final LocalSendState state = Bind.find<LocalSendLogic>().state;
|
||||
|
||||
final textStyle = Theme.of(context).textTheme;
|
||||
|
||||
Widget buildWifiInfo() {
|
||||
return Column(
|
||||
spacing: 8.0,
|
||||
children: [
|
||||
Text(
|
||||
'请检查以下信息,确保两台设备在同一局域网下',
|
||||
style: textStyle.titleSmall,
|
||||
),
|
||||
GetBuilder<LocalSendLogic>(
|
||||
id: 'WifiInfo',
|
||||
builder: (_) {
|
||||
return Wrap(
|
||||
spacing: 8.0,
|
||||
children: [
|
||||
ActionChip(
|
||||
onPressed: () {
|
||||
logic.getWifiInfo();
|
||||
},
|
||||
label: Text('SSID : ${state.wifiSSID}'),
|
||||
),
|
||||
ActionChip(
|
||||
onPressed: () {
|
||||
logic.getWifiInfo();
|
||||
},
|
||||
label: Text('IP : ${state.deviceIpAddress}'),
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildPortInfo() {
|
||||
return Column(
|
||||
spacing: 8.0,
|
||||
children: [
|
||||
Text(
|
||||
'如果您不知道这是什么,请不要修改',
|
||||
style: textStyle.titleSmall,
|
||||
),
|
||||
Wrap(
|
||||
spacing: 8.0,
|
||||
children: [
|
||||
ActionChip(
|
||||
onPressed: () {},
|
||||
label: Text('扫描端口: ${state.scanPort}'),
|
||||
),
|
||||
ActionChip(
|
||||
onPressed: () {},
|
||||
label: Text('传输端口: ${state.transferPort}'),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
return GetBuilder<LocalSendLogic>(
|
||||
assignId: true,
|
||||
builder: (_) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(16.0),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
spacing: 8.0,
|
||||
children: [
|
||||
Wrap(
|
||||
spacing: 8.0,
|
||||
runSpacing: 8.0,
|
||||
alignment: WrapAlignment.spaceEvenly,
|
||||
children: [buildWifiInfo(), buildPortInfo()],
|
||||
),
|
||||
GetBuilder<LocalSendLogic>(
|
||||
id: 'SegmentButton',
|
||||
builder: (_) {
|
||||
return SegmentedButton<String>(
|
||||
segments: const [
|
||||
ButtonSegment(
|
||||
value: 'send',
|
||||
icon: Icon(Icons.send),
|
||||
label: Text('发送'),
|
||||
),
|
||||
ButtonSegment(
|
||||
value: 'receive',
|
||||
icon: Icon(Icons.move_to_inbox_rounded),
|
||||
label: Text('接收'),
|
||||
),
|
||||
],
|
||||
selected: {state.type},
|
||||
onSelectionChanged: (newSelection) {
|
||||
logic.changeType(newSelection.first);
|
||||
},
|
||||
);
|
||||
}),
|
||||
GetBuilder<LocalSendLogic>(
|
||||
id: 'Panel',
|
||||
builder: (_) {
|
||||
return state.type == 'send' ? const LocalSendClientComponent() : const LocalSendServerComponent();
|
||||
}),
|
||||
],
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -47,8 +47,7 @@ class RecordSheetLogic extends GetxController with GetTickerProviderStateMixin {
|
||||
}
|
||||
|
||||
Future<void> startRecorder() async {
|
||||
if (await Utils().permissionUtil.checkPermission(Permission.microphone) &&
|
||||
await Utils().permissionUtil.checkPermission(Permission.bluetoothConnect)) {
|
||||
if (await Utils().permissionUtil.checkPermission(Permission.microphone)) {
|
||||
await animationController.forward();
|
||||
state.isRecording.value = true;
|
||||
state.isStarted.value = true;
|
||||
|
||||
@@ -12,7 +12,6 @@ import 'package:intl/find_locale.dart';
|
||||
import 'package:media_kit/media_kit.dart';
|
||||
import 'package:mood_diary/router/app_pages.dart';
|
||||
import 'package:mood_diary/router/app_routes.dart';
|
||||
import 'package:mood_diary/utils/channel.dart';
|
||||
import 'package:mood_diary/utils/utils.dart';
|
||||
|
||||
Future<void> initSystem() async {
|
||||
@@ -35,8 +34,6 @@ void platFormOption() {
|
||||
if (Platform.isAndroid) {
|
||||
//设置高刷
|
||||
unawaited(FlutterDisplayMode.setHighRefreshRate());
|
||||
//设置状态栏沉浸
|
||||
unawaited(ViewChannel.setSystemUIVisibility());
|
||||
}
|
||||
if (Platform.isWindows || Platform.isMacOS || Platform.isLinux) {
|
||||
doWhenWindowReady(() {
|
||||
|
||||
19
lib/pages/backup_sync/backup_sync_logic.dart
Normal file
19
lib/pages/backup_sync/backup_sync_logic.dart
Normal file
@@ -0,0 +1,19 @@
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import 'backup_sync_state.dart';
|
||||
|
||||
class BackupSyncLogic extends GetxController {
|
||||
final BackupSyncState state = BackupSyncState();
|
||||
|
||||
@override
|
||||
void onReady() {
|
||||
// TODO: implement onReady
|
||||
super.onReady();
|
||||
}
|
||||
|
||||
@override
|
||||
void onClose() {
|
||||
// TODO: implement onClose
|
||||
super.onClose();
|
||||
}
|
||||
}
|
||||
5
lib/pages/backup_sync/backup_sync_state.dart
Normal file
5
lib/pages/backup_sync/backup_sync_state.dart
Normal file
@@ -0,0 +1,5 @@
|
||||
class BackupSyncState {
|
||||
BackupSyncState() {
|
||||
///Initialize variables
|
||||
}
|
||||
}
|
||||
36
lib/pages/backup_sync/backup_sync_view.dart
Normal file
36
lib/pages/backup_sync/backup_sync_view.dart
Normal file
@@ -0,0 +1,36 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:mood_diary/components/local_send/local_send_view.dart';
|
||||
|
||||
import 'backup_sync_logic.dart';
|
||||
import 'backup_sync_state.dart';
|
||||
|
||||
class BackupSyncPage extends StatelessWidget {
|
||||
const BackupSyncPage({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final BackupSyncLogic logic = Get.put(BackupSyncLogic());
|
||||
final BackupSyncState state = Bind.find<BackupSyncLogic>().state;
|
||||
|
||||
return Scaffold(
|
||||
appBar: AppBar(
|
||||
title: const Text('备份与同步'),
|
||||
),
|
||||
body: ListView(
|
||||
children: const [
|
||||
ExpansionTile(
|
||||
leading: Icon(Icons.wifi_tethering_rounded),
|
||||
title: Text('局域网传输'),
|
||||
children: [LocalSendComponent()],
|
||||
),
|
||||
ExpansionTile(
|
||||
leading: Icon(Icons.backup_rounded),
|
||||
title: Text('WebDav'),
|
||||
children: [],
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ class CalendarPage extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final logic = Bind.find<CalendarLogic>();
|
||||
final logic = Get.put(CalendarLogic());
|
||||
final state = Bind.find<CalendarLogic>().state;
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
final i18n = AppLocalizations.of(context)!;
|
||||
|
||||
@@ -12,8 +12,7 @@ class DiaryLogic extends GetxController with GetTickerProviderStateMixin {
|
||||
final DiaryState state = DiaryState();
|
||||
|
||||
//初始化tab控制器,长度加一由于有一个默认分类
|
||||
late TabController tabController =
|
||||
TabController(length: state.categoryList.length + 1, vsync: this);
|
||||
late TabController tabController = TabController(length: state.categoryList.length + 1, vsync: this);
|
||||
|
||||
late HomeLogic homeLogic = Bind.find<HomeLogic>();
|
||||
|
||||
@@ -43,10 +42,9 @@ class DiaryLogic extends GetxController with GetTickerProviderStateMixin {
|
||||
/// 在动态更新分类后要重新监听
|
||||
void _tabBarListener() {
|
||||
if (tabController.indexIsChanging) return;
|
||||
_checkPageChange();
|
||||
checkPageChange();
|
||||
// 检查是否显示顶部内容
|
||||
_checkShowTop();
|
||||
|
||||
homeLogic.resetNavigatorBar();
|
||||
}
|
||||
|
||||
@@ -68,9 +66,7 @@ class DiaryLogic extends GetxController with GetTickerProviderStateMixin {
|
||||
if (tabController.index == 0) {
|
||||
await Bind.find<DiaryTabViewLogic>(tag: 'default').paginationDiary();
|
||||
} else {
|
||||
await Bind.find<DiaryTabViewLogic>(
|
||||
tag: state.categoryList[tabController.index - 1].id)
|
||||
.paginationDiary();
|
||||
await Bind.find<DiaryTabViewLogic>(tag: state.categoryList[tabController.index - 1].id).paginationDiary();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -99,14 +95,10 @@ class DiaryLogic extends GetxController with GetTickerProviderStateMixin {
|
||||
/// 需要在以下情况调用
|
||||
/// 1. tab bar 修改
|
||||
/// 2. update ho
|
||||
void _checkPageChange() {
|
||||
void checkPageChange() {
|
||||
state.currentTabBarIndex = tabController.index;
|
||||
|
||||
// 获取当前分类ID,若为默认分类,设为 'default'
|
||||
String categoryId = state.currentTabBarIndex == 0
|
||||
? 'default'
|
||||
: state.categoryList[state.currentTabBarIndex - 1].id;
|
||||
|
||||
String categoryId = state.currentTabBarIndex == 0 ? 'default' : state.categoryList[state.currentTabBarIndex - 1].id;
|
||||
// 遍历 keyMap,更新每个分类的状态
|
||||
state.keyMap.forEach((k, v) {
|
||||
v.currentState?.onPageChange(k == categoryId);
|
||||
@@ -129,15 +121,13 @@ class DiaryLogic extends GetxController with GetTickerProviderStateMixin {
|
||||
}
|
||||
} else {
|
||||
//查找分类对应的位置,加一是因为默认分类占了一个
|
||||
tabViewIndex =
|
||||
state.categoryList.indexWhere((e) => e.id == categoryId) + 1;
|
||||
tabViewIndex = state.categoryList.indexWhere((e) => e.id == categoryId) + 1;
|
||||
if (jump && tabController.index != 0) {
|
||||
tabController.animateTo(tabViewIndex);
|
||||
}
|
||||
}
|
||||
//如果控制器已经存在,重新获取,如果不存在,不需要任何操作
|
||||
if (tabViewIndex != 0 &&
|
||||
Bind.isRegistered<DiaryTabViewLogic>(tag: categoryId)) {
|
||||
if (tabViewIndex != 0 && Bind.isRegistered<DiaryTabViewLogic>(tag: categoryId)) {
|
||||
await Bind.find<DiaryTabViewLogic>(tag: categoryId).getDiary();
|
||||
}
|
||||
await Bind.find<DiaryTabViewLogic>(tag: 'default').getDiary();
|
||||
@@ -154,9 +144,8 @@ class DiaryLogic extends GetxController with GetTickerProviderStateMixin {
|
||||
state.categoryList = await Utils().isarUtil.getAllCategoryAsync();
|
||||
|
||||
// 移除 Map 中不再存在的 Category id
|
||||
state.keyMap.removeWhere((k, v) =>
|
||||
!state.categoryList.map((category) => category.id).contains(k) &&
|
||||
k != 'default');
|
||||
state.keyMap
|
||||
.removeWhere((k, v) => !state.categoryList.map((category) => category.id).contains(k) && k != 'default');
|
||||
|
||||
// 为新的 Category 添加新的 GlobalKey
|
||||
for (var category in state.categoryList) {
|
||||
@@ -173,31 +162,22 @@ class DiaryLogic extends GetxController with GetTickerProviderStateMixin {
|
||||
|
||||
//重新创建控制器
|
||||
tabController.removeListener(_tabBarListener);
|
||||
tabController =
|
||||
TabController(length: state.categoryList.length + 1, vsync: this);
|
||||
tabController = TabController(length: state.categoryList.length + 1, vsync: this);
|
||||
tabController.addListener(_tabBarListener);
|
||||
update();
|
||||
_checkPageChange();
|
||||
update(['All']);
|
||||
checkPageChange();
|
||||
}
|
||||
|
||||
//切换视图模式
|
||||
Future<void> changeViewMode(ViewModeType viewModeType) async {
|
||||
state.viewModeType = viewModeType;
|
||||
if (state.currentTabBarIndex == 0) {
|
||||
Bind.find<DiaryTabViewLogic>(tag: 'default').update(['TabView']);
|
||||
} else {
|
||||
Bind.find<DiaryTabViewLogic>(
|
||||
tag: state.categoryList[state.currentTabBarIndex - 1].id)
|
||||
.update(['TabView']);
|
||||
}
|
||||
homeLogic.state.isToTopShow = false;
|
||||
homeLogic.update(['Fab']);
|
||||
update(['TabBarView']);
|
||||
_checkShowTop();
|
||||
await Utils().prefUtil.setValue<int>('homeViewMode', viewModeType.number);
|
||||
}
|
||||
|
||||
// 回到顶部函数
|
||||
Future<void> toTop() async {
|
||||
await state.innerController.animateTo(0.0,
|
||||
duration: const Duration(milliseconds: 200), curve: Curves.linear);
|
||||
await state.innerController.animateTo(0.0, duration: const Duration(milliseconds: 200), curve: Curves.easeInOut);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:get/get.dart';
|
||||
@@ -14,7 +15,7 @@ class DiaryPage extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final logic = Bind.find<DiaryLogic>();
|
||||
final logic = Get.put(DiaryLogic());
|
||||
final state = Bind.find<DiaryLogic>().state;
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
final i18n = AppLocalizations.of(context)!;
|
||||
@@ -37,6 +38,7 @@ class DiaryPage extends StatelessWidget {
|
||||
tabAlignment: TabAlignment.start,
|
||||
indicatorSize: TabBarIndicatorSize.label,
|
||||
splashFactory: NoSplash.splashFactory,
|
||||
dragStartBehavior: DragStartBehavior.start,
|
||||
indicator: UnderlineTabIndicator(
|
||||
borderSide: BorderSide(
|
||||
color: colorScheme.primary,
|
||||
@@ -63,83 +65,96 @@ class DiaryPage extends StatelessWidget {
|
||||
|
||||
Widget buildTabBarView() {
|
||||
List<Widget> allViews = [];
|
||||
|
||||
// 添加全部日记页面
|
||||
allViews.add(buildDiaryView(0, state.keyMap['default'], null));
|
||||
|
||||
// 添加分类日记页面
|
||||
allViews.addAll(List.generate(state.categoryList.length, (index) {
|
||||
return buildDiaryView(
|
||||
index + 1,
|
||||
state.keyMap[state.categoryList[index].id],
|
||||
state.categoryList[index].id);
|
||||
return buildDiaryView(index + 1, state.keyMap[state.categoryList[index].id], state.categoryList[index].id);
|
||||
}));
|
||||
|
||||
return TabBarView(
|
||||
controller: logic.tabController,
|
||||
children: allViews,
|
||||
return NotificationListener<ScrollNotification>(
|
||||
onNotification: (ScrollNotification notification) {
|
||||
if (notification.metrics.axis == Axis.horizontal) {
|
||||
logic.checkPageChange();
|
||||
}
|
||||
return true;
|
||||
},
|
||||
child: TabBarView(
|
||||
controller: logic.tabController,
|
||||
dragStartBehavior: DragStartBehavior.start,
|
||||
children: allViews,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return NestedScrollView(
|
||||
key: state.nestedScrollKey,
|
||||
headerSliverBuilder: (context, _) {
|
||||
return [
|
||||
SliverOverlapAbsorber(
|
||||
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
||||
sliver: SliverAppBar(
|
||||
title: Text(
|
||||
state.customTitleName.isNotEmpty
|
||||
? state.customTitleName
|
||||
: i18n.appName,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
showDragHandle: true,
|
||||
useSafeArea: true,
|
||||
builder: (context) {
|
||||
return const SearchSheetComponent();
|
||||
});
|
||||
},
|
||||
icon: const Icon(Icons.search),
|
||||
tooltip: i18n.diaryPageSearchButton,
|
||||
),
|
||||
PopupMenuButton(
|
||||
offset: const Offset(0, 46),
|
||||
tooltip: i18n.diaryPageViewModeButton,
|
||||
itemBuilder: (context) {
|
||||
return <PopupMenuEntry<String>>[
|
||||
CheckedPopupMenuItem(
|
||||
checked: state.viewModeType == ViewModeType.list,
|
||||
onTap: () async {
|
||||
await logic.changeViewMode(ViewModeType.list);
|
||||
return GetBuilder<DiaryLogic>(
|
||||
assignId: true,
|
||||
builder: (_) {
|
||||
return NestedScrollView(
|
||||
key: state.nestedScrollKey,
|
||||
headerSliverBuilder: (context, _) {
|
||||
return [
|
||||
SliverOverlapAbsorber(
|
||||
handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
|
||||
sliver: SliverAppBar(
|
||||
title: GetBuilder<DiaryLogic>(
|
||||
id: 'Title',
|
||||
builder: (_) {
|
||||
return Text(
|
||||
state.customTitleName.isNotEmpty ? state.customTitleName : i18n.appName,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
);
|
||||
}),
|
||||
pinned: true,
|
||||
actions: [
|
||||
IconButton(
|
||||
onPressed: () {
|
||||
showModalBottomSheet(
|
||||
context: context,
|
||||
showDragHandle: true,
|
||||
useSafeArea: true,
|
||||
builder: (context) {
|
||||
return const SearchSheetComponent();
|
||||
});
|
||||
},
|
||||
child: Text(i18n.diaryViewModeList),
|
||||
icon: const Icon(Icons.search),
|
||||
tooltip: i18n.diaryPageSearchButton,
|
||||
),
|
||||
const PopupMenuDivider(),
|
||||
CheckedPopupMenuItem(
|
||||
checked: state.viewModeType == ViewModeType.grid,
|
||||
onTap: () async {
|
||||
await logic.changeViewMode(ViewModeType.grid);
|
||||
PopupMenuButton(
|
||||
offset: const Offset(0, 46),
|
||||
tooltip: i18n.diaryPageViewModeButton,
|
||||
itemBuilder: (context) {
|
||||
return <PopupMenuEntry<String>>[
|
||||
CheckedPopupMenuItem(
|
||||
checked: state.viewModeType == ViewModeType.list,
|
||||
onTap: () async {
|
||||
await logic.changeViewMode(ViewModeType.list);
|
||||
},
|
||||
child: Text(i18n.diaryViewModeList),
|
||||
),
|
||||
const PopupMenuDivider(),
|
||||
CheckedPopupMenuItem(
|
||||
checked: state.viewModeType == ViewModeType.grid,
|
||||
onTap: () async {
|
||||
await logic.changeViewMode(ViewModeType.grid);
|
||||
},
|
||||
child: Text(i18n.diaryViewModeGrid),
|
||||
),
|
||||
];
|
||||
},
|
||||
child: Text(i18n.diaryViewModeGrid),
|
||||
),
|
||||
];
|
||||
},
|
||||
],
|
||||
bottom: PreferredSize(preferredSize: const Size.fromHeight(46.0), child: buildTabBar()),
|
||||
),
|
||||
),
|
||||
],
|
||||
bottom: PreferredSize(
|
||||
preferredSize: const Size.fromHeight(46.0),
|
||||
child: buildTabBar()),
|
||||
),
|
||||
),
|
||||
];
|
||||
},
|
||||
body: buildTabBarView(),
|
||||
);
|
||||
];
|
||||
},
|
||||
body: GetBuilder<DiaryLogic>(
|
||||
id: 'TabBarView',
|
||||
builder: (_) {
|
||||
return buildTabBarView();
|
||||
}),
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ class MediaPage extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final logic = Bind.find<MediaLogic>();
|
||||
final logic = Get.put(MediaLogic());
|
||||
final state = Bind.find<MediaLogic>().state;
|
||||
final i18n = AppLocalizations.of(context)!;
|
||||
|
||||
|
||||
@@ -15,8 +15,7 @@ class SettingLogic extends GetxController {
|
||||
final SettingState state = SettingState();
|
||||
late final homeLogic = Bind.find<HomeLogic>();
|
||||
|
||||
late TextEditingController textEditingController =
|
||||
TextEditingController(text: state.customTitle);
|
||||
late TextEditingController textEditingController = TextEditingController(text: state.customTitle);
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
@@ -88,6 +87,10 @@ class SettingLogic extends GetxController {
|
||||
Get.toNamed(AppRoutes.diarySettingPage);
|
||||
}
|
||||
|
||||
void toBackupAndSyncPage() {
|
||||
Get.toNamed(AppRoutes.backupSyncPage);
|
||||
}
|
||||
|
||||
void cancelCustomTitle() {
|
||||
textEditingController.clear();
|
||||
Get.backLegacy();
|
||||
@@ -97,9 +100,7 @@ class SettingLogic extends GetxController {
|
||||
if (textEditingController.text.isNotEmpty) {
|
||||
state.customTitle = textEditingController.text;
|
||||
update(['CustomTitle']);
|
||||
await Utils()
|
||||
.prefUtil
|
||||
.setValue<String>('customTitleName', textEditingController.text);
|
||||
await Utils().prefUtil.setValue<String>('customTitleName', textEditingController.text);
|
||||
Get.backLegacy();
|
||||
textEditingController.clear();
|
||||
Utils().noticeUtil.showToast('重启应用后生效');
|
||||
@@ -129,8 +130,7 @@ class SettingLogic extends GetxController {
|
||||
//导入
|
||||
Future<void> import() async {
|
||||
Get.backLegacy();
|
||||
FilePickerResult? result = await FilePicker.platform
|
||||
.pickFiles(allowedExtensions: ['zip'], type: FileType.custom);
|
||||
FilePickerResult? result = await FilePicker.platform.pickFiles(allowedExtensions: ['zip'], type: FileType.custom);
|
||||
if (result != null) {
|
||||
Utils().noticeUtil.showToast('数据导入中,请不要离开页面');
|
||||
await Utils().fileUtil.extractFile(result.files.single.path!);
|
||||
|
||||
@@ -15,24 +15,14 @@ class SettingPage extends StatelessWidget {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final logic = Bind.find<SettingLogic>();
|
||||
final logic = Get.put(SettingLogic());
|
||||
final state = Bind.find<SettingLogic>().state;
|
||||
final textStyle = Theme.of(context).textTheme;
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
final i18n = AppLocalizations.of(context)!;
|
||||
|
||||
Widget buildManager() {
|
||||
return Column(
|
||||
children: [
|
||||
ListTile(
|
||||
title: Text(
|
||||
'管理',
|
||||
style: textStyle.titleLarge!.copyWith(color: colorScheme.primary, fontWeight: FontWeight.bold),
|
||||
),
|
||||
),
|
||||
const DashboardComponent(),
|
||||
],
|
||||
);
|
||||
return const DashboardComponent();
|
||||
}
|
||||
|
||||
Widget buildData() {
|
||||
@@ -56,6 +46,14 @@ class SettingPage extends StatelessWidget {
|
||||
},
|
||||
leading: const Icon(Icons.delete_outline),
|
||||
),
|
||||
ListTile(
|
||||
title: const Text('备份与同步'),
|
||||
trailing: const Icon(Icons.chevron_right),
|
||||
onTap: () {
|
||||
logic.toBackupAndSyncPage();
|
||||
},
|
||||
leading: const Icon(Icons.sync),
|
||||
),
|
||||
ListTile(
|
||||
title: Text(i18n.settingExport),
|
||||
onTap: () {
|
||||
|
||||
@@ -4,6 +4,8 @@ import 'package:mood_diary/pages/about/about_view.dart';
|
||||
import 'package:mood_diary/pages/agreement/agreement_view.dart';
|
||||
import 'package:mood_diary/pages/analyse/analyse_logic.dart';
|
||||
import 'package:mood_diary/pages/analyse/analyse_view.dart';
|
||||
import 'package:mood_diary/pages/backup_sync/backup_sync_logic.dart';
|
||||
import 'package:mood_diary/pages/backup_sync/backup_sync_view.dart';
|
||||
import 'package:mood_diary/pages/category_manager/category_manager_logic.dart';
|
||||
import 'package:mood_diary/pages/category_manager/category_manager_view.dart';
|
||||
import 'package:mood_diary/pages/diary_details/diary_details_view.dart';
|
||||
@@ -15,12 +17,8 @@ import 'package:mood_diary/pages/edit/edit_logic.dart';
|
||||
import 'package:mood_diary/pages/edit/edit_view.dart';
|
||||
import 'package:mood_diary/pages/font/font_logic.dart';
|
||||
import 'package:mood_diary/pages/font/font_view.dart';
|
||||
import 'package:mood_diary/pages/home/calendar/calendar_logic.dart';
|
||||
import 'package:mood_diary/pages/home/diary/diary_logic.dart';
|
||||
import 'package:mood_diary/pages/home/home_logic.dart';
|
||||
import 'package:mood_diary/pages/home/home_view.dart';
|
||||
import 'package:mood_diary/pages/home/media/media_logic.dart';
|
||||
import 'package:mood_diary/pages/home/setting/setting_logic.dart';
|
||||
import 'package:mood_diary/pages/image/image_logic.dart';
|
||||
import 'package:mood_diary/pages/image/image_view.dart';
|
||||
import 'package:mood_diary/pages/laboratory/laboratory_logic.dart';
|
||||
@@ -60,11 +58,6 @@ class AppPages {
|
||||
page: () => const HomePage(),
|
||||
binds: [
|
||||
Bind.lazyPut(() => HomeLogic()),
|
||||
Bind.lazyPut(() => DiaryLogic()),
|
||||
Bind.lazyPut(() => CalendarLogic()),
|
||||
Bind.lazyPut(() => MediaLogic()),
|
||||
//Bind.lazyPut(() => AssistantLogic()),
|
||||
Bind.lazyPut(() => SettingLogic()),
|
||||
],
|
||||
),
|
||||
//分析
|
||||
@@ -175,5 +168,10 @@ class AppPages {
|
||||
page: () => const DiarySettingPage(),
|
||||
binds: [Bind.lazyPut(() => DiarySettingLogic())],
|
||||
),
|
||||
GetPage(
|
||||
name: AppRoutes.backupSyncPage,
|
||||
page: () => const BackupSyncPage(),
|
||||
binds: [Bind.lazyPut(() => BackupSyncLogic())],
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
@@ -69,4 +69,7 @@ abstract class AppRoutes {
|
||||
|
||||
//日记个性化
|
||||
static const diarySettingPage = '/diarySetting';
|
||||
|
||||
//备份与同步页
|
||||
static const backupSyncPage = '/backupSync';
|
||||
}
|
||||
|
||||
27
lib/utils/aes_util.dart
Normal file
27
lib/utils/aes_util.dart
Normal file
@@ -0,0 +1,27 @@
|
||||
import 'dart:convert';
|
||||
import 'package:encrypt/encrypt.dart' as encrypt;
|
||||
|
||||
class AESUtil {
|
||||
final encrypt.Key _key;
|
||||
final encrypt.IV _iv;
|
||||
|
||||
// 初始化时设置密钥和初始化向量(IV)
|
||||
AESUtil(String key, String iv)
|
||||
: _key = encrypt.Key.fromUtf8(key.padRight(32, ' ')),
|
||||
// 32字节密钥
|
||||
_iv = encrypt.IV.fromUtf8(iv.padRight(16, ' ')); // 16字节IV
|
||||
|
||||
// 加密数据
|
||||
String encryptData(String data) {
|
||||
final encrypter = encrypt.Encrypter(encrypt.AES(_key));
|
||||
final encrypted = encrypter.encrypt(data, iv: _iv);
|
||||
return encrypted.base64; // 返回加密后的数据
|
||||
}
|
||||
|
||||
// 解密数据
|
||||
String decryptData(String encryptedData) {
|
||||
final encrypter = encrypt.Encrypter(encrypt.AES(_key));
|
||||
final decrypted = encrypter.decrypt64(encryptedData, iv: _iv);
|
||||
return decrypted; // 返回解密后的数据
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,5 @@
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
class ViewChannel {
|
||||
static const MethodChannel _channel = MethodChannel('view_channel');
|
||||
|
||||
static setSystemUIVisibility() async {
|
||||
await _channel.invokeMethod('setSystemUIVisibility');
|
||||
}
|
||||
}
|
||||
|
||||
class FontChannel {
|
||||
static const MethodChannel _channel = MethodChannel('font_channel');
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import 'package:isar/isar.dart';
|
||||
import 'package:latlong2/latlong.dart';
|
||||
import 'package:mood_diary/common/models/isar/category.dart';
|
||||
import 'package:mood_diary/common/models/isar/diary.dart';
|
||||
import 'package:mood_diary/common/models/map.dart';
|
||||
import 'package:mood_diary/utils/utils.dart';
|
||||
import 'package:path/path.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
@@ -237,6 +239,24 @@ class IsarUtil {
|
||||
isar.close();
|
||||
}
|
||||
|
||||
// 获取用于地图显示的对象
|
||||
Future<List<DiaryMapItem>> getAllMapItem() async {
|
||||
List<DiaryMapItem> res = [];
|
||||
|
||||
/// 所有的日记
|
||||
/// 要满足以下条件
|
||||
/// 1. 有定位坐标
|
||||
/// 2. 有照片
|
||||
/// 3. show
|
||||
var diaries =
|
||||
await _isar.diarys.where().showEqualTo(true).positionIsNotEmpty().imageNameIsNotEmpty().findAllAsync();
|
||||
for (var diary in diaries) {
|
||||
res.add(DiaryMapItem(LatLng(double.parse(diary.position[0]), double.parse(diary.position[1])), diary.isarId,
|
||||
diary.imageName.first));
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
//构建搜索
|
||||
void buildSearch(String dir) async {
|
||||
var isar = Isar.open(
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:mood_diary/utils/utils.dart';
|
||||
|
||||
class LocalLanClient {
|
||||
final dio = Utils().httpUtil.dio;
|
||||
final int port;
|
||||
RawDatagramSocket? socket;
|
||||
|
||||
LocalLanClient({this.port = 4040});
|
||||
|
||||
/// 初始化UDP套接字
|
||||
Future<void> initSocket() async {
|
||||
socket = await RawDatagramSocket.bind(InternetAddress.anyIPv4, port);
|
||||
socket!.broadcastEnabled = true;
|
||||
print("UDP socket initialized on port $port");
|
||||
}
|
||||
|
||||
/// 广播设备信息
|
||||
void broadcastPresence() {
|
||||
final message = utf8.encode("LocalLanClient: device here");
|
||||
socket!.send(message, InternetAddress("224.0.0.1"), port);
|
||||
print("Presence broadcasted");
|
||||
}
|
||||
|
||||
/// 监听其他设备的广播消息
|
||||
void listenForDevices() {
|
||||
socket!.listen((RawSocketEvent event) {
|
||||
if (event == RawSocketEvent.read) {
|
||||
final packet = socket!.receive();
|
||||
if (packet != null) {
|
||||
final message = utf8.decode(packet.data);
|
||||
print("Discovered device at ${packet.address.address}: $message");
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// 发送文件到指定设备IP
|
||||
Future<void> sendFile(String filePath, String deviceIp) async {
|
||||
final file = File(filePath);
|
||||
final formData = FormData.fromMap({
|
||||
"file": await MultipartFile.fromFile(file.path, filename: file.path.split('/').last),
|
||||
});
|
||||
|
||||
try {
|
||||
final response = await dio.post(
|
||||
'http://$deviceIp:$port/upload',
|
||||
data: formData,
|
||||
);
|
||||
print("File sent successfully: ${response.data}");
|
||||
} catch (e) {
|
||||
print("Error sending file: $e");
|
||||
}
|
||||
}
|
||||
|
||||
/// 关闭UDP套接字
|
||||
void closeSocket() {
|
||||
socket?.close();
|
||||
print("Socket closed");
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:shelf/shelf.dart';
|
||||
import 'package:shelf/shelf_io.dart' as shelf_io;
|
||||
import 'package:shelf_multipart/shelf_multipart.dart';
|
||||
|
||||
class LocalLanServer {
|
||||
final int port;
|
||||
HttpServer? _server;
|
||||
|
||||
LocalLanServer({this.port = 4040});
|
||||
|
||||
/// 启动文件服务器
|
||||
Future<void> start() async {
|
||||
// 配置路由和中间件
|
||||
final handler = const Pipeline()
|
||||
.addMiddleware(logRequests()) // 日志中间件
|
||||
.addHandler(_router); // 路由处理器
|
||||
|
||||
// 启动 Shelf 服务器
|
||||
_server = await shelf_io.serve(handler, InternetAddress.anyIPv4, port);
|
||||
print("Shelf server started on port $port");
|
||||
}
|
||||
|
||||
/// 路由处理器
|
||||
Future<Response> _router(Request request) async {
|
||||
if (request.url.path == 'upload' && request.method == 'POST') {
|
||||
return _handleFileUpload(request);
|
||||
} else {
|
||||
return Response.notFound('Not Found');
|
||||
}
|
||||
}
|
||||
|
||||
/// 处理文件上传请求
|
||||
Future<Response> _handleFileUpload(Request request) async {
|
||||
try {
|
||||
final multipart = request.multipart();
|
||||
await for (final part in multipart!.parts) {
|
||||
final fileName = 'received_${DateTime.now().millisecondsSinceEpoch}.file';
|
||||
final file = File('/path/to/save/$fileName'); // 替换为实际保存路径
|
||||
final sink = file.openWrite();
|
||||
await part.pipe(sink); // 将文件写入本地
|
||||
await sink.close();
|
||||
print("File received: ${file.path}");
|
||||
}
|
||||
return Response.ok('File received successfully');
|
||||
} catch (e) {
|
||||
print("Error receiving file: $e");
|
||||
return Response.internalServerError(body: 'File upload failed');
|
||||
}
|
||||
}
|
||||
|
||||
/// 停止服务器
|
||||
Future<void> stop() async {
|
||||
await _server?.close();
|
||||
print("Shelf server stopped");
|
||||
}
|
||||
}
|
||||
@@ -18,9 +18,9 @@ class MediaUtil {
|
||||
List<String> nameList = [];
|
||||
for (var imageFile in imageFileList) {
|
||||
//生成新的名字
|
||||
var imageName = 'image-${const Uuid().v7()}.webp';
|
||||
var imageName = 'image-${const Uuid().v7()}.png';
|
||||
nameList.add(imageName);
|
||||
await _compressAndSaveImage(imageFile, Utils().fileUtil.getRealPath('image', imageName), CompressFormat.webp);
|
||||
await _compressAndSaveImage(imageFile, Utils().fileUtil.getRealPath('image', imageName), CompressFormat.png);
|
||||
}
|
||||
return nameList;
|
||||
}
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:mood_diary/utils/utils.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
|
||||
class PermissionUtil {
|
||||
//权限申请
|
||||
Future<bool> checkPermission(Permission permission) async {
|
||||
if (Platform.isMacOS) {
|
||||
return true;
|
||||
}
|
||||
//检查当前权限
|
||||
final status = await permission.status;
|
||||
//如果还没有授权或者拒绝过
|
||||
|
||||
@@ -6,8 +6,6 @@ import 'package:mood_diary/utils/data/supabase.dart';
|
||||
import 'package:mood_diary/utils/file_util.dart';
|
||||
import 'package:mood_diary/utils/http_util.dart';
|
||||
import 'package:mood_diary/utils/layout_util.dart';
|
||||
import 'package:mood_diary/utils/localsend/client.dart';
|
||||
import 'package:mood_diary/utils/localsend/server.dart';
|
||||
import 'package:mood_diary/utils/log_util.dart';
|
||||
import 'package:mood_diary/utils/media_util.dart';
|
||||
import 'package:mood_diary/utils/notice_util.dart';
|
||||
@@ -59,8 +57,4 @@ class Utils {
|
||||
late final PrefUtil prefUtil = PrefUtil();
|
||||
|
||||
late final SupabaseUtil supabaseUtil = SupabaseUtil();
|
||||
|
||||
late final LocalLanClient localLanClient = LocalLanClient();
|
||||
|
||||
late final LocalLanServer localLanServer = LocalLanServer();
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ import isar_flutter_libs
|
||||
import local_auth_darwin
|
||||
import media_kit_libs_macos_video
|
||||
import media_kit_video
|
||||
import network_info_plus
|
||||
import objectbox_flutter_libs
|
||||
import package_info_plus
|
||||
import path_provider_foundation
|
||||
@@ -44,6 +45,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
FLALocalAuthPlugin.register(with: registry.registrar(forPlugin: "FLALocalAuthPlugin"))
|
||||
MediaKitLibsMacosVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitLibsMacosVideoPlugin"))
|
||||
MediaKitVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitVideoPlugin"))
|
||||
NetworkInfoPlusPlugin.register(with: registry.registrar(forPlugin: "NetworkInfoPlusPlugin"))
|
||||
ObjectboxFlutterLibsPlugin.register(with: registry.registrar(forPlugin: "ObjectboxFlutterLibsPlugin"))
|
||||
FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin"))
|
||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||
|
||||
@@ -30,6 +30,8 @@ PODS:
|
||||
- FlutterMacOS
|
||||
- media_kit_video (0.0.1):
|
||||
- FlutterMacOS
|
||||
- network_info_plus (0.0.1):
|
||||
- FlutterMacOS
|
||||
- ObjectBox (4.0.1)
|
||||
- objectbox_flutter_libs (0.0.1):
|
||||
- FlutterMacOS
|
||||
@@ -76,6 +78,7 @@ DEPENDENCIES:
|
||||
- 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`)
|
||||
- network_info_plus (from `Flutter/ephemeral/.symlinks/plugins/network_info_plus/macos`)
|
||||
- objectbox_flutter_libs (from `Flutter/ephemeral/.symlinks/plugins/objectbox_flutter_libs/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`)
|
||||
@@ -124,6 +127,8 @@ EXTERNAL SOURCES:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/media_kit_native_event_loop/macos
|
||||
media_kit_video:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/media_kit_video/macos
|
||||
network_info_plus:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/network_info_plus/macos
|
||||
objectbox_flutter_libs:
|
||||
:path: Flutter/ephemeral/.symlinks/plugins/objectbox_flutter_libs/macos
|
||||
package_info_plus:
|
||||
@@ -153,7 +158,7 @@ SPEC CHECKSUMS:
|
||||
app_links: 10e0a0ab602ffaf34d142cd4862f29d34b303b2a
|
||||
audioplayers_darwin: dcad41de4fbd0099cb3749f7ab3b0cb8f70b810c
|
||||
bitsdojo_window_macos: 44e3b8fe3dd463820e0321f6256c5b1c16bb6a00
|
||||
device_info_plus: 74e614483d05c89290d30a4c8feae15d555f7427
|
||||
device_info_plus: 1b14eed9bf95428983aed283a8d51cce3d8c4215
|
||||
dynamic_color: 2eaa27267de1ca20d879fbd6e01259773fb1670f
|
||||
fc_native_video_thumbnail: 927d4dcfd4c7e9f2cc1a20bb52dfee83de3792c2
|
||||
file_selector_macos: cc3858c981fe6889f364731200d6232dac1d812d
|
||||
@@ -165,14 +170,15 @@ SPEC CHECKSUMS:
|
||||
media_kit_libs_macos_video: b3e2bbec2eef97c285f2b1baa7963c67c753fb82
|
||||
media_kit_native_event_loop: 81fd5b45192b72f8b5b69eaf5b540f45777eb8d5
|
||||
media_kit_video: c75b07f14d59706c775778e4dd47dd027de8d1e5
|
||||
network_info_plus: 2cb02d8435635eae13b3b79279681985121cf30c
|
||||
ObjectBox: 0bc4bb75eea85f6af06b369148b334c2056bbc29
|
||||
objectbox_flutter_libs: 769e6f44f7381c8a8e46a2ed5c71c6068bb476f7
|
||||
package_info_plus: f5790acc797bf17c3e959e9d6cf162cc68ff7523
|
||||
package_info_plus: 12f1c5c2cfe8727ca46cbd0b26677728972d9a5b
|
||||
path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46
|
||||
quill_native_bridge_macos: f90985c5269ac7ba84d933605b463d96e5f544fe
|
||||
record_darwin: a0d515a0ef78c440c123ea3ac76184c9927a94d6
|
||||
screen_brightness_macos: 2d6d3af2165592d9a55ffcd95b7550970e41ebda
|
||||
share_plus: fd717ef89a2801d3491e737630112b80c310640e
|
||||
share_plus: 1fa619de8392a4398bfaf176d441853922614e89
|
||||
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
|
||||
sqflite_darwin: a553b1fd6fe66f53bbb0fe5b4f5bab93f08d7a13
|
||||
tflite_flutter: b186ea3c62c076a90652f47c01005fe36c75171e
|
||||
|
||||
82
pubspec.lock
82
pubspec.lock
@@ -78,6 +78,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.6.0"
|
||||
asn1lib:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: asn1lib
|
||||
sha256: "4bae5ae63e6d6dd17c4aac8086f3dec26c0236f6a0f03416c6c19d830c367cf5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.5.8"
|
||||
async:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -426,10 +434,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: device_info_plus
|
||||
sha256: c4af09051b4f0508f6c1dc0a5c085bf014d5c9a4a0678ce1799c2b4d716387a0
|
||||
sha256: f545ffbadee826f26f2e1a0f0cbd667ae9a6011cc0f77c0f8f00a969655e6e95
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "11.1.0"
|
||||
version: "11.1.1"
|
||||
device_info_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -478,6 +486,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.7.0"
|
||||
encrypt:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: encrypt
|
||||
sha256: "62d9aa4670cc2a8798bab89b39fc71b6dfbacf615de6cf5001fb39f7e4a996a2"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.0.3"
|
||||
equatable:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -724,10 +740,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_keyboard_visibility_temp_fork
|
||||
sha256: e342172aaa6173a661e822c85a005f8c5d0a04a1d263e00cb9f9155adab9cb7c
|
||||
sha256: "2d94acecfc170d244157821cc67e784f60972677aac94a6672626a5d6b2dc537"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.1"
|
||||
version: "0.1.3"
|
||||
flutter_keyboard_visibility_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1385,8 +1401,32 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.0"
|
||||
objectbox:
|
||||
network_info_plus:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: network_info_plus
|
||||
sha256: bf9e39e523e9951d741868dc33ac386b0bc24301e9b7c8a7d60dbc34879150a8
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.1.1"
|
||||
network_info_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: network_info_plus_platform_interface
|
||||
sha256: b7f35f4a7baef511159e524499f3c15464a49faa5ec10e92ee0bce265e664906
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.1"
|
||||
nm:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: nm
|
||||
sha256: "2c9aae4127bdc8993206464fcc063611e0e36e72018696cd9631023a31b24254"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.5.0"
|
||||
objectbox:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: objectbox
|
||||
sha256: ea823f4bf1d0a636e7aa50b43daabb64dd0fbd80b85a033016ccc1bc4f76f432
|
||||
@@ -1421,10 +1461,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: package_info_plus
|
||||
sha256: df3eb3e0aed5c1107bb0fdb80a8e82e778114958b1c5ac5644fb1ac9cae8a998
|
||||
sha256: da8d9ac8c4b1df253d1a328b7bf01ae77ef132833479ab40763334db13b91cce
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "8.1.0"
|
||||
version: "8.1.1"
|
||||
package_info_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1569,6 +1609,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.8"
|
||||
pointycastle:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: pointycastle
|
||||
sha256: "4be0097fcf3fd3e8449e53730c631200ebc7b88016acecab2b0da2f0149222fe"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.9.1"
|
||||
polylabel:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1685,10 +1733,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: quill_native_bridge_web
|
||||
sha256: "59d673b946ecb8dbcdd387a4957de12f0263690bdbe159832355855ac7a45de9"
|
||||
sha256: bb3ab017fdb9b60a29cac0bce3acfd48396d13c1bd0499c97af112c84937b4d1
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.0.1-dev.4"
|
||||
version: "0.0.1-dev.5"
|
||||
quill_native_bridge_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1853,10 +1901,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: share_plus
|
||||
sha256: "3af2cda1752e5c24f2fc04b6083b40f013ffe84fb90472f30c6499a9213d5442"
|
||||
sha256: "9c9bafd4060728d7cdb2464c341743adbd79d327cb067ec7afb64583540b47c8"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "10.1.1"
|
||||
version: "10.1.2"
|
||||
share_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -1869,10 +1917,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: shared_preferences
|
||||
sha256: "746e5369a43170c25816cc472ee016d3a66bc13fcf430c0bc41ad7b4b2922051"
|
||||
sha256: "95f9997ca1fb9799d494d0cb2a780fd7be075818d59f00c43832ed112b158a82"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.2"
|
||||
version: "2.3.3"
|
||||
shared_preferences_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -2002,10 +2050,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqflite
|
||||
sha256: "79a297dc3cc137e758c6a4baf83342b039e5a6d2436fcdf3f96a00adaaf2ad62"
|
||||
sha256: "2d7299468485dca85efeeadf5d38986909c5eb0cd71fd3db2c2f000e6c9454bb"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.0"
|
||||
version: "2.4.1"
|
||||
sqflite_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -2026,10 +2074,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: sqflite_darwin
|
||||
sha256: "769733dddf94622d5541c73e4ddc6aa7b252d865285914b6fcd54a63c4b4f027"
|
||||
sha256: "96a698e2bc82bd770a4d6aab00b42396a7c63d9e33513a56945cbccb594c2474"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1-1"
|
||||
version: "2.4.1"
|
||||
sqflite_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
24
pubspec.yaml
24
pubspec.yaml
@@ -36,7 +36,7 @@ dependencies:
|
||||
path_provider: 2.1.5
|
||||
calendar_date_picker2: 1.1.7
|
||||
logger: 2.4.0
|
||||
flutter_drawing_board: ^0.9.5
|
||||
flutter_drawing_board: 0.9.5
|
||||
flutter_displaymode: 0.6.0
|
||||
fl_chart: 0.69.0
|
||||
file_picker: 8.1.3
|
||||
@@ -44,20 +44,20 @@ dependencies:
|
||||
local_auth_android: 1.0.46
|
||||
permission_handler: 11.3.1
|
||||
image_picker: 1.1.2
|
||||
device_info_plus: 11.1.0
|
||||
device_info_plus: 11.1.1
|
||||
flutter_image_compress: 2.3.0
|
||||
photo_view: 0.15.0
|
||||
package_info_plus: 8.1.0
|
||||
package_info_plus: 8.1.1
|
||||
uuid: 4.5.1
|
||||
flutter_quill: 10.8.5
|
||||
share_plus: 10.1.1
|
||||
share_plus: 10.1.2
|
||||
url_launcher: 6.3.1
|
||||
archive: 3.6.1
|
||||
crypto: 3.0.6
|
||||
markdown_widget: 2.3.2+6
|
||||
flutter_colorpicker: 1.1.0
|
||||
geolocator: 13.0.1
|
||||
shared_preferences: 2.3.2
|
||||
shared_preferences: 2.3.3
|
||||
isar: 4.0.0-dev.14
|
||||
isar_flutter_libs: 4.0.0-dev.14
|
||||
fluttertoast: 8.2.8
|
||||
@@ -91,6 +91,9 @@ dependencies:
|
||||
smooth_page_indicator: 1.2.0+3
|
||||
table_calendar: 3.1.2
|
||||
unicons: 3.0.0
|
||||
network_info_plus: 6.1.1
|
||||
encrypt: 5.0.3
|
||||
objectbox: 4.0.3
|
||||
|
||||
|
||||
flutter_localizations:
|
||||
@@ -129,13 +132,4 @@ flutter:
|
||||
|
||||
uses-material-design: true
|
||||
|
||||
msix_config:
|
||||
display_name: 心绪日记
|
||||
app_installer:
|
||||
publish_folder_path: \win
|
||||
publisher_display_name: ZhuJHua
|
||||
# certificate_path: D:\msix.pfx
|
||||
# certificate_password: liuzhuming
|
||||
logo_path: assets/icon/icon.png
|
||||
trim_logo: false
|
||||
identity_name: cn.yooss.moodiary
|
||||
|
||||
|
||||
Reference in New Issue
Block a user