Merge branch 'main' into design

This commit is contained in:
guozhigq
2024-10-21 23:32:58 +08:00
48 changed files with 1705 additions and 1181 deletions

View File

@ -47,13 +47,14 @@
<activity <activity
android:name="com.guozhigq.pilipala.MainActivity" android:name="com.guozhigq.pilipala.MainActivity"
android:exported="true" android:exported="true"
android:launchMode="singleTop" android:launchMode="singleTask"
android:theme="@style/LaunchTheme" android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode" android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true" android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize" android:windowSoftInputMode="adjustResize"
android:supportsPictureInPicture="true" android:supportsPictureInPicture="true"
android:resizeableActivity="true" android:resizeableActivity="true"
android:autoVerify="true"
> >
<!-- Specifies an Android theme to apply to this Activity as soon as <!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user the Android process has started. This theme is visible to the user
@ -63,10 +64,21 @@
android:name="io.flutter.embedding.android.NormalTheme" android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme" android:resource="@style/NormalTheme"
/> />
<meta-data android:name="flutter_deeplinking_enabled" android:value="false" />
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN"/> <action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/> <category android:name="android.intent.category.LAUNCHER"/>
</intent-filter> </intent-filter>
<!-- Deep Link -->
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<action android:name="android.intent.action.SEARCH" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="pili"/>
<data android:scheme="pilipala"/>
</intent-filter>
<!-- App Link -->
<intent-filter> <intent-filter>
<action android:name="android.intent.action.VIEW" /> <action android:name="android.intent.action.VIEW" />
<action android:name="android.intent.action.SEARCH" /> <action android:name="android.intent.action.SEARCH" />

View File

@ -1,4 +1,6 @@
PODS: PODS:
- app_links (0.0.2):
- Flutter
- appscheme (1.0.4): - appscheme (1.0.4):
- Flutter - Flutter
- audio_service (0.0.1): - audio_service (0.0.1):
@ -27,6 +29,8 @@ PODS:
- Flutter - Flutter
- GT3Captcha-iOS - GT3Captcha-iOS
- GT3Captcha-iOS (0.15.8.3) - GT3Captcha-iOS (0.15.8.3)
- image_picker_ios (0.0.1):
- Flutter
- media_kit_libs_ios_video (1.0.4): - media_kit_libs_ios_video (1.0.4):
- Flutter - Flutter
- media_kit_native_event_loop (1.0.0): - media_kit_native_event_loop (1.0.0):
@ -66,6 +70,7 @@ PODS:
- Flutter - Flutter
DEPENDENCIES: DEPENDENCIES:
- app_links (from `.symlinks/plugins/app_links/ios`)
- appscheme (from `.symlinks/plugins/appscheme/ios`) - appscheme (from `.symlinks/plugins/appscheme/ios`)
- audio_service (from `.symlinks/plugins/audio_service/ios`) - audio_service (from `.symlinks/plugins/audio_service/ios`)
- audio_session (from `.symlinks/plugins/audio_session/ios`) - audio_session (from `.symlinks/plugins/audio_session/ios`)
@ -77,6 +82,7 @@ DEPENDENCIES:
- flutter_volume_controller (from `.symlinks/plugins/flutter_volume_controller/ios`) - flutter_volume_controller (from `.symlinks/plugins/flutter_volume_controller/ios`)
- fluttertoast (from `.symlinks/plugins/fluttertoast/ios`) - fluttertoast (from `.symlinks/plugins/fluttertoast/ios`)
- gt3_flutter_plugin (from `.symlinks/plugins/gt3_flutter_plugin/ios`) - gt3_flutter_plugin (from `.symlinks/plugins/gt3_flutter_plugin/ios`)
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
- media_kit_libs_ios_video (from `.symlinks/plugins/media_kit_libs_ios_video/ios`) - 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_native_event_loop (from `.symlinks/plugins/media_kit_native_event_loop/ios`)
- media_kit_video (from `.symlinks/plugins/media_kit_video/ios`) - media_kit_video (from `.symlinks/plugins/media_kit_video/ios`)
@ -102,6 +108,8 @@ SPEC REPOS:
- Toast - Toast
EXTERNAL SOURCES: EXTERNAL SOURCES:
app_links:
:path: ".symlinks/plugins/app_links/ios"
appscheme: appscheme:
:path: ".symlinks/plugins/appscheme/ios" :path: ".symlinks/plugins/appscheme/ios"
audio_service: audio_service:
@ -124,6 +132,8 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/fluttertoast/ios" :path: ".symlinks/plugins/fluttertoast/ios"
gt3_flutter_plugin: gt3_flutter_plugin:
:path: ".symlinks/plugins/gt3_flutter_plugin/ios" :path: ".symlinks/plugins/gt3_flutter_plugin/ios"
image_picker_ios:
:path: ".symlinks/plugins/image_picker_ios/ios"
media_kit_libs_ios_video: media_kit_libs_ios_video:
:path: ".symlinks/plugins/media_kit_libs_ios_video/ios" :path: ".symlinks/plugins/media_kit_libs_ios_video/ios"
media_kit_native_event_loop: media_kit_native_event_loop:
@ -160,6 +170,7 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/webview_flutter_wkwebview/ios" :path: ".symlinks/plugins/webview_flutter_wkwebview/ios"
SPEC CHECKSUMS: SPEC CHECKSUMS:
app_links: e7a6750a915a9e161c58d91bc610e8cd1d4d0ad0
appscheme: b1c3f8862331cb20430cf9e0e4af85dbc1572ad8 appscheme: b1c3f8862331cb20430cf9e0e4af85dbc1572ad8
audio_service: f509d65da41b9521a61f1c404dd58651f265a567 audio_service: f509d65da41b9521a61f1c404dd58651f265a567
audio_session: 4f3e461722055d21515cf3261b64c973c062f345 audio_session: 4f3e461722055d21515cf3261b64c973c062f345
@ -173,6 +184,7 @@ SPEC CHECKSUMS:
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
gt3_flutter_plugin: 5bd2c08d3c19cbb6ee3b08f4358439e54c8ab2ee gt3_flutter_plugin: 5bd2c08d3c19cbb6ee3b08f4358439e54c8ab2ee
GT3Captcha-iOS: 5e3b1077834d8a9d6f4d64a447a30af3e14affe6 GT3Captcha-iOS: 5e3b1077834d8a9d6f4d64a447a30af3e14affe6
image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1
media_kit_libs_ios_video: a5fe24bc7875ccd6378a0978c13185e1344651c1 media_kit_libs_ios_video: a5fe24bc7875ccd6378a0978c13185e1344651c1
media_kit_native_event_loop: e6b2ab20cf0746eb1c33be961fcf79667304fa2a media_kit_native_event_loop: e6b2ab20cf0746eb1c33be961fcf79667304fa2a
media_kit_video: 5da63f157170e5bf303bf85453b7ef6971218a2e media_kit_video: 5da63f157170e5bf303bf85453b7ef6971218a2e

View File

@ -65,44 +65,29 @@
<array> <array>
<dict> <dict>
<key>CFBundleURLName</key> <key>CFBundleURLName</key>
<string></string> <string>bilibili</string>
<key>CFBundleURLSchemes</key> <key>CFBundleURLSchemes</key>
<array> <array>
<string>http</string> <string>http</string>
<string>https</string> <string>https</string>
</array>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string></string>
<key>CFBundleURLSchemes</key>
<array>
<string>m.bilibili.com</string>
<string>bilibili.com</string>
<string>www.bilibili.com</string>
<string>bangumi.bilibili.com</string>
<string>bilibili.cn</string>
<string>www.bilibili.cn</string>
<string>bangumi.bilibili.cn</string>
<string>bilibili.tv</string>
<string>www.bilibili.tv</string>
<string>bangumi.bilibili.tv</string>
<string>miniapp.bilibili.com</string>
<string>live.bilibili.com</string>
</array>
</dict>
</array>
</dict>
<!-- 当其他应用程序或系统通过 bilibili -->
<dict>
<key>CFBundleURLName</key>
<string>bilibili</string>
<key>CFBundleURLSchemes</key>
<array>
<string>bilibili</string> <string>bilibili</string>
<string>m.bilibili.com</string>
<string>bilibili.com</string>
<string>www.bilibili.com</string>
<string>bangumi.bilibili.com</string>
<string>bilibili.cn</string>
<string>www.bilibili.cn</string>
<string>bangumi.bilibili.cn</string>
<string>bilibili.tv</string>
<string>www.bilibili.tv</string>
<string>bangumi.bilibili.tv</string>
<string>miniapp.bilibili.com</string>
<string>live.bilibili.com</string>
<string>pili</string>
<string>pilipala</string>
</array> </array>
<key>FlutterDeepLinkingEnabled</key>
<false/>
</dict> </dict>
</array> </array>
<key>UIBackgroundModes</key> <key>UIBackgroundModes</key>

View File

@ -282,9 +282,10 @@ class VideoStat extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Row( return Row(
children: [ children: [
StatView(view: videoItem.stat.view), if (videoItem.stat.view != null) StatView(view: videoItem.stat.view),
const SizedBox(width: 8), const SizedBox(width: 8),
StatDanMu(danmu: videoItem.stat.danmu), if (videoItem.stat.danmu != null)
StatDanMu(danmu: videoItem.stat.danmu),
if (videoItem is RecVideoItemModel) ...<Widget>[ if (videoItem is RecVideoItemModel) ...<Widget>[
crossAxisCount > 1 ? const Spacer() : const SizedBox(width: 8), crossAxisCount > 1 ? const Spacer() : const SizedBox(width: 8),
RichText( RichText(

View File

@ -593,6 +593,15 @@ class Api {
static const String liveRoomEntry = static const String liveRoomEntry =
'${HttpString.liveBaseUrl}/xlive/web-room/v1/index/roomEntryAction'; '${HttpString.liveBaseUrl}/xlive/web-room/v1/index/roomEntryAction';
/// 用户信息
static const String accountInfo = '/x/member/web/account';
/// 更新用户信息
static const String updateAccountInfo = '/x/member/web/update';
/// 删除评论 /// 删除评论
static const String replyDel = '/x/v2/reply/del'; static const String replyDel = '/x/v2/reply/del';
/// 图片上传
static const String uploadImage = '/x/dynamic/feed/draw/upload_bfs';
} }

View File

@ -1,5 +1,8 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:image_picker/image_picker.dart';
import '../models/video/reply/data.dart'; import '../models/video/reply/data.dart';
import '../models/video/reply/emote.dart'; import '../models/video/reply/emote.dart';
import 'api.dart'; import 'api.dart';
@ -131,4 +134,44 @@ class ReplyHttp {
return {'status': false, 'msg': res.data['message']}; return {'status': false, 'msg': res.data['message']};
} }
} }
// 图片上传
static Future uploadImage({required XFile xFile, String type = 'im'}) async {
var formData = FormData.fromMap({
'file_up': await xFileToMultipartFile(xFile),
'biz': type,
'csrf': await Request.getCsrf(),
'build': 0,
'mobi_app': 'web',
});
var res = await Request().post(
Api.uploadImage,
data: formData,
);
if (res.data['code'] == 0) {
var data = res.data['data'];
data['img_src'] = data['image_url'];
data['img_width'] = data['image_width'];
data['img_height'] = data['image_height'];
data.remove('image_url');
data.remove('image_width');
data.remove('image_height');
return {
'status': true,
'data': data,
};
} else {
return {
'status': false,
'date': [],
'msg': res.data['message'],
};
}
}
static Future<MultipartFile> xFileToMultipartFile(XFile xFile) async {
var file = File(xFile.path);
var bytes = await file.readAsBytes();
return MultipartFile.fromBytes(bytes, filename: xFile.name);
}
} }

View File

@ -1,4 +1,6 @@
import 'dart:convert'; import 'dart:convert';
import 'package:dio/dio.dart';
import 'package:html/parser.dart'; import 'package:html/parser.dart';
import 'package:pilipala/models/video/later.dart'; import 'package:pilipala/models/video/later.dart';
import '../models/model_hot_video_item.dart'; import '../models/model_hot_video_item.dart';
@ -465,4 +467,53 @@ class UserHttp {
.toList() .toList()
}; };
} }
static Future getAccountInfo() async {
var res = await Request().get(
Api.accountInfo,
data: {'web_location': 333.33},
);
if (res.data['code'] == 0) {
return {
'status': true,
'data': res.data['data'],
};
} else {
return {
'status': false,
'data': {},
'mag': res.data['message'],
};
}
}
static Future updateAccountInfo({
required String uname,
required String sign,
required String sex,
required String birthday,
}) async {
var res = await Request().post(
Api.updateAccountInfo,
data: {
'uname': uname,
'usersign': sign,
'sex': sex,
'birthday': birthday,
'csrf': await Request.getCsrf(),
},
options: Options(contentType: Headers.formUrlEncodedContentType),
);
if (res.data['code'] == 0) {
return {
'status': true,
'msg': '更新成功',
};
} else {
return {
'status': false,
'msg': res.data['message'],
};
}
}
} }

View File

@ -1,4 +1,6 @@
import 'dart:convert';
import 'dart:developer'; import 'dart:developer';
import 'package:dio/dio.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import '../common/constants.dart'; import '../common/constants.dart';
import '../models/common/reply_type.dart'; import '../models/common/reply_type.dart';
@ -346,20 +348,34 @@ class VideoHttp {
required String message, required String message,
int? root, int? root,
int? parent, int? parent,
List<Map<dynamic, dynamic>>? pictures,
}) async { }) async {
if (message == '') { if (message == '') {
return {'status': false, 'data': [], 'msg': '请输入评论内容'}; return {'status': false, 'data': [], 'msg': '请输入评论内容'};
} }
var params = <String, dynamic>{
'plat': 1,
'oid': oid,
'type': type.index,
// 'root': root == null || root == 0 ? '' : root,
// 'parent': parent == null || parent == 0 ? '' : parent,
'message': message,
'at_name_to_mid': {},
if (pictures != null) 'pictures': jsonEncode(pictures),
'gaia_source': 'main_web',
'csrf': await Request.getCsrf(),
};
Map sign = await WbiSign().makSign(params);
params.remove('wts');
params.remove('w_rid');
FormData formData = FormData.fromMap({...params});
var res = await Request().post( var res = await Request().post(
Api.replyAdd, Api.replyAdd,
data: { queryParameters: {
'type': type.index, 'w_rid': sign['w_rid'],
'oid': oid, 'wts': sign['wts'],
'root': root == null || root == 0 ? '' : root,
'parent': parent == null || parent == 0 ? '' : parent,
'message': message,
'csrf': await Request.getCsrf(),
}, },
data: formData,
); );
log(res.toString()); log(res.toString());
if (res.data['code'] == 0) { if (res.data['code'] == 0) {

View File

@ -60,6 +60,7 @@ void main() async {
systemNavigationBarColor: Colors.transparent, systemNavigationBarColor: Colors.transparent,
systemNavigationBarDividerColor: Colors.transparent, systemNavigationBarDividerColor: Colors.transparent,
statusBarColor: Colors.transparent, statusBarColor: Colors.transparent,
systemNavigationBarContrastEnforced: false,
)); ));
} }

View File

@ -1,8 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:pilipala/pages/mine/index.dart';
import '../../pages/dynamics/index.dart'; import '../../pages/dynamics/index.dart';
import '../../pages/home/index.dart'; import '../../pages/home/index.dart';
import '../../pages/media/index.dart';
import '../../pages/rank/index.dart'; import '../../pages/rank/index.dart';
List defaultNavigationBars = [ List defaultNavigationBars = [
@ -51,15 +50,15 @@ List defaultNavigationBars = [
{ {
'id': 3, 'id': 3,
'icon': const Icon( 'icon': const Icon(
Icons.video_collection_outlined, Icons.person_outline,
size: 20, size: 20,
), ),
'selectIcon': const Icon( 'selectIcon': const Icon(
Icons.video_collection, Icons.person,
size: 21, size: 21,
), ),
'label': "媒体库", 'label': "我的",
'count': 0, 'count': 0,
'page': const MediaPage(), 'page': const MinePage(),
} },
]; ];

View File

@ -8,7 +8,7 @@ import 'package:pilipala/utils/storage.dart';
import '../../http/index.dart'; import '../../http/index.dart';
class HomeController extends GetxController with GetTickerProviderStateMixin { class HomeController extends GetxController with GetTickerProviderStateMixin {
bool flag = false; bool flag = true;
late RxList tabs = [].obs; late RxList tabs = [].obs;
RxInt initialIndex = 1.obs; RxInt initialIndex = 1.obs;
late TabController tabController; late TabController tabController;

View File

@ -5,7 +5,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart';
import 'package:pilipala/pages/mine/index.dart'; import 'package:pilipala/pages/main/index.dart';
import 'package:pilipala/utils/feed_back.dart'; import 'package:pilipala/utils/feed_back.dart';
import './controller.dart'; import './controller.dart';
@ -19,6 +19,8 @@ class HomePage extends StatefulWidget {
class _HomePageState extends State<HomePage> class _HomePageState extends State<HomePage>
with AutomaticKeepAliveClientMixin, TickerProviderStateMixin { with AutomaticKeepAliveClientMixin, TickerProviderStateMixin {
final HomeController _homeController = Get.put(HomeController()); final HomeController _homeController = Get.put(HomeController());
final MainController mainController = Get.put(MainController());
List videoList = []; List videoList = [];
late Stream<bool> stream; late Stream<bool> stream;
@ -33,15 +35,14 @@ class _HomePageState extends State<HomePage>
showUserBottomSheet() { showUserBottomSheet() {
feedBack(); feedBack();
showModalBottomSheet( final MainController mainController = Get.put(MainController());
context: context, int mineItemIndex = mainController.navigationBars
builder: (_) => const SizedBox( .indexWhere((item) => item['label'] == "我的");
height: 450, if (mineItemIndex != -1) {
child: MinePage(), mainController.pageController.jumpToPage(mineItemIndex);
), } else {
clipBehavior: Clip.hardEdge, Get.toNamed('/mine');
isScrollControlled: true, }
);
} }
@override @override

View File

@ -3,7 +3,6 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart';
import 'package:pilipala/pages/mine/view.dart';
import 'package:pilipala/utils/storage.dart'; import 'package:pilipala/utils/storage.dart';
Box userInfoCache = GStrorage.userInfo; Box userInfoCache = GStrorage.userInfo;
@ -54,19 +53,9 @@ class HomeAppBar extends StatelessWidget {
// icon: const Icon(CupertinoIcons.bell, size: 22), // icon: const Icon(CupertinoIcons.bell, size: 22),
// ), // ),
const SizedBox(width: 6), const SizedBox(width: 6),
/// TODO
if (userInfo != null) ...[ if (userInfo != null) ...[
GestureDetector( GestureDetector(
onTap: () => showModalBottomSheet( onTap: () => Get.toNamed('/mine'),
context: context,
builder: (_) => const SizedBox(
height: 450,
child: MinePage(),
),
clipBehavior: Clip.hardEdge,
isScrollControlled: true,
),
child: NetworkImgLayer( child: NetworkImgLayer(
type: 'avatar', type: 'avatar',
width: 32, width: 32,
@ -77,15 +66,7 @@ class HomeAppBar extends StatelessWidget {
const SizedBox(width: 10), const SizedBox(width: 10),
] else ...[ ] else ...[
IconButton( IconButton(
onPressed: () => showModalBottomSheet( onPressed: () => Get.toNamed('/mine'),
context: context,
builder: (_) => const SizedBox(
height: 450,
child: MinePage(),
),
clipBehavior: Clip.hardEdge,
isScrollControlled: true,
),
icon: const Icon(CupertinoIcons.person, size: 22), icon: const Icon(CupertinoIcons.person, size: 22),
), ),
], ],

View File

@ -5,6 +5,7 @@ import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart';
import 'package:pilipala/http/common.dart'; import 'package:pilipala/http/common.dart';
import 'package:pilipala/utils/storage.dart'; import 'package:pilipala/utils/storage.dart';
import 'package:pilipala/utils/utils.dart'; import 'package:pilipala/utils/utils.dart';
@ -25,6 +26,7 @@ class MainController extends GetxController {
late PageController pageController; late PageController pageController;
int selectedIndex = 0; int selectedIndex = 0;
Box userInfoCache = GStrorage.userInfo; Box userInfoCache = GStrorage.userInfo;
dynamic userInfo;
RxBool userLogin = false.obs; RxBool userLogin = false.obs;
late Rx<DynamicBadgeMode> dynamicBadgeType = DynamicBadgeMode.number.obs; late Rx<DynamicBadgeMode> dynamicBadgeType = DynamicBadgeMode.number.obs;
late bool enableGradientBg; late bool enableGradientBg;
@ -38,7 +40,7 @@ class MainController extends GetxController {
} }
hideTabBar = setting.get(SettingBoxKey.hideTabBar, defaultValue: false); hideTabBar = setting.get(SettingBoxKey.hideTabBar, defaultValue: false);
var userInfo = userInfoCache.get('userInfoCache'); userInfo = userInfoCache.get('userInfoCache');
userLogin.value = userInfo != null; userLogin.value = userInfo != null;
dynamicBadgeType.value = DynamicBadgeMode.values[setting.get( dynamicBadgeType.value = DynamicBadgeMode.values[setting.get(
SettingBoxKey.dynamicBadgeMode, SettingBoxKey.dynamicBadgeMode,
@ -73,12 +75,20 @@ class MainController extends GetxController {
} }
int dynamicItemIndex = int dynamicItemIndex =
navigationBars.indexWhere((item) => item['label'] == "动态"); navigationBars.indexWhere((item) => item['label'] == "动态");
int mineItemIndex =
navigationBars.indexWhere((item) => item['label'] == "我的");
var res = await CommonHttp.unReadDynamic(); var res = await CommonHttp.unReadDynamic();
var data = res['data']; var data = res['data'];
if (dynamicItemIndex != -1) { if (dynamicItemIndex != -1) {
navigationBars[dynamicItemIndex]['count'] = navigationBars[dynamicItemIndex]['count'] =
data == null ? 0 : data.length; // 修改 count 属性为新的值 data == null ? 0 : data.length; // 修改 count 属性为新的值
} }
if (mineItemIndex != -1 && userInfo != null) {
Widget avatar = NetworkImgLayer(
width: 28, height: 28, src: userInfo.face, type: 'avatar');
navigationBars[mineItemIndex]['icon'] = avatar;
navigationBars[mineItemIndex]['selectIcon'] = avatar;
}
navigationBars.refresh(); navigationBars.refresh();
} }

View File

@ -1,12 +1,13 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:pilipala/models/common/dynamic_badge_mode.dart'; import 'package:pilipala/models/common/dynamic_badge_mode.dart';
import 'package:pilipala/pages/dynamics/index.dart'; import 'package:pilipala/pages/dynamics/index.dart';
import 'package:pilipala/pages/home/index.dart'; import 'package:pilipala/pages/home/index.dart';
import 'package:pilipala/pages/media/index.dart'; import 'package:pilipala/pages/mine/index.dart';
import 'package:pilipala/pages/rank/index.dart'; import 'package:pilipala/pages/rank/index.dart';
import 'package:pilipala/utils/event_bus.dart'; import 'package:pilipala/utils/event_bus.dart';
import 'package:pilipala/utils/feed_back.dart'; import 'package:pilipala/utils/feed_back.dart';
@ -26,7 +27,7 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
late HomeController _homeController; late HomeController _homeController;
RankController? _rankController; RankController? _rankController;
late DynamicsController _dynamicController; late DynamicsController _dynamicController;
late MediaController _mediaController; late MineController _mineController;
int? _lastSelectTime; //上次点击时间 int? _lastSelectTime; //上次点击时间
Box setting = GStrorage.setting; Box setting = GStrorage.setting;
@ -92,24 +93,22 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
_dynamicController.flag = false; _dynamicController.flag = false;
} }
if (currentPage is MediaPage) { if (currentPage is MinePage) {
_mediaController.queryFavFolder(); _mineController.queryFavFolder();
_mineController.queryUserInfo();
} }
} }
void controllerInit() { void controllerInit() {
_homeController = Get.put(HomeController()); _homeController = Get.put(HomeController());
_dynamicController = Get.put(DynamicsController()); _dynamicController = Get.put(DynamicsController());
_mediaController = Get.put(MediaController()); _mineController = Get.put(MineController());
if (_mainController.pagesIds.contains(1)) { if (_mainController.pagesIds.contains(1)) {
_rankController = Get.put(RankController()); _rankController = Get.put(RankController());
} }
if (_mainController.pagesIds.contains(2)) { if (_mainController.pagesIds.contains(2)) {
_dynamicController = Get.put(DynamicsController()); _dynamicController = Get.put(DynamicsController());
} }
if (_mainController.pagesIds.contains(3)) {
_mediaController = Get.put(MediaController());
}
} }
@override @override
@ -129,6 +128,14 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
GlobalDataCache().sheetHeight = sheetHeight; GlobalDataCache().sheetHeight = sheetHeight;
localCache.put('sheetHeight', sheetHeight); localCache.put('sheetHeight', sheetHeight);
localCache.put('statusBarHeight', statusBarHeight); localCache.put('statusBarHeight', statusBarHeight);
SystemChrome.setSystemUIOverlayStyle(
SystemUiOverlayStyle(
systemNavigationBarColor: Colors.transparent,
systemNavigationBarIconBrightness:
Get.isDarkMode ? Brightness.light : Brightness.dark,
),
);
return PopScope( return PopScope(
canPop: false, canPop: false,
onPopInvoked: (bool didPop) async { onPopInvoked: (bool didPop) async {
@ -198,20 +205,21 @@ class _MainAppState extends State<MainApp> with SingleTickerProviderStateMixin {
destinations: <Widget>[ destinations: <Widget>[
..._mainController.navigationBars.map((e) { ..._mainController.navigationBars.map((e) {
return NavigationDestination( return NavigationDestination(
icon: Badge( icon: _mainController
label: _mainController .dynamicBadgeType.value ==
.dynamicBadgeType.value == DynamicBadgeMode.number
DynamicBadgeMode.number ? Badge(
? Text(e['count'].toString()) label: Text(e['count'].toString()),
: null, padding: const EdgeInsets.fromLTRB(
padding: 6, 0, 6, 0),
const EdgeInsets.fromLTRB(6, 0, 6, 0), isLabelVisible: _mainController
isLabelVisible: _mainController .dynamicBadgeType
.dynamicBadgeType.value != .value !=
DynamicBadgeMode.hidden && DynamicBadgeMode.hidden &&
e['count'] > 0, e['count'] > 0,
child: e['icon'], child: e['icon'],
), )
: e['icon'],
selectedIcon: e['selectIcon'], selectedIcon: e['selectIcon'],
label: e['label'], label: e['label'],
); );

View File

@ -1,65 +0,0 @@
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:hive/hive.dart';
import 'package:pilipala/http/user.dart';
import 'package:pilipala/models/user/fav_folder.dart';
import 'package:pilipala/utils/storage.dart';
class MediaController extends GetxController {
Rx<FavFolderData> favFolderData = FavFolderData().obs;
Box userInfoCache = GStrorage.userInfo;
RxBool userLogin = false.obs;
List list = [
{
'icon': Icons.file_download_outlined,
'title': '离线缓存',
'onTap': () {
SmartDialog.showToast('功能开发中');
},
},
{
'icon': Icons.history,
'title': '观看记录',
'onTap': () => Get.toNamed('/history'),
},
{
'icon': Icons.star_border,
'title': '我的收藏',
'onTap': () => Get.toNamed('/fav'),
},
{
'icon': Icons.subscriptions_outlined,
'title': '我的订阅',
'onTap': () => Get.toNamed('/subscription'),
},
{
'icon': Icons.watch_later_outlined,
'title': '稍后再看',
'onTap': () => Get.toNamed('/later'),
},
];
var userInfo;
int? mid;
final ScrollController scrollController = ScrollController();
@override
void onInit() {
super.onInit();
userInfo = userInfoCache.get('userInfoCache');
userLogin.value = userInfo != null;
}
Future<dynamic> queryFavFolder() async {
if (!userLogin.value) {
return {'status': false, 'data': [], 'msg': '未登录'};
}
var res = await await UserHttp.userfavFolder(
pn: 1,
ps: 5,
mid: mid ?? GStrorage.userInfo.get('userInfoCache').mid,
);
favFolderData.value = res['data'];
return res;
}
}

View File

@ -1,296 +0,0 @@
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart';
import 'package:pilipala/models/user/fav_folder.dart';
import 'package:pilipala/pages/media/index.dart';
import 'package:pilipala/utils/utils.dart';
class MediaPage extends StatefulWidget {
const MediaPage({super.key});
@override
State<MediaPage> createState() => _MediaPageState();
}
class _MediaPageState extends State<MediaPage>
with AutomaticKeepAliveClientMixin {
late MediaController mediaController;
late Future _futureBuilderFuture;
@override
bool get wantKeepAlive => true;
@override
void initState() {
super.initState();
mediaController = Get.put(MediaController());
_futureBuilderFuture = mediaController.queryFavFolder();
mediaController.userLogin.listen((status) {
setState(() {
_futureBuilderFuture = mediaController.queryFavFolder();
});
});
}
@override
void dispose() {
mediaController.scrollController.removeListener(() {});
super.dispose();
}
@override
Widget build(BuildContext context) {
super.build(context);
Color primary = Theme.of(context).colorScheme.primary;
return Scaffold(
appBar: AppBar(toolbarHeight: 30),
body: SingleChildScrollView(
controller: mediaController.scrollController,
child: Column(
children: [
ListTile(
leading: null,
title: Padding(
padding: const EdgeInsets.only(left: 20),
child: Text(
'媒体库',
style: TextStyle(
fontSize: Theme.of(context).textTheme.titleLarge!.fontSize,
fontWeight: FontWeight.bold,
),
),
),
),
for (var i in mediaController.list) ...[
ListTile(
onTap: () => i['onTap'](),
dense: true,
leading: Padding(
padding: const EdgeInsets.only(left: 15),
child: Icon(
i['icon'],
color: primary,
),
),
contentPadding:
const EdgeInsets.only(left: 15, top: 2, bottom: 2),
minLeadingWidth: 0,
title: Text(
i['title'],
style: const TextStyle(fontSize: 15),
),
),
],
Obx(() => mediaController.userLogin.value
? favFolder(mediaController, context)
: const SizedBox()),
SizedBox(
height: MediaQuery.of(context).padding.bottom +
kBottomNavigationBarHeight,
)
],
),
),
);
}
Widget favFolder(mediaController, context) {
return Column(
children: [
Divider(
height: 35,
color: Theme.of(context).dividerColor.withOpacity(0.1),
),
ListTile(
onTap: () => Get.toNamed('/fav'),
leading: null,
dense: true,
title: Padding(
padding: const EdgeInsets.only(left: 10),
child: Obx(
() => Text.rich(
TextSpan(
children: [
TextSpan(
text: '收藏夹 ',
style: TextStyle(
fontSize:
Theme.of(context).textTheme.titleMedium!.fontSize,
fontWeight: FontWeight.bold),
),
if (mediaController.favFolderData.value.count != null)
TextSpan(
text: mediaController.favFolderData.value.count
.toString(),
style: TextStyle(
fontSize:
Theme.of(context).textTheme.titleSmall!.fontSize,
color: Theme.of(context).colorScheme.primary,
),
),
],
),
),
),
),
trailing: IconButton(
onPressed: () {
setState(() {
_futureBuilderFuture = mediaController.queryFavFolder();
});
},
icon: const Icon(
Icons.refresh,
size: 20,
),
),
),
// const SizedBox(height: 10),
SizedBox(
width: double.infinity,
height: MediaQuery.textScalerOf(context).scale(200),
child: FutureBuilder(
future: _futureBuilderFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.data == null) {
return const SizedBox();
}
Map data = snapshot.data as Map;
if (data['status']) {
List favFolderList =
mediaController.favFolderData.value.list!;
int favFolderCount =
mediaController.favFolderData.value.count!;
bool flag = favFolderCount > favFolderList.length;
return Obx(() => ListView.builder(
itemCount:
mediaController.favFolderData.value.list!.length +
(flag ? 1 : 0),
itemBuilder: (context, index) {
if (flag && index == favFolderList.length) {
return Padding(
padding: const EdgeInsets.only(
right: 14, bottom: 35),
child: Center(
child: IconButton(
style: ButtonStyle(
padding: MaterialStateProperty.all(
EdgeInsets.zero),
backgroundColor:
MaterialStateProperty.resolveWith(
(states) {
return Theme.of(context)
.colorScheme
.primaryContainer
.withOpacity(0.5);
}),
),
onPressed: () => Get.toNamed('/fav'),
icon: Icon(
Icons.arrow_forward_ios,
size: 18,
color: Theme.of(context)
.colorScheme
.primary,
),
),
));
} else {
return FavFolderItem(
item: mediaController
.favFolderData.value.list![index],
index: index);
}
},
scrollDirection: Axis.horizontal,
));
} else {
return SizedBox(
height: 160,
child: Center(child: Text(data['msg'])),
);
}
} else {
// 骨架屏
return const SizedBox();
}
}),
),
],
);
}
}
class FavFolderItem extends StatelessWidget {
const FavFolderItem({super.key, this.item, this.index});
final FavFolderItemData? item;
final int? index;
@override
Widget build(BuildContext context) {
String heroTag = Utils.makeHeroTag(item!.fid);
return Container(
margin: EdgeInsets.only(left: index == 0 ? 20 : 0, right: 14),
child: GestureDetector(
onTap: () => Get.toNamed('/favDetail', arguments: item, parameters: {
'mediaId': item!.id.toString(),
'heroTag': heroTag,
'isOwner': '1',
}),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const SizedBox(height: 12),
Container(
width: 180,
height: 110,
margin: const EdgeInsets.only(bottom: 8),
clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12),
color: Theme.of(context).colorScheme.onInverseSurface,
boxShadow: [
BoxShadow(
color: Theme.of(context).colorScheme.onInverseSurface,
offset: const Offset(4, -12), // 阴影与容器的距离
blurRadius: 0.0, // 高斯的标准偏差与盒子的形状卷积。
spreadRadius: 0.0, // 在应用模糊之前,框应该膨胀的量。
),
],
),
child: LayoutBuilder(
builder: (context, BoxConstraints box) {
return Hero(
tag: heroTag,
child: NetworkImgLayer(
src: item!.cover,
width: box.maxWidth,
height: box.maxHeight,
),
);
},
),
),
Text(
' ${item!.title}',
overflow: TextOverflow.fade,
maxLines: 1,
),
Text(
'${item!.mediaCount}条视频',
style: Theme.of(context)
.textTheme
.labelSmall!
.copyWith(color: Theme.of(context).colorScheme.outline),
)
],
),
),
);
}
}

View File

@ -32,7 +32,7 @@ class MemberController extends GetxController {
@override @override
void onInit() { void onInit() {
super.onInit(); super.onInit();
mid = int.parse(Get.parameters['mid']!); mid = int.tryParse(Get.parameters['mid']!) ?? -2;
userInfo = userInfoCache.get('userInfoCache'); userInfo = userInfoCache.get('userInfoCache');
ownerMid = userInfo != null ? userInfo.mid : -1; ownerMid = userInfo != null ? userInfo.mid : -1;
isOwner.value = mid == ownerMid; isOwner.value = mid == ownerMid;
@ -43,6 +43,11 @@ class MemberController extends GetxController {
// 获取用户信息 // 获取用户信息
Future<Map<String, dynamic>> getInfo() async { Future<Map<String, dynamic>> getInfo() async {
if (mid == -2) {
SmartDialog.showToast('用户ID获取异常');
return {'status': false, 'msg': '用户ID获取异常'};
}
await getMemberStat(); await getMemberStat();
await getMemberView(); await getMemberView();
var res = await MemberHttp.memberInfo(mid: mid); var res = await MemberHttp.memberInfo(mid: mid);

View File

@ -1,5 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:pilipala/common/constants.dart'; import 'package:pilipala/common/constants.dart';
@ -36,7 +37,7 @@ class _MemberPageState extends State<MemberPage>
@override @override
void initState() { void initState() {
super.initState(); super.initState();
mid = int.parse(Get.parameters['mid']!); mid = int.tryParse(Get.parameters['mid']!) ?? -1;
heroTag = Get.arguments['heroTag'] ?? Utils.makeHeroTag(mid); heroTag = Get.arguments['heroTag'] ?? Utils.makeHeroTag(mid);
_memberController = Get.put(MemberController(), tag: heroTag); _memberController = Get.put(MemberController(), tag: heroTag);
_futureBuilderFuture = _memberController.getInfo(); _futureBuilderFuture = _memberController.getInfo();
@ -100,8 +101,14 @@ class _MemberPageState extends State<MemberPage>
), ),
actions: [ actions: [
IconButton( IconButton(
onPressed: () => Get.toNamed( onPressed: () {
'/memberSearch?mid=$mid&uname=${_memberController.memberInfo.value.name!}'), if (mid == -1) {
SmartDialog.showToast('用户ID获取异常');
return;
}
Get.toNamed(
'/memberSearch?mid=$mid&uname=${_memberController.memberInfo.value.name ?? ''}');
},
icon: const Icon(Icons.search_outlined), icon: const Icon(Icons.search_outlined),
), ),
PopupMenuButton( PopupMenuButton(

View File

@ -1,9 +1,9 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart';
import 'package:pilipala/models/live/item.dart'; import 'package:pilipala/models/live/item.dart';
import 'package:pilipala/models/member/info.dart'; import 'package:pilipala/models/member/info.dart';
import 'package:pilipala/plugin/pl_gallery/index.dart';
import 'package:pilipala/utils/utils.dart'; import 'package:pilipala/utils/utils.dart';
class ProfilePanel extends StatelessWidget { class ProfilePanel extends StatelessWidget {
@ -71,11 +71,27 @@ class ProfilePanel extends StatelessWidget {
tag: ctr.heroTag!, tag: ctr.heroTag!,
child: Stack( child: Stack(
children: [ children: [
NetworkImgLayer( InkWell(
width: 90, onTap: () {
height: 90, Navigator.of(context).push(
type: 'avatar', HeroDialogRoute<void>(
src: !loadingStatus ? memberInfo.face : ctr.face.value, builder: (BuildContext context) =>
InteractiveviewerGallery(
sources: [
!loadingStatus ? memberInfo.face : ctr.face.value
],
initIndex: 0,
onPageChanged: (int pageIndex) {},
),
),
);
},
child: NetworkImgLayer(
width: 90,
height: 90,
type: 'avatar',
src: !loadingStatus ? memberInfo.face : ctr.face.value,
),
), ),
if (!loadingStatus && if (!loadingStatus &&
memberInfo.liveRoom != null && memberInfo.liveRoom != null &&
@ -237,7 +253,7 @@ class ProfilePanel extends StatelessWidget {
Widget buildEditProfileButton(BuildContext context) { Widget buildEditProfileButton(BuildContext context) {
return TextButton( return TextButton(
onPressed: () { onPressed: () {
SmartDialog.showToast('功能开发中 💪'); Get.toNamed('/mineEdit');
}, },
style: TextButton.styleFrom( style: TextButton.styleFrom(
padding: const EdgeInsets.symmetric(horizontal: 80), padding: const EdgeInsets.symmetric(horizontal: 80),

View File

@ -4,22 +4,45 @@ import 'package:get/get.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:pilipala/http/user.dart'; import 'package:pilipala/http/user.dart';
import 'package:pilipala/models/common/theme_type.dart'; import 'package:pilipala/models/common/theme_type.dart';
import 'package:pilipala/models/user/fav_folder.dart';
import 'package:pilipala/models/user/info.dart'; import 'package:pilipala/models/user/info.dart';
import 'package:pilipala/models/user/stat.dart'; import 'package:pilipala/models/user/stat.dart';
import 'package:pilipala/utils/storage.dart'; import 'package:pilipala/utils/storage.dart';
class MineController extends GetxController { class MineController extends GetxController {
// 用户信息 头像、昵称、lv RxBool userLogin = false.obs;
Rx<UserInfoData> userInfo = UserInfoData().obs;
// 用户状态 动态、关注、粉丝 // 用户状态 动态、关注、粉丝
Rx<UserStat> userStat = UserStat().obs; Rx<UserStat> userStat = UserStat().obs;
RxBool userLogin = false.obs; // 用户信息 头像、昵称、lv
Box userInfoCache = GStrorage.userInfo; Rx<UserInfoData> userInfo = UserInfoData().obs;
Box setting = GStrorage.setting;
Rx<ThemeType> themeType = ThemeType.system.obs; Rx<ThemeType> themeType = ThemeType.system.obs;
Rx<FavFolderData> favFolderData = FavFolderData().obs;
Box setting = GStrorage.setting;
Box userInfoCache = GStrorage.userInfo;
List menuList = [
{
'icon': Icons.history,
'title': '观看记录',
'onTap': () => Get.toNamed('/history'),
},
{
'icon': Icons.star_border,
'title': '我的收藏',
'onTap': () => Get.toNamed('/fav'),
},
{
'icon': Icons.subscriptions_outlined,
'title': '我的订阅',
'onTap': () => Get.toNamed('/subscription'),
},
{
'icon': Icons.watch_later_outlined,
'title': '稍后再看',
'onTap': () => Get.toNamed('/later'),
},
];
@override @override
onInit() { void onInit() {
super.onInit(); super.onInit();
if (userInfoCache.get('userInfoCache') != null) { if (userInfoCache.get('userInfoCache') != null) {
@ -109,7 +132,10 @@ class MineController extends GetxController {
SmartDialog.showToast('账号未登录'); SmartDialog.showToast('账号未登录');
return; return;
} }
Get.toNamed('/follow?mid=${userInfo.value.mid}', preventDuplicates: false); Get.toNamed(
'/follow?mid=${userInfo.value.mid}',
preventDuplicates: false,
);
} }
pushFans() { pushFans() {
@ -117,7 +143,10 @@ class MineController extends GetxController {
SmartDialog.showToast('账号未登录'); SmartDialog.showToast('账号未登录');
return; return;
} }
Get.toNamed('/fan?mid=${userInfo.value.mid}', preventDuplicates: false); Get.toNamed(
'/fan?mid=${userInfo.value.mid}',
preventDuplicates: false,
);
} }
pushDynamic() { pushDynamic() {
@ -125,7 +154,22 @@ class MineController extends GetxController {
SmartDialog.showToast('账号未登录'); SmartDialog.showToast('账号未登录');
return; return;
} }
Get.toNamed('/memberDynamics?mid=${userInfo.value.mid}', Get.toNamed(
preventDuplicates: false); '/memberDynamics?mid=${userInfo.value.mid}',
preventDuplicates: false,
);
}
Future<dynamic> queryFavFolder() async {
if (!userLogin.value) {
return {'status': false, 'data': [], 'msg': '未登录'};
}
var res = await await UserHttp.userfavFolder(
pn: 1,
ps: 5,
mid: userInfo.value.mid!,
);
favFolderData.value = res['data'];
return res;
} }
} }

View File

@ -1,12 +1,13 @@
// ignore_for_file: no_leading_underscores_for_local_identifiers
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:pilipala/common/constants.dart'; import 'package:pilipala/common/constants.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart';
import 'package:pilipala/models/common/theme_type.dart'; import 'package:pilipala/models/common/theme_type.dart';
import 'package:pilipala/models/user/fav_folder.dart';
import 'package:pilipala/models/user/info.dart'; import 'package:pilipala/models/user/info.dart';
import 'package:pilipala/models/user/stat.dart';
import 'package:pilipala/utils/utils.dart';
import 'controller.dart'; import 'controller.dart';
class MinePage extends StatefulWidget { class MinePage extends StatefulWidget {
@ -16,19 +17,23 @@ class MinePage extends StatefulWidget {
State<MinePage> createState() => _MinePageState(); State<MinePage> createState() => _MinePageState();
} }
class _MinePageState extends State<MinePage> { class _MinePageState extends State<MinePage>
final MineController mineController = Get.put(MineController()); with AutomaticKeepAliveClientMixin {
final MineController ctr = Get.put(MineController());
late Future _futureBuilderFuture; late Future _futureBuilderFuture;
@override
bool get wantKeepAlive => true;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_futureBuilderFuture = mineController.queryUserInfo(); _futureBuilderFuture = ctr.queryUserInfo();
ctr.queryFavFolder();
mineController.userLogin.listen((status) { ctr.userLogin.listen((status) {
if (mounted) { if (mounted) {
setState(() { setState(() {
_futureBuilderFuture = mineController.queryUserInfo(); _futureBuilderFuture = ctr.queryUserInfo();
}); });
} }
}); });
@ -36,50 +41,44 @@ class _MinePageState extends State<MinePage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
super.build(context);
return Scaffold( return Scaffold(
appBar: AppBar( appBar: AppBar(
automaticallyImplyLeading: false,
scrolledUnderElevation: 0, scrolledUnderElevation: 0,
elevation: 0, elevation: 0,
toolbarHeight: kTextTabBarHeight + 20,
backgroundColor: Colors.transparent,
centerTitle: false,
title: const Text(
'PLPL',
style: TextStyle(
height: 2.8,
fontSize: 17,
fontWeight: FontWeight.bold,
fontFamily: 'Jura-Bold',
),
),
actions: [ actions: [
IconButton( IconButton(
onPressed: () => mineController.onChangeTheme(), icon: const Icon(Icons.search_outlined),
icon: Icon( onPressed: () => Get.toNamed('/search'),
mineController.themeType.value == ThemeType.dark
? CupertinoIcons.sun_max
: CupertinoIcons.moon,
size: 22,
),
), ),
IconButton( IconButton(
onPressed: () => Get.toNamed('/setting', preventDuplicates: false), icon: Icon(
icon: const Icon( ctr.themeType.value == ThemeType.dark
CupertinoIcons.slider_horizontal_3, ? Icons.wb_sunny_outlined
: Icons.dark_mode_outlined,
), ),
onPressed: () => ctr.onChangeTheme(),
), ),
const SizedBox(width: 10), IconButton(
icon: const Icon(Icons.settings_outlined),
onPressed: () => Get.toNamed('/setting', preventDuplicates: false),
),
const SizedBox(width: 22),
], ],
), ),
body: LayoutBuilder( body: RefreshIndicator(
builder: (context, constraint) { onRefresh: () async {
return SingleChildScrollView( await ctr.queryUserInfo();
physics: const NeverScrollableScrollPhysics(), },
child: SizedBox( child: SingleChildScrollView(
height: constraint.maxHeight, physics: const AlwaysScrollableScrollPhysics(
parent: BouncingScrollPhysics()),
child: Padding(
padding: const EdgeInsets.only(bottom: 110),
child: Expanded(
child: Column( child: Column(
children: [ children: [
Obx(() => _buildProfileSection(context, ctr.userInfo.value)),
const SizedBox(height: 10), const SizedBox(height: 10),
FutureBuilder( FutureBuilder(
future: _futureBuilderFuture, future: _futureBuilderFuture,
@ -90,275 +89,424 @@ class _MinePageState extends State<MinePage> {
} }
if (snapshot.data['status']) { if (snapshot.data['status']) {
return Obx( return Obx(
() => userInfoBuild(mineController, context)); () => _buildStatsSection(
context,
ctr.userStat.value,
),
);
} else { } else {
return userInfoBuild(mineController, context); return _buildStatsSection(
context,
ctr.userStat.value,
);
} }
} else { } else {
return userInfoBuild(mineController, context); return _buildStatsSection(
context,
ctr.userStat.value,
);
} }
}, },
), ),
_buildMenuSection(context),
Obx(
() => Visibility(
visible: ctr.userLogin.value,
child: Divider(
height: 25,
color: Theme.of(context).dividerColor.withOpacity(0.1),
),
),
),
Obx(
() => ctr.userLogin.value
? _buildFavoritesSection(context)
: const SizedBox(),
),
SizedBox(
height: MediaQuery.of(context).padding.bottom +
kBottomNavigationBarHeight,
)
], ],
), ),
), ),
),
),
),
);
}
Widget _buildProfileSection(BuildContext context, UserInfoData userInfo) {
return InkWell(
onTap: () => ctr.onLogin(),
child: Padding(
padding: const EdgeInsets.fromLTRB(24, 10, 30, 10),
child: Row(
children: [
userInfo.face != null
? NetworkImgLayer(
src: userInfo.face,
width: 85,
height: 85,
type: 'avatar',
)
: ClipOval(
child: SizedBox(
width: 85,
height: 85,
child: Image.asset('assets/images/noface.jpeg'),
),
),
const SizedBox(width: 12),
Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Text(
userInfo.uname ?? '去登录',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 17,
fontWeight: FontWeight.bold,
color: userInfo.vipStatus == 1
? const Color.fromARGB(255, 251, 100, 163)
: null,
),
),
const SizedBox(width: 6),
Image.asset(
'assets/images/lv/lv${userInfo.levelInfo != null ? userInfo.levelInfo!.currentLevel : '0'}.png',
height: 12,
),
],
),
const SizedBox(height: 2),
if (userInfo.vipType != 0 && userInfo.vipStatus == 1) ...[
Image.network(
userInfo.vipLabel!['img_label_uri_hans_static'],
height: 22,
),
const SizedBox(height: 2),
],
Text.rich(
TextSpan(children: [
TextSpan(
text: '硬币: ',
style: TextStyle(
color: Theme.of(context).colorScheme.outline)),
TextSpan(
text: (userInfo.money ?? '-').toString(),
style: TextStyle(
color: Theme.of(context).colorScheme.primary)),
]),
)
],
),
const Spacer(),
Icon(
Icons.keyboard_arrow_right_rounded,
size: 28,
color: Theme.of(context).colorScheme.outline,
),
],
),
),
);
}
Widget _buildStatsSection(BuildContext context, UserStat userStat) {
return Padding(
padding: const EdgeInsets.only(left: 30, right: 30),
child: LayoutBuilder(
builder: (context, constraints) {
return SizedBox(
height: constraints.maxWidth / 3 * 0.6,
child: GridView.count(
primary: false,
padding: const EdgeInsets.all(0),
crossAxisCount: 3,
childAspectRatio: 1.67,
children: <Widget>[
_buildStatItem(
context,
(userStat.dynamicCount ?? '-').toString(),
'动态',
ctr.pushDynamic,
),
_buildStatItem(
context,
(userStat.following ?? '-').toString(),
'关注',
ctr.pushFollow,
),
_buildStatItem(
context,
(userStat.follower ?? '-').toString(),
'粉丝',
ctr.pushFans,
),
],
),
); );
}, },
), ),
); );
} }
Widget userInfoBuild(_mineController, context) { Widget _buildStatItem(
return Column( BuildContext context,
children: [ String count,
const SizedBox(height: 5), String label,
GestureDetector( Function onTap,
onTap: () => _mineController.onLogin(), ) {
child: ClipOval( TextStyle style = TextStyle(
child: Container( fontSize: Theme.of(context).textTheme.titleMedium!.fontSize,
width: 85, fontWeight: FontWeight.bold);
height: 85, return InkWell(
color: Theme.of(context).colorScheme.onInverseSurface, onTap: () => onTap(),
child: Center( // onTap: () {},
child: _mineController.userInfo.value.face != null borderRadius: StyleString.mdRadius,
? NetworkImgLayer( child: Column(
src: _mineController.userInfo.value.face, mainAxisAlignment: MainAxisAlignment.center,
width: 85, children: [
height: 85) Text(count, style: style),
: Image.asset('assets/images/noface.jpeg'), const SizedBox(height: 2),
), Text(
), label,
), style: Theme.of(context).textTheme.labelMedium!.copyWith(
), color: Theme.of(context).colorScheme.outline,
const SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
_mineController.userInfo.value.uname ?? '点击头像登录',
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(width: 4),
Image.asset(
'assets/images/lv/lv${_mineController.userInfo.value.levelInfo != null ? _mineController.userInfo.value.levelInfo!.currentLevel : '0'}.png',
height: 10,
),
],
),
const SizedBox(height: 5),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text.rich(TextSpan(children: [
TextSpan(
text: '硬币: ',
style:
TextStyle(color: Theme.of(context).colorScheme.outline)),
TextSpan(
text: (_mineController.userInfo.value.money ?? 'pilipala')
.toString(),
style:
TextStyle(color: Theme.of(context).colorScheme.primary)),
]))
],
),
const SizedBox(height: 25),
if (_mineController.userInfo.value.levelInfo != null) ...[
LayoutBuilder(
builder: (context, BoxConstraints box) {
LevelInfo levelInfo = _mineController.userInfo.value.levelInfo;
return SizedBox(
width: box.maxWidth,
height: 24,
child: Stack(
children: [
Positioned(
top: 0,
right: 0,
bottom: 0,
child: Container(
color: Theme.of(context).colorScheme.primary,
height: 24,
constraints:
const BoxConstraints(minWidth: 100), // 设置最小宽度为100
width: box.maxWidth *
(1 - (levelInfo.currentExp! / levelInfo.nextExp!)),
child: Center(
child: Text(
'${levelInfo.currentExp!}/${levelInfo.nextExp!}',
style: TextStyle(
color: Theme.of(context).colorScheme.onPrimary,
fontSize: 12,
),
),
),
),
),
Positioned(
top: 23,
left: 0,
bottom: 0,
child: Container(
width: box.maxWidth *
(_mineController
.userInfo.value.levelInfo!.currentExp! /
_mineController
.userInfo.value.levelInfo!.nextExp!),
height: 1,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.primary,
),
),
),
],
), ),
);
},
), ),
], ],
const SizedBox(height: 30), ),
Padding( );
padding: const EdgeInsets.only(left: 12, right: 12), }
child: LayoutBuilder(
builder: (context, constraints) { Widget _buildMenuSection(BuildContext context) {
TextStyle style = TextStyle( return Padding(
fontSize: Theme.of(context).textTheme.titleMedium!.fontSize, padding: const EdgeInsets.only(left: 12, right: 12),
color: Theme.of(context).colorScheme.primary, child: LayoutBuilder(
fontWeight: FontWeight.bold); builder: (BuildContext context, BoxConstraints constraints) {
return SizedBox( return SizedBox(
height: constraints.maxWidth / 3 * 0.6, height: constraints.maxWidth / 4 * 0.85,
child: GridView.count( child: GridView.count(
primary: false, primary: false,
padding: const EdgeInsets.all(0), crossAxisCount: 4,
crossAxisCount: 3, padding: const EdgeInsets.all(0),
childAspectRatio: 1.67, childAspectRatio: 1.2,
children: <Widget>[ children: [
InkWell( ...ctr.menuList.map((element) {
onTap: () => _mineController.pushDynamic(), return InkWell(
borderRadius: StyleString.mdRadius, onTap: () {
child: Column( if (!ctr.userLogin.value) {
mainAxisAlignment: MainAxisAlignment.center, SmartDialog.showToast('账号未登录');
children: [ } else {
AnimatedSwitcher( element['onTap']();
duration: const Duration(milliseconds: 400), }
transitionBuilder: },
(Widget child, Animation<double> animation) { borderRadius: StyleString.mdRadius,
return ScaleTransition( child: Column(
scale: animation, child: child); mainAxisAlignment: MainAxisAlignment.center,
}, children: [
child: Text( Padding(
(_mineController.userStat.value.dynamicCount ?? padding: const EdgeInsets.all(10),
'-') child: Icon(
.toString(), element['icon'],
key: ValueKey<String>(_mineController size: 21,
.userStat.value.dynamicCount color: Theme.of(context).colorScheme.primary,
.toString()),
style: style),
), ),
const SizedBox(height: 8), ),
Text( const SizedBox(height: 4),
'动态', Text(element['title'])
style: Theme.of(context).textTheme.labelMedium, ],
),
],
),
), ),
InkWell( );
onTap: () => _mineController.pushFollow(), }).toList(),
borderRadius: StyleString.mdRadius, ],
child: Column( ),
mainAxisAlignment: MainAxisAlignment.center, );
children: [ },
AnimatedSwitcher( ),
duration: const Duration(milliseconds: 400), );
transitionBuilder: }
(Widget child, Animation<double> animation) {
return ScaleTransition( Widget _buildFavoritesSection(context) {
scale: animation, child: child); return Column(
}, children: [
child: Text( _buildFavoritesTitle(context, 'fav', '收藏夹'),
(_mineController.userStat.value.following ?? const SizedBox(height: 4),
'-') SizedBox(
.toString(), width: double.infinity,
key: ValueKey<String>(_mineController height: MediaQuery.textScalerOf(context).scale(180),
.userStat.value.following child: FutureBuilder(
.toString()), future: ctr.queryFavFolder(),
style: style), builder: (context, snapshot) {
), if (snapshot.connectionState == ConnectionState.done) {
const SizedBox(height: 8), Map? data = snapshot.data;
Text( if (data != null && data['status']) {
'关注', List favFolderList = ctr.favFolderData.value.list!;
style: Theme.of(context).textTheme.labelMedium, int favFolderCount = ctr.favFolderData.value.count!;
), bool flag = favFolderCount > favFolderList.length;
], return Obx(
), () => ListView.builder(
itemCount:
ctr.favFolderData.value.list!.length + (flag ? 1 : 0),
itemBuilder: (context, index) {
if (flag && index == favFolderList.length) {
return Padding(
padding: const EdgeInsets.only(right: 14),
child: Center(
child: IconButton(
style: ButtonStyle(
padding: MaterialStateProperty.all(
EdgeInsets.zero),
backgroundColor:
MaterialStateProperty.resolveWith(
(states) {
return Theme.of(context)
.colorScheme
.primaryContainer
.withOpacity(0.5);
}),
),
onPressed: () => Get.toNamed('/fav'),
icon: Icon(
Icons.arrow_forward_ios,
size: 18,
color: Theme.of(context).colorScheme.primary,
),
),
),
);
} else {
return FavFolderItem(
item: ctr.favFolderData.value.list![index],
index: index,
);
}
},
scrollDirection: Axis.horizontal,
), ),
InkWell( );
onTap: () => _mineController.pushFans(), } else {
borderRadius: StyleString.mdRadius, return SizedBox(
child: Column( height: 110,
mainAxisAlignment: MainAxisAlignment.center, child: Center(child: Text(data?['msg'] ?? '')),
children: [ );
AnimatedSwitcher( }
duration: const Duration(milliseconds: 400), } else {
transitionBuilder: // 骨架屏
(Widget child, Animation<double> animation) { return Obx(
return ScaleTransition( () => ctr.favFolderData.value.list != null
scale: animation, child: child); ? ListView.builder(
}, scrollDirection: Axis.horizontal,
child: Text( itemCount: ctr.favFolderData.value.list!.length,
(_mineController.userStat.value.follower ?? '-') itemBuilder: (context, index) {
.toString(), return FavFolderItem(
key: ValueKey<String>(_mineController item: ctr.favFolderData.value.list![index],
.userStat.value.follower index: index,
.toString()), );
style: style), },
), )
const SizedBox(height: 8), : const SizedBox(),
Text( );
'粉丝', }
style: Theme.of(context).textTheme.labelMedium,
),
],
),
),
],
),
);
}, },
), ),
), ),
], ],
); );
} }
Widget _buildFavoritesTitle(
BuildContext context,
String type,
String title,
) {
return ListTile(
onTap: () => Get.toNamed('/fav'),
leading: null,
dense: true,
title: Padding(
padding: const EdgeInsets.only(left: 10),
child: Text.rich(
TextSpan(
children: [
TextSpan(
text: title,
style: TextStyle(
fontSize: Theme.of(context).textTheme.titleMedium!.fontSize,
fontWeight: FontWeight.bold),
),
const TextSpan(text: ' '),
TextSpan(
text: '20',
style: TextStyle(
fontSize: Theme.of(context).textTheme.titleSmall!.fontSize,
color: Theme.of(context).colorScheme.primary,
),
),
],
),
),
),
// trailing: IconButton(
// onPressed: () {},
// icon: const Icon(
// Icons.refresh,
// size: 20,
// ),
// ),
);
}
} }
class ActionItem extends StatelessWidget { class FavFolderItem extends StatelessWidget {
final Icon? icon; const FavFolderItem({super.key, this.item, this.index});
final Function? onTap; final FavFolderItemData? item;
final String? text; final int? index;
const ActionItem({
Key? key,
this.icon,
this.onTap,
this.text,
}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return InkWell( String heroTag = Utils.makeHeroTag(item!.fid);
onTap: () {}, return Container(
borderRadius: StyleString.mdRadius, margin: EdgeInsets.only(left: index == 0 ? 20 : 0, right: 14),
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Icon(icon!.icon!), InkWell(
onTap: () =>
Get.toNamed('/favDetail', arguments: item, parameters: {
'mediaId': item!.id.toString(),
'heroTag': heroTag,
'isOwner': '1',
}),
borderRadius: StyleString.mdRadius,
child: Hero(
tag: heroTag,
child: NetworkImgLayer(
src: item!.cover,
width: 180,
height: 110,
),
),
),
const SizedBox(height: 8), const SizedBox(height: 8),
Text( Text(
text!, ' ${item!.title}',
style: Theme.of(context).textTheme.labelMedium, overflow: TextOverflow.fade,
maxLines: 1,
), ),
Text(
'${item!.mediaCount}条视频',
style: Theme.of(context)
.textTheme
.labelSmall!
.copyWith(color: Theme.of(context).colorScheme.outline),
)
], ],
), ),
); );

View File

@ -0,0 +1,45 @@
import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:hive/hive.dart';
import 'package:pilipala/http/user.dart';
import 'package:pilipala/utils/storage.dart';
class MineEditController extends GetxController {
Box userInfoCache = GStrorage.userInfo;
final formKey = GlobalKey<FormState>();
final TextEditingController unameCtr = TextEditingController();
final TextEditingController useridCtr = TextEditingController();
final TextEditingController signCtr = TextEditingController();
final TextEditingController birthdayCtr = TextEditingController();
String? sex;
dynamic userInfo;
@override
void onInit() {
super.onInit();
userInfo = userInfoCache.get('userInfoCache');
}
Future getAccountInfo() async {
var res = await UserHttp.getAccountInfo();
if (res['status']) {
unameCtr.text = res['data']['uname'];
useridCtr.text = res['data']['userid'];
signCtr.text = res['data']['sign'];
birthdayCtr.text = res['data']['birthday'];
sex = res['data']['sex'];
}
return res;
}
Future updateAccountInfo() async {
var res = await UserHttp.updateAccountInfo(
uname: unameCtr.text,
sign: signCtr.text,
sex: sex!,
birthday: birthdayCtr.text,
);
SmartDialog.showToast(res['status'] ? res['msg'] : "更新失败:${res['msg']}");
}
}

View File

@ -1,4 +1,4 @@
library media; library mine_edit;
export './controller.dart'; export './controller.dart';
export './view.dart'; export './view.dart';

View File

@ -0,0 +1,177 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'controller.dart';
class MineEditPage extends StatefulWidget {
const MineEditPage({super.key});
@override
State<MineEditPage> createState() => _MineEditPageState();
}
class _MineEditPageState extends State<MineEditPage> {
final MineEditController ctr = Get.put(MineEditController());
late Future _futureBuilderFuture;
@override
void initState() {
super.initState();
_futureBuilderFuture = ctr.getAccountInfo();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('编辑资料'),
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: FutureBuilder(
future: _futureBuilderFuture,
builder: ((context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.data == null) {
return const SizedBox();
}
if (snapshot.data['status']) {
return Form(
key: ctr.formKey,
child: Column(
children: [
// 用户头像
// InkWell(
// onTap: () {},
// child: CircleAvatar(
// radius: 50,
// backgroundColor: Colors.transparent,
// backgroundImage:
// NetworkImage(ctr.userInfo.face), // 替换为实际的头像路径
// ),
// ),
const SizedBox(height: 24.0),
// 昵称
TextFormField(
controller: ctr.unameCtr,
decoration: const InputDecoration(
labelText: '昵称',
border: OutlineInputBorder(),
),
validator: (value) {
if (value == null || value.isEmpty) {
return '请输入昵称';
}
return null;
},
),
const SizedBox(height: 20.0),
// 用户名
TextFormField(
controller: ctr.useridCtr,
decoration: const InputDecoration(
labelText: '用户名',
border: OutlineInputBorder(),
enabled: false,
),
readOnly: true,
validator: (value) {
if (value == null || value.isEmpty) {
return '请输入用户名';
}
return null;
},
),
const SizedBox(height: 20.0),
// 签名
TextFormField(
controller: ctr.signCtr,
decoration: const InputDecoration(
labelText: '签名',
border: OutlineInputBorder(),
),
validator: (value) {
if (value == null || value.isEmpty) {
return '请输入签名';
}
return null;
},
),
const SizedBox(height: 20.0),
// 性别
DropdownButtonFormField<String>(
value: ctr.sex,
decoration: const InputDecoration(
labelText: '性别',
border: OutlineInputBorder(),
),
items: ['', '', '保密'].map((String value) {
return DropdownMenuItem<String>(
value: value,
child: Text(value),
);
}).toList(),
onChanged: (newValue) {
ctr.sex = newValue;
},
validator: (value) {
if (value == null || value.isEmpty) {
return '请选择性别';
}
return null;
},
),
const SizedBox(height: 20.0),
// 出生日期
TextFormField(
controller: ctr.birthdayCtr,
decoration: const InputDecoration(
labelText: '出生日期',
border: OutlineInputBorder(),
),
enabled: false,
readOnly: true,
onTap: () async {
// DateTime? pickedDate = await showDatePicker(
// context: context,
// initialDate: DateTime(1995, 12, 23),
// firstDate: DateTime(1900),
// lastDate: DateTime(2100),
// );
// if (pickedDate != null) {
// ctr.birthdayCtr.text =
// "${pickedDate.toLocal()}".split(' ')[0];
// }
},
validator: (value) {
if (value == null || value.isEmpty) {
return '请选择出生日期';
}
return null;
},
),
const SizedBox(height: 30.0),
// 提交按钮
ElevatedButton(
onPressed: () {
if (ctr.formKey.currentState!.validate()) {
// 处理表单提交
ctr.updateAccountInfo();
}
},
child: const Text('提交'),
),
],
),
);
} else {
return const SizedBox();
}
} else {
return const SizedBox();
}
})),
),
);
}
}

View File

@ -1,7 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:pilipala/models/common/tab_type.dart';
import 'package:pilipala/utils/storage.dart'; import 'package:pilipala/utils/storage.dart';
import '../../../models/common/nav_bar_config.dart'; import '../../../models/common/nav_bar_config.dart';
@ -23,7 +22,7 @@ class _NavigationbarSetPageState extends State<NavigationBarSetPage> {
super.initState(); super.initState();
defaultNavTabs = defaultNavigationBars; defaultNavTabs = defaultNavigationBars;
navBarSort = settingStorage navBarSort = settingStorage
.get(SettingBoxKey.navBarSort, defaultValue: [0, 1, 2, 3]); .get(SettingBoxKey.navBarSort, defaultValue: [0, 1, 2, 3, 4]);
// 对 tabData 进行排序 // 对 tabData 进行排序
defaultNavTabs.sort((a, b) { defaultNavTabs.sort((a, b) {
int indexA = navBarSort.indexOf(a['id']); int indexA = navBarSort.indexOf(a['id']);

View File

@ -675,7 +675,6 @@ class VideoDetailController extends GetxController
@override @override
void onClose() { void onClose() {
super.onClose(); super.onClose();
plPlayerController.dispose();
tabCtr.removeListener(() { tabCtr.removeListener(() {
onTabChanged(); onTabChanged();
}); });

View File

@ -773,7 +773,7 @@ InlineSpan buildContent(
// source: '', // source: '',
// dataString: matchStr, // dataString: matchStr,
); );
PiliSchame.fullPathPush(scheme); PiliSchame.httpsScheme(scheme);
} }
} else { } else {
if (appUrlSchema.startsWith('bilibili://search')) { if (appUrlSchema.startsWith('bilibili://search')) {

View File

@ -1,13 +1,18 @@
import 'dart:async'; import 'dart:async';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:image_picker/image_picker.dart';
import 'package:pilipala/http/dynamics.dart'; import 'package:pilipala/http/dynamics.dart';
import 'package:pilipala/http/reply.dart';
import 'package:pilipala/http/video.dart'; import 'package:pilipala/http/video.dart';
import 'package:pilipala/models/common/reply_type.dart'; import 'package:pilipala/models/common/reply_type.dart';
import 'package:pilipala/models/video/reply/emote.dart'; import 'package:pilipala/models/video/reply/emote.dart';
import 'package:pilipala/models/video/reply/item.dart'; import 'package:pilipala/models/video/reply/item.dart';
import 'package:pilipala/pages/emote/index.dart'; import 'package:pilipala/pages/emote/index.dart';
import 'package:pilipala/plugin/pl_gallery/hero_dialog_route.dart';
import 'package:pilipala/plugin/pl_gallery/interactiveviewer_gallery.dart';
import 'package:pilipala/utils/feed_back.dart'; import 'package:pilipala/utils/feed_back.dart';
import 'toolbar_icon_button.dart'; import 'toolbar_icon_button.dart';
@ -44,6 +49,9 @@ class _VideoReplyNewDialogState extends State<VideoReplyNewDialog>
RxBool isForward = false.obs; RxBool isForward = false.obs;
RxBool showForward = false.obs; RxBool showForward = false.obs;
RxString message = ''.obs; RxString message = ''.obs;
final ImagePicker _picker = ImagePicker();
RxList<String> imageList = [''].obs;
List<Map<dynamic, dynamic>> pictures = [];
@override @override
void initState() { void initState() {
@ -60,6 +68,7 @@ class _VideoReplyNewDialogState extends State<VideoReplyNewDialog>
if (routePath.startsWith('/video')) { if (routePath.startsWith('/video')) {
showForward.value = true; showForward.value = true;
} }
imageList.clear();
} }
_autoFocus() async { _autoFocus() async {
@ -90,6 +99,7 @@ class _VideoReplyNewDialogState extends State<VideoReplyNewDialog>
message: widget.replyItem != null && widget.replyItem!.root != 0 message: widget.replyItem != null && widget.replyItem!.root != 0
? ' 回复 @${widget.replyItem!.member!.uname!} : ${message.value}' ? ' 回复 @${widget.replyItem!.member!.uname!} : ${message.value}'
: message.value, : message.value,
pictures: pictures,
); );
if (result['status']) { if (result['status']) {
SmartDialog.showToast(result['data']['success_toast']); SmartDialog.showToast(result['data']['success_toast']);
@ -125,6 +135,59 @@ class _VideoReplyNewDialogState extends State<VideoReplyNewDialog>
); );
} }
void onChooseImage() async {
if (mounted) {
try {
final XFile? pickedFile =
await _picker.pickImage(source: ImageSource.gallery);
var res = await ReplyHttp.uploadImage(xFile: pickedFile!);
if (res['status']) {
imageList.add(res['data']['img_src']);
pictures.add(res['data']);
}
} catch (e) {
debugPrint('选择图片失败: $e');
}
}
}
void onPreviewImg(picList, initIndex, context) {
Navigator.of(context).push(
HeroDialogRoute<void>(
builder: (BuildContext context) => InteractiveviewerGallery(
sources: picList,
initIndex: initIndex,
itemBuilder: (
BuildContext context,
int index,
bool isFocus,
bool enablePageView,
) {
return GestureDetector(
behavior: HitTestBehavior.opaque,
onTap: () {
if (enablePageView) {
Navigator.of(context).pop();
}
},
child: Center(
child: Hero(
tag: picList[index],
child: CachedNetworkImage(
fadeInDuration: const Duration(milliseconds: 0),
imageUrl: picList[index],
fit: BoxFit.contain,
),
),
),
);
},
onPageChanged: (int pageIndex) {},
),
),
);
}
@override @override
void didChangeMetrics() { void didChangeMetrics() {
super.didChangeMetrics(); super.didChangeMetrics();
@ -175,10 +238,11 @@ class _VideoReplyNewDialogState extends State<VideoReplyNewDialog>
), ),
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
ConstrainedBox( ConstrainedBox(
constraints: const BoxConstraints( constraints: const BoxConstraints(
maxHeight: 200, maxHeight: 250,
minHeight: 120, minHeight: 120,
), ),
child: Container( child: Container(
@ -209,6 +273,65 @@ class _VideoReplyNewDialogState extends State<VideoReplyNewDialog>
), ),
), ),
), ),
Obx(
() => Padding(
padding: const EdgeInsets.fromLTRB(12, 10, 12, 10),
child: SizedBox(
height: 65, // 固定高度以避免无限扩展
child: ListView.separated(
scrollDirection: Axis.horizontal,
itemCount: imageList.length,
itemBuilder: (context, index) {
final url = imageList[index];
return url != ''
? Container(
width: 65,
height: 65,
clipBehavior: Clip.hardEdge,
decoration: BoxDecoration(
color: Theme.of(context)
.colorScheme
.secondaryContainer,
borderRadius:
const BorderRadius.all(Radius.circular(6))),
child: InkWell(
onTap: () =>
onPreviewImg(imageList, index, context),
onLongPress: () {
feedBack();
imageList.removeAt(index);
},
child: CachedNetworkImage(
imageUrl: url,
width: 65,
height: 65,
fit: BoxFit.cover,
),
),
)
: const SizedBox();
},
separatorBuilder: (context, index) =>
const SizedBox(width: 8.0),
),
),
),
),
Obx(
() => Visibility(
visible: imageList.isNotEmpty,
child: Padding(
padding: const EdgeInsets.fromLTRB(12, 0, 12, 10),
child: Text(
'点击预览,长按删除',
style: TextStyle(
color: Theme.of(context).colorScheme.outline,
fontSize: 12,
),
),
),
),
),
Divider( Divider(
height: 1, height: 1,
color: Theme.of(context).dividerColor.withOpacity(0.1), color: Theme.of(context).dividerColor.withOpacity(0.1),
@ -240,7 +363,7 @@ class _VideoReplyNewDialogState extends State<VideoReplyNewDialog>
toolbarType: toolbarType, toolbarType: toolbarType,
selected: toolbarType == 'input', selected: toolbarType == 'input',
), ),
const SizedBox(width: 20), const SizedBox(width: 10),
ToolbarIconButton( ToolbarIconButton(
onPressed: () { onPressed: () {
if (toolbarType == 'input') { if (toolbarType == 'input') {
@ -254,6 +377,15 @@ class _VideoReplyNewDialogState extends State<VideoReplyNewDialog>
toolbarType: toolbarType, toolbarType: toolbarType,
selected: toolbarType == 'emote', selected: toolbarType == 'emote',
), ),
if (widget.root != null && widget.root == 0) ...[
const SizedBox(width: 10),
ToolbarIconButton(
onPressed: onChooseImage,
icon: const Icon(Icons.photo, size: 22),
toolbarType: toolbarType,
selected: toolbarType == 'picture',
),
],
const SizedBox(width: 6), const SizedBox(width: 6),
Obx( Obx(
() => showForward.value () => showForward.value

View File

@ -511,6 +511,14 @@ class _VideoDetailPageState extends State<VideoDetailPage>
exitFullScreen(); exitFullScreen();
} }
SystemChrome.setSystemUIOverlayStyle(
SystemUiOverlayStyle(
systemNavigationBarColor: Colors.transparent,
systemNavigationBarIconBrightness:
Get.isDarkMode ? Brightness.light : Brightness.dark,
),
);
Widget buildLoadingWidget() { Widget buildLoadingWidget() {
return Center(child: Lottie.asset('assets/loading.json', width: 200)); return Center(child: Lottie.asset('assets/loading.json', width: 200));
} }
@ -605,11 +613,6 @@ class _VideoDetailPageState extends State<VideoDetailPage>
backgroundColor: Colors.black, backgroundColor: Colors.black,
elevation: 0, elevation: 0,
scrolledUnderElevation: 0, scrolledUnderElevation: 0,
systemOverlayStyle: Get.isDarkMode
? SystemUiOverlayStyle.light
: snapshot.data!.toDouble() > kToolbarHeight
? SystemUiOverlayStyle.dark
: SystemUiOverlayStyle.light,
); );
}), }),
), ),

View File

@ -228,7 +228,7 @@ class SessionItem extends StatelessWidget {
parameters: { parameters: {
'talkerId': sessionItem.talkerId.toString(), 'talkerId': sessionItem.talkerId.toString(),
'name': sessionItem.accountInfo.name, 'name': sessionItem.accountInfo.name,
'face': sessionItem.accountInfo.face, 'face': sessionItem.accountInfo.face ?? '',
'mid': (sessionItem.accountInfo?.mid ?? 0).toString(), 'mid': (sessionItem.accountInfo?.mid ?? 0).toString(),
'heroTag': heroTag, 'heroTag': heroTag,
}, },
@ -244,7 +244,7 @@ class SessionItem extends StatelessWidget {
width: 45, width: 45,
height: 45, height: 45,
type: 'avatar', type: 'avatar',
src: sessionItem.accountInfo.face, src: sessionItem.accountInfo.face ?? '',
), ),
), ),
), ),

View File

@ -1045,6 +1045,14 @@ class PlPlayerController {
/// 缓存本次弹幕选项 /// 缓存本次弹幕选项
cacheDanmakuOption() { cacheDanmakuOption() {
final cache = GlobalDataCache();
cache.blockTypes = blockTypes;
cache.showArea = showArea;
cache.opacityVal = opacityVal;
cache.fontSizeVal = fontSizeVal;
cache.danmakuDurationVal = danmakuDurationVal;
cache.strokeWidth = strokeWidth;
localCache.put(LocalCacheKey.danmakuBlockType, blockTypes); localCache.put(LocalCacheKey.danmakuBlockType, blockTypes);
localCache.put(LocalCacheKey.danmakuShowArea, showArea); localCache.put(LocalCacheKey.danmakuShowArea, showArea);
localCache.put(LocalCacheKey.danmakuOpacity, opacityVal); localCache.put(LocalCacheKey.danmakuOpacity, opacityVal);

View File

@ -0,0 +1,76 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import '../widgets/seek_indicator.dart';
class SeekPanel extends StatelessWidget {
const SeekPanel({
required this.mountSeekBackwardButton,
required this.mountSeekForwardButton,
required this.hideSeekBackwardButton,
required this.hideSeekForwardButton,
required this.onSubmittedcb,
Key? key,
}) : super(key: key);
final RxBool mountSeekBackwardButton;
final RxBool mountSeekForwardButton;
final RxBool hideSeekBackwardButton;
final RxBool hideSeekForwardButton;
final void Function(String, Duration) onSubmittedcb;
@override
Widget build(BuildContext context) {
return Obx(
() => Visibility(
visible: mountSeekBackwardButton.value || mountSeekForwardButton.value,
child: Positioned.fill(
child: Row(
children: [
_buildSeekIndicator(
mountSeekBackwardButton,
hideSeekBackwardButton,
'backward',
SeekIndicator(
direction: SeekDirection.backward,
onSubmitted: (Duration value) {
onSubmittedcb.call('backward', value);
},
),
),
Expanded(child: Container()),
_buildSeekIndicator(
mountSeekForwardButton,
hideSeekForwardButton,
'forward',
SeekIndicator(
direction: SeekDirection.forward,
onSubmitted: (Duration value) {
onSubmittedcb.call('forward', value);
},
),
),
],
),
),
),
);
}
Widget _buildSeekIndicator(
RxBool mountSeekButton,
RxBool hideSeekButton,
String direction,
Widget seekIndicator,
) {
return Expanded(
child: mountSeekButton.value
? AnimatedOpacity(
opacity: hideSeekButton.value ? 0.0 : 1.0,
duration: const Duration(milliseconds: 400),
curve: Curves.easeInOut,
child: seekIndicator,
)
: const SizedBox.shrink(),
);
}
}

View File

@ -22,12 +22,11 @@ import 'package:screen_brightness/screen_brightness.dart';
import '../../utils/global_data_cache.dart'; import '../../utils/global_data_cache.dart';
import 'models/bottom_control_type.dart'; import 'models/bottom_control_type.dart';
import 'models/bottom_progress_behavior.dart'; import 'models/bottom_progress_behavior.dart';
import 'panels/seek_panel.dart';
import 'widgets/app_bar_ani.dart'; import 'widgets/app_bar_ani.dart';
import 'widgets/backward_seek.dart';
import 'widgets/bottom_control.dart'; import 'widgets/bottom_control.dart';
import 'widgets/common_btn.dart'; import 'widgets/common_btn.dart';
import 'widgets/control_bar.dart'; import 'widgets/control_bar.dart';
import 'widgets/forward_seek.dart';
import 'widgets/play_pause_btn.dart'; import 'widgets/play_pause_btn.dart';
class PLVideoPlayer extends StatefulWidget { class PLVideoPlayer extends StatefulWidget {
@ -69,8 +68,8 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
final RxBool _mountSeekBackwardButton = false.obs; final RxBool _mountSeekBackwardButton = false.obs;
final RxBool _mountSeekForwardButton = false.obs; final RxBool _mountSeekForwardButton = false.obs;
final RxBool _hideSeekBackwardButton = false.obs; final RxBool _hideSeekBackwardButton = true.obs;
final RxBool _hideSeekForwardButton = false.obs; final RxBool _hideSeekForwardButton = true.obs;
final RxDouble _brightnessValue = 0.0.obs; final RxDouble _brightnessValue = 0.0.obs;
final RxBool _brightnessIndicator = false.obs; final RxBool _brightnessIndicator = false.obs;
@ -97,10 +96,12 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
void onDoubleTapSeekBackward() { void onDoubleTapSeekBackward() {
_mountSeekBackwardButton.value = true; _mountSeekBackwardButton.value = true;
_hideSeekBackwardButton.value = false;
} }
void onDoubleTapSeekForward() { void onDoubleTapSeekForward() {
_mountSeekForwardButton.value = true; _mountSeekForwardButton.value = true;
_hideSeekForwardButton.value = false;
} }
// 双击播放、暂停 // 双击播放、暂停
@ -375,6 +376,29 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
return list; return list;
} }
void _handleSubmittedCallback(String type, Duration value) {
final PlPlayerController _ = widget.controller;
final Player player =
_.videoPlayerController ?? widget.controller.videoPlayerController!;
late Duration result;
switch (type) {
case 'backward':
_hideSeekBackwardButton.value = true;
result = player.state.position - value;
break;
case 'forward':
_hideSeekForwardButton.value = true;
result = player.state.position + value;
break;
}
_mountSeekBackwardButton.value = false;
_mountSeekForwardButton.value = false;
result = result.clamp(Duration.zero, player.state.duration);
player.seek(result);
_.play();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final PlPlayerController _ = widget.controller; final PlPlayerController _ = widget.controller;
@ -865,99 +889,13 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
} }
}), }),
/// 点击 快进/快退 /// 快进/快退面板
Obx( SeekPanel(
() => Visibility( mountSeekBackwardButton: _mountSeekBackwardButton,
visible: mountSeekForwardButton: _mountSeekForwardButton,
_mountSeekBackwardButton.value || _mountSeekForwardButton.value, hideSeekBackwardButton: _hideSeekBackwardButton,
child: Positioned.fill( hideSeekForwardButton: _hideSeekForwardButton,
child: Row( onSubmittedcb: _handleSubmittedCallback,
children: [
Expanded(
child: _mountSeekBackwardButton.value
? TweenAnimationBuilder<double>(
tween: Tween<double>(
begin: 0.0,
end: _hideSeekBackwardButton.value ? 0.0 : 1.0,
),
duration: const Duration(milliseconds: 150),
builder: (BuildContext context, double value,
Widget? child) =>
Opacity(
opacity: value,
child: child,
),
onEnd: () {
if (_hideSeekBackwardButton.value) {
_hideSeekBackwardButton.value = false;
_mountSeekBackwardButton.value = false;
}
},
child: BackwardSeekIndicator(
onChanged: (Duration value) => {},
onSubmitted: (Duration value) {
_hideSeekBackwardButton.value = true;
final Player player =
widget.controller.videoPlayerController!;
Duration result = player.state.position - value;
result = result.clamp(
Duration.zero,
player.state.duration,
);
player.seek(result);
widget.controller.play();
},
),
)
: const SizedBox(),
),
Expanded(
child: SizedBox(
width: MediaQuery.sizeOf(context).width / 4,
),
),
Expanded(
child: _mountSeekForwardButton.value
? TweenAnimationBuilder<double>(
tween: Tween<double>(
begin: 0.0,
end: _hideSeekForwardButton.value ? 0.0 : 1.0,
),
duration: const Duration(milliseconds: 150),
builder: (BuildContext context, double value,
Widget? child) =>
Opacity(
opacity: value,
child: child,
),
onEnd: () {
if (_hideSeekForwardButton.value) {
_hideSeekForwardButton.value = false;
_mountSeekForwardButton.value = false;
}
},
child: ForwardSeekIndicator(
onChanged: (Duration value) => {},
onSubmitted: (Duration value) {
_hideSeekForwardButton.value = true;
final Player player =
widget.controller.videoPlayerController!;
Duration result = player.state.position + value;
result = result.clamp(
Duration.zero,
player.state.duration,
);
player.seek(result);
widget.controller.play();
},
),
)
: const SizedBox(),
),
],
),
),
),
), ),
], ],
); );

View File

@ -1,91 +0,0 @@
import 'dart:async';
import 'package:flutter/material.dart';
class BackwardSeekIndicator extends StatefulWidget {
final void Function(Duration) onChanged;
final void Function(Duration) onSubmitted;
const BackwardSeekIndicator({
Key? key,
required this.onChanged,
required this.onSubmitted,
}) : super(key: key);
@override
State<BackwardSeekIndicator> createState() => BackwardSeekIndicatorState();
}
class BackwardSeekIndicatorState extends State<BackwardSeekIndicator> {
Duration value = const Duration(seconds: 10);
Timer? timer;
@override
void setState(VoidCallback fn) {
if (mounted) {
super.setState(fn);
}
}
@override
void initState() {
super.initState();
timer = Timer(const Duration(milliseconds: 200), () {
widget.onSubmitted.call(value);
});
}
void increment() {
timer?.cancel();
timer = Timer(const Duration(milliseconds: 200), () {
widget.onSubmitted.call(value);
});
widget.onChanged.call(value);
// 重复点击 快退秒数累加10
setState(() {
value += const Duration(seconds: 10);
});
}
@override
Widget build(BuildContext context) {
return Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: [
Color(0x88767676),
Color(0x00767676),
],
begin: Alignment.centerLeft,
end: Alignment.centerRight,
),
),
child: InkWell(
splashColor: const Color(0x44767676),
onTap: increment,
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Icon(
Icons.fast_rewind,
size: 24.0,
color: Color(0xFFFFFFFF),
),
const SizedBox(height: 8.0),
Text(
'快退${value.inSeconds}',
style: const TextStyle(
fontSize: 12.0,
color: Color(0xFFFFFFFF),
),
),
],
),
),
),
);
}
}

View File

@ -1,91 +0,0 @@
import 'dart:async';
import 'package:flutter/material.dart';
class ForwardSeekIndicator extends StatefulWidget {
final void Function(Duration) onChanged;
final void Function(Duration) onSubmitted;
const ForwardSeekIndicator({
Key? key,
required this.onChanged,
required this.onSubmitted,
}) : super(key: key);
@override
State<ForwardSeekIndicator> createState() => ForwardSeekIndicatorState();
}
class ForwardSeekIndicatorState extends State<ForwardSeekIndicator> {
Duration value = const Duration(seconds: 10);
Timer? timer;
@override
void setState(VoidCallback fn) {
if (mounted) {
super.setState(fn);
}
}
@override
void initState() {
super.initState();
timer = Timer(const Duration(milliseconds: 200), () {
widget.onSubmitted.call(value);
});
}
void increment() {
timer?.cancel();
timer = Timer(const Duration(milliseconds: 200), () {
widget.onSubmitted.call(value);
});
widget.onChanged.call(value);
// 重复点击 快进秒数累加10
setState(() {
value += const Duration(seconds: 10);
});
}
@override
Widget build(BuildContext context) {
return Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: [
Color(0x00767676),
Color(0x88767676),
],
begin: Alignment.centerLeft,
end: Alignment.centerRight,
),
),
child: InkWell(
splashColor: const Color(0x44767676),
onTap: increment,
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
const Icon(
Icons.fast_forward,
size: 24.0,
color: Color(0xFFFFFFFF),
),
const SizedBox(height: 8.0),
Text(
'快进${value.inSeconds}',
style: const TextStyle(
fontSize: 12.0,
color: Color(0xFFFFFFFF),
),
),
],
),
),
),
);
}
}

View File

@ -0,0 +1,94 @@
import 'dart:async';
import 'package:flutter/material.dart';
enum SeekDirection { forward, backward }
class SeekIndicator extends StatefulWidget {
final SeekDirection direction;
final void Function(Duration) onSubmitted;
const SeekIndicator({
Key? key,
required this.direction,
required this.onSubmitted,
}) : super(key: key);
@override
State<SeekIndicator> createState() => _SeekIndicatorState();
}
class _SeekIndicatorState extends State<SeekIndicator> {
Timer? timer;
@override
void initState() {
super.initState();
_startTimer();
}
void _startTimer() {
timer?.cancel();
timer = Timer(const Duration(milliseconds: 400), () {
widget.onSubmitted.call(const Duration(seconds: 10));
timer = null;
});
}
@override
void dispose() {
timer?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: widget.direction == SeekDirection.forward
? [
const Color(0x00767676),
const Color(0x88767676),
]
: [
const Color(0x88767676),
const Color(0x00767676),
],
begin: Alignment.centerLeft,
end: Alignment.centerRight,
),
),
child: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Icon(
widget.direction == SeekDirection.forward
? Icons.fast_forward
: Icons.fast_rewind,
size: 24.0,
color: const Color(0xFFFFFFFF),
),
const SizedBox(height: 8.0),
Text(
widget.direction == SeekDirection.forward ? '快进10秒' : '快退10秒',
style: const TextStyle(
fontSize: 12.0,
color: Color(0xFFFFFFFF),
shadows: [
Shadow(
color: Color(0xFF000000),
offset: Offset(0, 2),
blurRadius: 4,
),
],
),
),
],
),
),
);
}
}

View File

@ -10,6 +10,8 @@ import 'package:pilipala/pages/message/at/index.dart';
import 'package:pilipala/pages/message/like/index.dart'; import 'package:pilipala/pages/message/like/index.dart';
import 'package:pilipala/pages/message/reply/index.dart'; import 'package:pilipala/pages/message/reply/index.dart';
import 'package:pilipala/pages/message/system/index.dart'; import 'package:pilipala/pages/message/system/index.dart';
import 'package:pilipala/pages/mine/index.dart';
import 'package:pilipala/pages/mine_edit/index.dart';
import 'package:pilipala/pages/opus/index.dart'; import 'package:pilipala/pages/opus/index.dart';
import 'package:pilipala/pages/read/index.dart'; import 'package:pilipala/pages/read/index.dart';
import 'package:pilipala/pages/setting/pages/logs.dart'; import 'package:pilipala/pages/setting/pages/logs.dart';
@ -31,7 +33,6 @@ import '../pages/html/index.dart';
import '../pages/later/index.dart'; import '../pages/later/index.dart';
import '../pages/live_room/view.dart'; import '../pages/live_room/view.dart';
import '../pages/login/index.dart'; import '../pages/login/index.dart';
import '../pages/media/index.dart';
import '../pages/member/index.dart'; import '../pages/member/index.dart';
import '../pages/member_archive/index.dart'; import '../pages/member_archive/index.dart';
import '../pages/member_coin/index.dart'; import '../pages/member_coin/index.dart';
@ -79,8 +80,6 @@ class Routes {
// 设置 // 设置
CustomGetPage(name: '/setting', page: () => const SettingPage()), CustomGetPage(name: '/setting', page: () => const SettingPage()),
// //
CustomGetPage(name: '/media', page: () => const MediaPage()),
//
CustomGetPage(name: '/fav', page: () => const FavPage()), CustomGetPage(name: '/fav', page: () => const FavPage()),
// //
CustomGetPage(name: '/favDetail', page: () => const FavDetailPage()), CustomGetPage(name: '/favDetail', page: () => const FavDetailPage()),
@ -187,6 +186,8 @@ class Routes {
// 系统通知 // 系统通知
CustomGetPage( CustomGetPage(
name: '/messageSystem', page: () => const MessageSystemPage()), name: '/messageSystem', page: () => const MessageSystemPage()),
// 我的
CustomGetPage(name: '/mine', page: () => const MinePage()),
// 收藏夹编辑 // 收藏夹编辑
CustomGetPage(name: '/favEdit', page: () => const FavEditPage()), CustomGetPage(name: '/favEdit', page: () => const FavEditPage()),
@ -196,6 +197,8 @@ class Routes {
// 用户专栏 // 用户专栏
CustomGetPage( CustomGetPage(
name: '/memberArticle', page: () => const MemberArticlePage()), name: '/memberArticle', page: () => const MemberArticlePage()),
// 用户信息编辑
CustomGetPage(name: '/mineEdit', page: () => const MineEditPage()),
]; ];
} }

View File

@ -1,3 +1,4 @@
import 'package:app_links/app_links.dart';
import 'package:appscheme/appscheme.dart'; import 'package:appscheme/appscheme.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
@ -10,22 +11,29 @@ import 'url_utils.dart';
import 'utils.dart'; import 'utils.dart';
class PiliSchame { class PiliSchame {
static late AppLinks appLinks;
static AppScheme appScheme = AppSchemeImpl.getInstance()!; static AppScheme appScheme = AppSchemeImpl.getInstance()!;
static Future<void> init() async { static Future<void> init() async {
/// appLinks = AppLinks();
final SchemeEntity? value = await appScheme.getInitScheme(); appLinks.uriLinkStream.listen((Uri uri) {
if (value != null) { final String scheme = uri.scheme;
routePush(value); if (RegExp(r'^pili', caseSensitive: false).hasMatch(scheme)) {
} piliScheme(uri);
}
});
appScheme.getInitScheme().then((SchemeEntity? value) {
if (value != null) {
routePush(value);
}
});
/// 完整链接进入 b23.无效
appScheme.getLatestScheme().then((SchemeEntity? value) { appScheme.getLatestScheme().then((SchemeEntity? value) {
if (value != null) { if (value != null) {
routePush(value); routePush(value);
} }
}); });
/// 注册从外部打开的Scheme监听信息 #
appScheme.registerSchemeListener().listen((SchemeEntity? event) { appScheme.registerSchemeListener().listen((SchemeEntity? event) {
if (event != null) { if (event != null) {
routePush(event); routePush(event);
@ -36,88 +44,11 @@ class PiliSchame {
/// 路由跳转 /// 路由跳转
static void routePush(value) async { static void routePush(value) async {
final String scheme = value.scheme; final String scheme = value.scheme;
final String host = value.host;
final String path = value.path;
if (scheme == 'bilibili') { if (scheme == 'bilibili') {
switch (host) { biliScheme(value);
case 'root':
Navigator.popUntil(
Get.context!, (Route<dynamic> route) => route.isFirst);
break;
case 'space':
final String mid = path.split('/').last;
Get.toNamed<dynamic>(
'/member?mid=$mid',
arguments: <String, dynamic>{'face': null},
);
break;
case 'video':
String pathQuery = path.split('/').last;
final numericRegex = RegExp(r'^[0-9]+$');
if (numericRegex.hasMatch(pathQuery)) {
pathQuery = 'AV$pathQuery';
}
Map map = IdUtils.matchAvorBv(input: pathQuery);
if (map.containsKey('AV')) {
_videoPush(map['AV'], null);
} else if (map.containsKey('BV')) {
_videoPush(null, map['BV']);
} else {
SmartDialog.showToast('投稿匹配失败');
}
break;
case 'live':
final String roomId = path.split('/').last;
Get.toNamed<dynamic>(
'/liveRoom?roomid=$roomId',
arguments: <String, String?>{'liveItem': null, 'heroTag': roomId},
);
break;
case 'bangumi':
if (path.startsWith('/season')) {
final String seasonId = path.split('/').last;
RoutePush.bangumiPush(int.parse(seasonId), null);
}
break;
case 'opus':
if (path.startsWith('/detail')) {
var opusId = path.split('/').last;
Get.toNamed('/opus', parameters: {
'title': '',
'id': opusId,
'articleType': 'opus',
});
}
break;
case 'search':
Get.toNamed('/searchResult', parameters: {'keyword': ''});
break;
case 'article':
final String id = path.split('/').last.split('?').first;
Get.toNamed(
'/read',
parameters: {
'title': 'cv$id',
'id': id,
'dynamicType': 'read',
},
);
break;
case 'pgc':
if (path.contains('ep')) {
final String lastPathSegment = path.split('/').last;
RoutePush.bangumiPush(
null, int.parse(lastPathSegment.split('?').first));
}
break;
default:
SmartDialog.showToast('未匹配地址,请联系开发者');
Clipboard.setData(ClipboardData(text: value.toJson().toString()));
break;
}
} }
if (['http', 'https'].contains(scheme)) { if (['http', 'https'].contains(scheme)) {
fullPathPush(value); httpsScheme(value);
} }
} }
@ -148,7 +79,7 @@ class PiliSchame {
} }
} }
static Future<void> fullPathPush(Uri value) async { static Future<void> httpsScheme(Uri value) async {
// https://m.bilibili.com/bangumi/play/ss39708 // https://m.bilibili.com/bangumi/play/ss39708
// https | m.bilibili.com | /bangumi/play/ss39708 // https | m.bilibili.com | /bangumi/play/ss39708
// final String scheme = value.scheme!; // final String scheme = value.scheme!;
@ -281,6 +212,153 @@ class PiliSchame {
} }
} }
static Future<void> biliScheme(SchemeEntity value) async {
final String host = value.host!;
final String path = value.path!;
switch (host) {
case 'root':
Navigator.popUntil(
Get.context!, (Route<dynamic> route) => route.isFirst);
break;
case 'space':
final String mid = path.split('/').last;
Get.toNamed<dynamic>(
'/member?mid=$mid',
arguments: <String, dynamic>{'face': null},
);
break;
case 'video':
String pathQuery = path.split('/').last;
final numericRegex = RegExp(r'^[0-9]+$');
if (numericRegex.hasMatch(pathQuery)) {
pathQuery = 'AV$pathQuery';
}
Map map = IdUtils.matchAvorBv(input: pathQuery);
if (map.containsKey('AV')) {
_videoPush(map['AV'], null);
} else if (map.containsKey('BV')) {
_videoPush(null, map['BV']);
} else {
SmartDialog.showToast('投稿匹配失败');
}
break;
case 'live':
final String roomId = path.split('/').last;
Get.toNamed<dynamic>(
'/liveRoom?roomid=$roomId',
arguments: <String, String?>{'liveItem': null, 'heroTag': roomId},
);
break;
case 'bangumi':
if (path.startsWith('/season')) {
final String seasonId = path.split('/').last;
RoutePush.bangumiPush(int.parse(seasonId), null);
}
break;
case 'opus':
if (path.startsWith('/detail')) {
var opusId = path.split('/').last;
Get.toNamed('/opus', parameters: {
'title': '',
'id': opusId,
'articleType': 'opus',
});
}
break;
case 'search':
Get.toNamed('/searchResult', parameters: {'keyword': ''});
break;
case 'article':
final String id = path.split('/').last.split('?').first;
Get.toNamed(
'/read',
parameters: {
'title': 'cv$id',
'id': id,
'dynamicType': 'read',
},
);
break;
case 'pgc':
if (path.contains('ep')) {
final String lastPathSegment = path.split('/').last;
RoutePush.bangumiPush(
null, int.parse(lastPathSegment.split('?').first));
}
break;
case 'following':
if (path.startsWith('/detail')) {
var opusId = path.split('/').last;
Get.toNamed(
'/webview',
parameters: {
'url': 'https://m.bilibili.com/opus/$opusId',
'type': 'url',
'pageTitle': ''
},
);
}
break;
default:
SmartDialog.showToast('未匹配地址,请联系开发者');
Clipboard.setData(ClipboardData(text: value.toJson().toString()));
break;
}
}
static Future<void> piliScheme(Uri value) async {
final String host = value.host;
final String path = value.path;
final String arg = path.split('/').last;
switch (host) {
case 'home':
case 'root':
Get.toNamed('/');
break;
case 'member':
if (arg != '') {
final int? mid = int.tryParse(arg);
if (mid == null) {
SmartDialog.showToast('用户id有误');
return;
}
Get.toNamed<dynamic>(
'/member?mid=$mid',
arguments: <String, dynamic>{'face': null},
);
} else {
Get.toNamed('/mine');
}
break;
case 'search':
if (arg != '') {
final String encodedArg = Uri.decodeComponent(arg);
Get.toNamed('/searchResult', parameters: {'keyword': encodedArg});
} else {
Get.toNamed('/search');
}
break;
case 'setting':
Get.toNamed('/setting');
break;
case 'fav':
Get.toNamed('/fav');
break;
case 'history':
Get.toNamed('/history');
break;
case 'later':
Get.toNamed('/later');
break;
case 'msg':
Get.toNamed('/whisper');
break;
default:
Get.toNamed('/');
break;
}
}
static void _handleEpisodePath(String lastPathSegment, String redirectUrl) { static void _handleEpisodePath(String lastPathSegment, String redirectUrl) {
final String seasonId = _extractIdFromPath(lastPathSegment); final String seasonId = _extractIdFromPath(lastPathSegment);
RoutePush.bangumiPush(null, Utils.matchNum(seasonId).first); RoutePush.bangumiPush(null, Utils.matchNum(seasonId).first);

View File

@ -1,6 +1,7 @@
import 'dart:async'; import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
@ -50,13 +51,17 @@ class CacheManage {
Future<double> getTotalSizeOfFilesInDir(final FileSystemEntity file) async { Future<double> getTotalSizeOfFilesInDir(final FileSystemEntity file) async {
if (file is File) { if (file is File) {
int length = await file.length(); int length = await file.length();
return double.parse(length.toString()); return length.toDouble();
} }
if (file is Directory) { if (file is Directory) {
final List<FileSystemEntity> children = file.listSync();
double total = 0; double total = 0;
for (final FileSystemEntity child in children) { try {
total += await getTotalSizeOfFilesInDir(child); await for (final FileSystemEntity child in file.list()) {
total += await getTotalSizeOfFilesInDir(child);
}
} catch (e) {
// 处理错误,例如记录日志或显示错误消息
print('读取目录时出错: $e');
} }
return total; return total;
} }
@ -77,16 +82,17 @@ class CacheManage {
// 清除缓存 // 清除缓存
Future<bool> clearCacheAll() async { Future<bool> clearCacheAll() async {
bool cleanStatus = await SmartDialog.show( bool? cleanStatus = await showDialog<bool>(
useSystem: true, context: Get.context!,
animationType: SmartAnimationType.centerFade_otherSlide,
builder: (BuildContext context) { builder: (BuildContext context) {
return AlertDialog( return AlertDialog(
title: const Text('提示'), title: const Text('提示'),
content: const Text('该操作将清除图片及网络请求缓存数据,确认清除?'), content: const Text('该操作将清除图片及网络请求缓存数据,确认清除?'),
actions: [ actions: [
TextButton( TextButton(
onPressed: (() => {SmartDialog.dismiss()}), onPressed: () {
Navigator.of(context).pop(false);
},
child: Text( child: Text(
'取消', '取消',
style: TextStyle(color: Theme.of(context).colorScheme.outline), style: TextStyle(color: Theme.of(context).colorScheme.outline),
@ -94,40 +100,45 @@ class CacheManage {
), ),
TextButton( TextButton(
onPressed: () async { onPressed: () async {
SmartDialog.dismiss(); Navigator.of(context).pop(true);
SmartDialog.showLoading(msg: '正在清除...');
try {
// 清除缓存 图片缓存
await clearLibraryCache();
SmartDialog.dismiss().then((res) {
SmartDialog.showToast('清除完成');
});
} catch (err) {
SmartDialog.dismiss();
SmartDialog.showToast(err.toString());
}
}, },
child: const Text('确认'), child: const Text('确认'),
) ),
], ],
); );
}, },
).then((res) { );
return true; if (cleanStatus != null && cleanStatus) {
}); SmartDialog.showLoading(msg: '正在清除...');
return cleanStatus; try {
// 清除缓存 图片缓存
await clearLibraryCache();
SmartDialog.dismiss().then((res) {
SmartDialog.showToast('清除完成');
});
} catch (err) {
SmartDialog.dismiss();
SmartDialog.showToast(err.toString());
}
}
return cleanStatus!;
} }
/// 清除 Documents 目录下的 DioCache.db /// 清除 Documents 目录下的 DioCache.db
Future clearApplicationCache() async { Future<void> clearApplicationCache() async {
Directory directory = await getApplicationDocumentsDirectory(); try {
if (directory.existsSync()) { Directory directory = await getApplicationDocumentsDirectory();
String dioCacheFileName = if (directory.existsSync()) {
'${directory.path}${Platform.pathSeparator}DioCache.db'; String dioCacheFileName =
var dioCacheFile = File(dioCacheFileName); '${directory.path}${Platform.pathSeparator}DioCache.db';
if (dioCacheFile.existsSync()) { File dioCacheFile = File(dioCacheFileName);
dioCacheFile.delete(); if (await dioCacheFile.exists()) {
await dioCacheFile.delete();
}
} }
} catch (e) {
// 处理错误,例如记录日志或显示错误消息
print('清除缓存时出错: $e');
} }
} }

View File

@ -10,7 +10,6 @@ import 'package:hive/hive.dart';
import 'package:pilipala/http/user.dart'; import 'package:pilipala/http/user.dart';
import 'package:pilipala/pages/dynamics/index.dart'; import 'package:pilipala/pages/dynamics/index.dart';
import 'package:pilipala/pages/home/index.dart'; import 'package:pilipala/pages/home/index.dart';
import 'package:pilipala/pages/media/index.dart';
import 'package:pilipala/pages/mine/index.dart'; import 'package:pilipala/pages/mine/index.dart';
import 'package:pilipala/utils/cookie.dart'; import 'package:pilipala/utils/cookie.dart';
import 'package:pilipala/utils/storage.dart'; import 'package:pilipala/utils/storage.dart';
@ -31,9 +30,6 @@ class LoginUtils {
DynamicsController dynamicsCtr = Get.find<DynamicsController>(); DynamicsController dynamicsCtr = Get.find<DynamicsController>();
dynamicsCtr.userLogin.value = status; dynamicsCtr.userLogin.value = status;
MediaController mediaCtr = Get.find<MediaController>();
mediaCtr.userLogin.value = status;
} catch (err) { } catch (err) {
SmartDialog.showToast('refreshLoginStatus error: ${err.toString()}'); SmartDialog.showToast('refreshLoginStatus error: ${err.toString()}');
} }
@ -84,8 +80,6 @@ class LoginUtils {
final HomeController homeCtr = Get.find<HomeController>(); final HomeController homeCtr = Get.find<HomeController>();
homeCtr.updateLoginStatus(true); homeCtr.updateLoginStatus(true);
homeCtr.userFace.value = result['data'].face; homeCtr.userFace.value = result['data'].face;
final MediaController mediaCtr = Get.find<MediaController>();
mediaCtr.mid = result['data'].mid;
await LoginUtils.refreshLoginStatus(true); await LoginUtils.refreshLoginStatus(true);
} catch (err) { } catch (err) {
SmartDialog.show(builder: (BuildContext context) { SmartDialog.show(builder: (BuildContext context) {

View File

@ -7,7 +7,9 @@
#include "generated_plugin_registrant.h" #include "generated_plugin_registrant.h"
#include <dynamic_color/dynamic_color_plugin.h> #include <dynamic_color/dynamic_color_plugin.h>
#include <file_selector_linux/file_selector_plugin.h>
#include <flutter_volume_controller/flutter_volume_controller_plugin.h> #include <flutter_volume_controller/flutter_volume_controller_plugin.h>
#include <gtk/gtk_plugin.h>
#include <media_kit_libs_linux/media_kit_libs_linux_plugin.h> #include <media_kit_libs_linux/media_kit_libs_linux_plugin.h>
#include <media_kit_video/media_kit_video_plugin.h> #include <media_kit_video/media_kit_video_plugin.h>
#include <url_launcher_linux/url_launcher_plugin.h> #include <url_launcher_linux/url_launcher_plugin.h>
@ -16,9 +18,15 @@ void fl_register_plugins(FlPluginRegistry* registry) {
g_autoptr(FlPluginRegistrar) dynamic_color_registrar = g_autoptr(FlPluginRegistrar) dynamic_color_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "DynamicColorPlugin"); fl_plugin_registry_get_registrar_for_plugin(registry, "DynamicColorPlugin");
dynamic_color_plugin_register_with_registrar(dynamic_color_registrar); dynamic_color_plugin_register_with_registrar(dynamic_color_registrar);
g_autoptr(FlPluginRegistrar) file_selector_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FileSelectorPlugin");
file_selector_plugin_register_with_registrar(file_selector_linux_registrar);
g_autoptr(FlPluginRegistrar) flutter_volume_controller_registrar = g_autoptr(FlPluginRegistrar) flutter_volume_controller_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterVolumeControllerPlugin"); fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterVolumeControllerPlugin");
flutter_volume_controller_plugin_register_with_registrar(flutter_volume_controller_registrar); flutter_volume_controller_plugin_register_with_registrar(flutter_volume_controller_registrar);
g_autoptr(FlPluginRegistrar) gtk_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "GtkPlugin");
gtk_plugin_register_with_registrar(gtk_registrar);
g_autoptr(FlPluginRegistrar) media_kit_libs_linux_registrar = g_autoptr(FlPluginRegistrar) media_kit_libs_linux_registrar =
fl_plugin_registry_get_registrar_for_plugin(registry, "MediaKitLibsLinuxPlugin"); fl_plugin_registry_get_registrar_for_plugin(registry, "MediaKitLibsLinuxPlugin");
media_kit_libs_linux_plugin_register_with_registrar(media_kit_libs_linux_registrar); media_kit_libs_linux_plugin_register_with_registrar(media_kit_libs_linux_registrar);

View File

@ -4,7 +4,9 @@
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
dynamic_color dynamic_color
file_selector_linux
flutter_volume_controller flutter_volume_controller
gtk
media_kit_libs_linux media_kit_libs_linux
media_kit_video media_kit_video
url_launcher_linux url_launcher_linux

View File

@ -5,11 +5,13 @@
import FlutterMacOS import FlutterMacOS
import Foundation import Foundation
import app_links
import audio_service import audio_service
import audio_session import audio_session
import connectivity_plus import connectivity_plus
import device_info_plus import device_info_plus
import dynamic_color import dynamic_color
import file_selector_macos
import flutter_volume_controller import flutter_volume_controller
import media_kit_libs_macos_video import media_kit_libs_macos_video
import media_kit_video import media_kit_video
@ -22,11 +24,13 @@ import url_launcher_macos
import wakelock_plus import wakelock_plus
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
AppLinksMacosPlugin.register(with: registry.registrar(forPlugin: "AppLinksMacosPlugin"))
AudioServicePlugin.register(with: registry.registrar(forPlugin: "AudioServicePlugin")) AudioServicePlugin.register(with: registry.registrar(forPlugin: "AudioServicePlugin"))
AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin")) AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin"))
ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin")) ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin"))
DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin"))
DynamicColorPlugin.register(with: registry.registrar(forPlugin: "DynamicColorPlugin")) DynamicColorPlugin.register(with: registry.registrar(forPlugin: "DynamicColorPlugin"))
FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin"))
FlutterVolumeControllerPlugin.register(with: registry.registrar(forPlugin: "FlutterVolumeControllerPlugin")) FlutterVolumeControllerPlugin.register(with: registry.registrar(forPlugin: "FlutterVolumeControllerPlugin"))
MediaKitLibsMacosVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitLibsMacosVideoPlugin")) MediaKitLibsMacosVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitLibsMacosVideoPlugin"))
MediaKitVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitVideoPlugin")) MediaKitVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitVideoPlugin"))

View File

@ -17,6 +17,38 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "6.2.0" version: "6.2.0"
app_links:
dependency: "direct main"
description:
name: app_links
sha256: ad1a6d598e7e39b46a34f746f9a8b011ee147e4c275d407fa457e7a62f84dd99
url: "https://pub.flutter-io.cn"
source: hosted
version: "6.3.2"
app_links_linux:
dependency: transitive
description:
name: app_links_linux
sha256: f5f7173a78609f3dfd4c2ff2c95bd559ab43c80a87dc6a095921d96c05688c81
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.0.3"
app_links_platform_interface:
dependency: transitive
description:
name: app_links_platform_interface
sha256: "05f5379577c513b534a29ddea68176a4d4802c46180ee8e2e966257158772a3f"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.0.2"
app_links_web:
dependency: transitive
description:
name: app_links_web
sha256: af060ed76183f9e2b87510a9480e56a5352b6c249778d07bd2c95fc35632a555
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.0.4"
appscheme: appscheme:
dependency: "direct main" dependency: "direct main"
description: description:
@ -497,6 +529,38 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "7.0.0" version: "7.0.0"
file_selector_linux:
dependency: transitive
description:
name: file_selector_linux
sha256: "045d372bf19b02aeb69cacf8b4009555fb5f6f0b7ad8016e5f46dd1387ddd492"
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.9.2+1"
file_selector_macos:
dependency: transitive
description:
name: file_selector_macos
sha256: cb284e267f8e2a45a904b5c094d2ba51d0aabfc20b1538ab786d9ef7dc2bf75c
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.9.4+1"
file_selector_platform_interface:
dependency: transitive
description:
name: file_selector_platform_interface
sha256: a3994c26f10378a039faa11de174d7b78eb8f79e4dd0af2a451410c1a5c3f66b
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.6.2"
file_selector_windows:
dependency: transitive
description:
name: file_selector_windows
sha256: "2ad726953f6e8affbc4df8dc78b77c3b4a060967a291e528ef72ae846c60fb69"
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.9.3+2"
fixnum: fixnum:
dependency: transitive dependency: transitive
description: description:
@ -670,6 +734,14 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "0.1.0" version: "0.1.0"
gtk:
dependency: transitive
description:
name: gtk
sha256: e8ce9ca4b1df106e4d72dad201d345ea1a036cc12c360f1a7d5a758f78ffa42c
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.1.0"
hive: hive:
dependency: "direct main" dependency: "direct main"
description: description:
@ -742,6 +814,70 @@ packages:
url: "https://pub.flutter-io.cn" url: "https://pub.flutter-io.cn"
source: hosted source: hosted
version: "4.1.3" version: "4.1.3"
image_picker:
dependency: "direct main"
description:
name: image_picker
sha256: "021834d9c0c3de46bf0fe40341fa07168407f694d9b2bb18d532dc1261867f7a"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.1.2"
image_picker_android:
dependency: transitive
description:
name: image_picker_android
sha256: "0f57fee1e8bfadf8cc41818bbcd7f72e53bb768a54d9496355d5e8a5681a19f1"
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.8.12+1"
image_picker_for_web:
dependency: transitive
description:
name: image_picker_for_web
sha256: "65d94623e15372c5c51bebbcb820848d7bcb323836e12dfdba60b5d3a8b39e50"
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.0.5"
image_picker_ios:
dependency: transitive
description:
name: image_picker_ios
sha256: "6703696ad49f5c3c8356d576d7ace84d1faf459afb07accbb0fae780753ff447"
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.8.12"
image_picker_linux:
dependency: transitive
description:
name: image_picker_linux
sha256: "4ed1d9bb36f7cd60aa6e6cd479779cc56a4cb4e4de8f49d487b1aaad831300fa"
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.2.1+1"
image_picker_macos:
dependency: transitive
description:
name: image_picker_macos
sha256: "3f5ad1e8112a9a6111c46d0b57a7be2286a9a07fc6e1976fdf5be2bd31d4ff62"
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.2.1+1"
image_picker_platform_interface:
dependency: transitive
description:
name: image_picker_platform_interface
sha256: "9ec26d410ff46f483c5519c29c02ef0e02e13a543f882b152d4bfd2f06802f80"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.10.0"
image_picker_windows:
dependency: transitive
description:
name: image_picker_windows
sha256: "6ad07afc4eb1bc25f3a01084d28520496c4a3bb0cb13685435838167c9dcedeb"
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.2.1+1"
intl: intl:
dependency: transitive dependency: transitive
description: description:

View File

@ -112,6 +112,7 @@ dependencies:
flutter_displaymode: ^0.6.0 flutter_displaymode: ^0.6.0
# scheme跳转 # scheme跳转
appscheme: ^1.0.8 appscheme: ^1.0.8
app_links: ^6.3.2
# 弹幕 # 弹幕
ns_danmaku: ns_danmaku:
git: git:
@ -150,6 +151,8 @@ dependencies:
brotli: ^0.6.0 brotli: ^0.6.0
# 文本语法高亮 # 文本语法高亮
re_highlight: ^0.0.3 re_highlight: ^0.0.3
# 图片选择器
image_picker: ^1.1.2
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:

View File

@ -6,8 +6,10 @@
#include "generated_plugin_registrant.h" #include "generated_plugin_registrant.h"
#include <app_links/app_links_plugin_c_api.h>
#include <connectivity_plus/connectivity_plus_windows_plugin.h> #include <connectivity_plus/connectivity_plus_windows_plugin.h>
#include <dynamic_color/dynamic_color_plugin_c_api.h> #include <dynamic_color/dynamic_color_plugin_c_api.h>
#include <file_selector_windows/file_selector_windows.h>
#include <flutter_volume_controller/flutter_volume_controller_plugin_c_api.h> #include <flutter_volume_controller/flutter_volume_controller_plugin_c_api.h>
#include <media_kit_libs_windows_video/media_kit_libs_windows_video_plugin_c_api.h> #include <media_kit_libs_windows_video/media_kit_libs_windows_video_plugin_c_api.h>
#include <media_kit_video/media_kit_video_plugin_c_api.h> #include <media_kit_video/media_kit_video_plugin_c_api.h>
@ -17,10 +19,14 @@
#include <url_launcher_windows/url_launcher_windows.h> #include <url_launcher_windows/url_launcher_windows.h>
void RegisterPlugins(flutter::PluginRegistry* registry) { void RegisterPlugins(flutter::PluginRegistry* registry) {
AppLinksPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("AppLinksPluginCApi"));
ConnectivityPlusWindowsPluginRegisterWithRegistrar( ConnectivityPlusWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin")); registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin"));
DynamicColorPluginCApiRegisterWithRegistrar( DynamicColorPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("DynamicColorPluginCApi")); registry->GetRegistrarForPlugin("DynamicColorPluginCApi"));
FileSelectorWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FileSelectorWindows"));
FlutterVolumeControllerPluginCApiRegisterWithRegistrar( FlutterVolumeControllerPluginCApiRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FlutterVolumeControllerPluginCApi")); registry->GetRegistrarForPlugin("FlutterVolumeControllerPluginCApi"));
MediaKitLibsWindowsVideoPluginCApiRegisterWithRegistrar( MediaKitLibsWindowsVideoPluginCApiRegisterWithRegistrar(

View File

@ -3,8 +3,10 @@
# #
list(APPEND FLUTTER_PLUGIN_LIST list(APPEND FLUTTER_PLUGIN_LIST
app_links
connectivity_plus connectivity_plus
dynamic_color dynamic_color
file_selector_windows
flutter_volume_controller flutter_volume_controller
media_kit_libs_windows_video media_kit_libs_windows_video
media_kit_video media_kit_video