From 8f6fe042cd23f167950c99b7434272684ec3082f Mon Sep 17 00:00:00 2001 From: guozhigq Date: Wed, 18 Sep 2024 00:16:02 +0800 Subject: [PATCH 01/90] =?UTF-8?q?feat:=20=E6=88=91=E7=9A=84&=E5=AA=92?= =?UTF-8?q?=E4=BD=93=E9=A1=B5=E9=9D=A2=E5=90=88=E5=B9=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/models/common/nav_bar_config.dart | 13 +- lib/pages/home/view.dart | 21 +- lib/pages/home/widgets/app_bar.dart | 23 +- lib/pages/main/view.dart | 9 +- lib/pages/media/controller.dart | 65 -- lib/pages/media/index.dart | 4 - lib/pages/media/view.dart | 296 ------- lib/pages/mine/controller.dart | 66 +- lib/pages/mine/view.dart | 740 +++++++++++------- .../setting/pages/navigation_bar_set.dart | 3 +- lib/pages/webview/controller.dart | 1 - lib/router/app_pages.dart | 6 +- lib/utils/login.dart | 6 - 13 files changed, 524 insertions(+), 729 deletions(-) delete mode 100644 lib/pages/media/controller.dart delete mode 100644 lib/pages/media/index.dart delete mode 100644 lib/pages/media/view.dart diff --git a/lib/models/common/nav_bar_config.dart b/lib/models/common/nav_bar_config.dart index 64cebafb..1c3a3438 100644 --- a/lib/models/common/nav_bar_config.dart +++ b/lib/models/common/nav_bar_config.dart @@ -1,8 +1,7 @@ import 'package:flutter/material.dart'; - +import 'package:pilipala/pages/mine/index.dart'; import '../../pages/dynamics/index.dart'; import '../../pages/home/index.dart'; -import '../../pages/media/index.dart'; import '../../pages/rank/index.dart'; List defaultNavigationBars = [ @@ -51,15 +50,15 @@ List defaultNavigationBars = [ { 'id': 3, 'icon': const Icon( - Icons.video_collection_outlined, + Icons.person_outline, size: 20, ), 'selectIcon': const Icon( - Icons.video_collection, + Icons.person, size: 21, ), - 'label': "媒体库", + 'label': "我的", 'count': 0, - 'page': const MediaPage(), - } + 'page': const MinePage(), + }, ]; diff --git a/lib/pages/home/view.dart b/lib/pages/home/view.dart index 187ed6c3..11d36c9b 100644 --- a/lib/pages/home/view.dart +++ b/lib/pages/home/view.dart @@ -5,7 +5,7 @@ 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/pages/mine/index.dart'; +import 'package:pilipala/pages/main/index.dart'; import 'package:pilipala/utils/feed_back.dart'; import './controller.dart'; @@ -19,6 +19,8 @@ class HomePage extends StatefulWidget { class _HomePageState extends State with AutomaticKeepAliveClientMixin, TickerProviderStateMixin { final HomeController _homeController = Get.put(HomeController()); + final MainController mainController = Get.put(MainController()); + List videoList = []; late Stream stream; @@ -33,15 +35,14 @@ class _HomePageState extends State showUserBottomSheet() { feedBack(); - showModalBottomSheet( - context: context, - builder: (_) => const SizedBox( - height: 450, - child: MinePage(), - ), - clipBehavior: Clip.hardEdge, - isScrollControlled: true, - ); + final MainController mainController = Get.put(MainController()); + if (mainController.navigationBars + .where((item) => item['label'] == "我的") + .isNotEmpty) { + mainController.pageController.jumpToPage(2); + } else { + Get.toNamed('/mine'); + } } @override diff --git a/lib/pages/home/widgets/app_bar.dart b/lib/pages/home/widgets/app_bar.dart index 36920aef..35cb9d4e 100644 --- a/lib/pages/home/widgets/app_bar.dart +++ b/lib/pages/home/widgets/app_bar.dart @@ -3,7 +3,6 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:hive/hive.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart'; -import 'package:pilipala/pages/mine/view.dart'; import 'package:pilipala/utils/storage.dart'; Box userInfoCache = GStrorage.userInfo; @@ -54,19 +53,9 @@ class HomeAppBar extends StatelessWidget { // icon: const Icon(CupertinoIcons.bell, size: 22), // ), const SizedBox(width: 6), - - /// TODO if (userInfo != null) ...[ GestureDetector( - onTap: () => showModalBottomSheet( - context: context, - builder: (_) => const SizedBox( - height: 450, - child: MinePage(), - ), - clipBehavior: Clip.hardEdge, - isScrollControlled: true, - ), + onTap: () => Get.toNamed('/mine'), child: NetworkImgLayer( type: 'avatar', width: 32, @@ -77,15 +66,7 @@ class HomeAppBar extends StatelessWidget { const SizedBox(width: 10), ] else ...[ IconButton( - onPressed: () => showModalBottomSheet( - context: context, - builder: (_) => const SizedBox( - height: 450, - child: MinePage(), - ), - clipBehavior: Clip.hardEdge, - isScrollControlled: true, - ), + onPressed: () => Get.toNamed('/mine'), icon: const Icon(CupertinoIcons.person, size: 22), ), ], diff --git a/lib/pages/main/view.dart b/lib/pages/main/view.dart index 3da667e8..825e65bc 100644 --- a/lib/pages/main/view.dart +++ b/lib/pages/main/view.dart @@ -6,7 +6,7 @@ import 'package:hive/hive.dart'; import 'package:pilipala/models/common/dynamic_badge_mode.dart'; import 'package:pilipala/pages/dynamics/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/utils/event_bus.dart'; import 'package:pilipala/utils/feed_back.dart'; @@ -25,7 +25,7 @@ class _MainAppState extends State with SingleTickerProviderStateMixin { final HomeController _homeController = Get.put(HomeController()); final RankController _rankController = Get.put(RankController()); final DynamicsController _dynamicController = Get.put(DynamicsController()); - final MediaController _mediaController = Get.put(MediaController()); + final MineController _mineController = Get.put(MineController()); int? _lastSelectTime; //上次点击时间 Box setting = GStrorage.setting; @@ -90,8 +90,9 @@ class _MainAppState extends State with SingleTickerProviderStateMixin { _dynamicController.flag = false; } - if (currentPage is MediaPage) { - _mediaController.queryFavFolder(); + if (currentPage is MinePage) { + _mineController.queryFavFolder(); + _mineController.queryUserInfo(); } } diff --git a/lib/pages/media/controller.dart b/lib/pages/media/controller.dart deleted file mode 100644 index 757d5ac9..00000000 --- a/lib/pages/media/controller.dart +++ /dev/null @@ -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().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 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; - } -} diff --git a/lib/pages/media/index.dart b/lib/pages/media/index.dart deleted file mode 100644 index 8fae4891..00000000 --- a/lib/pages/media/index.dart +++ /dev/null @@ -1,4 +0,0 @@ -library media; - -export './controller.dart'; -export './view.dart'; diff --git a/lib/pages/media/view.dart b/lib/pages/media/view.dart deleted file mode 100644 index 965628c4..00000000 --- a/lib/pages/media/view.dart +++ /dev/null @@ -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 createState() => _MediaPageState(); -} - -class _MediaPageState extends State - 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), - ) - ], - ), - ), - ); - } -} diff --git a/lib/pages/mine/controller.dart b/lib/pages/mine/controller.dart index e682a7e5..24401711 100644 --- a/lib/pages/mine/controller.dart +++ b/lib/pages/mine/controller.dart @@ -4,22 +4,45 @@ import 'package:get/get.dart'; import 'package:hive/hive.dart'; import 'package:pilipala/http/user.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/stat.dart'; import 'package:pilipala/utils/storage.dart'; class MineController extends GetxController { - // 用户信息 头像、昵称、lv - Rx userInfo = UserInfoData().obs; + RxBool userLogin = false.obs; // 用户状态 动态、关注、粉丝 Rx userStat = UserStat().obs; - RxBool userLogin = false.obs; - Box userInfoCache = GStrorage.userInfo; - Box setting = GStrorage.setting; + // 用户信息 头像、昵称、lv + Rx userInfo = UserInfoData().obs; Rx themeType = ThemeType.system.obs; - + Rx 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 - onInit() { + void onInit() { super.onInit(); if (userInfoCache.get('userInfoCache') != null) { @@ -109,7 +132,10 @@ class MineController extends GetxController { SmartDialog.showToast('账号未登录'); return; } - Get.toNamed('/follow?mid=${userInfo.value.mid}', preventDuplicates: false); + Get.toNamed( + '/follow?mid=${userInfo.value.mid}', + preventDuplicates: false, + ); } pushFans() { @@ -117,7 +143,10 @@ class MineController extends GetxController { SmartDialog.showToast('账号未登录'); return; } - Get.toNamed('/fan?mid=${userInfo.value.mid}', preventDuplicates: false); + Get.toNamed( + '/fan?mid=${userInfo.value.mid}', + preventDuplicates: false, + ); } pushDynamic() { @@ -125,7 +154,22 @@ class MineController extends GetxController { SmartDialog.showToast('账号未登录'); return; } - Get.toNamed('/memberDynamics?mid=${userInfo.value.mid}', - preventDuplicates: false); + Get.toNamed( + '/memberDynamics?mid=${userInfo.value.mid}', + preventDuplicates: false, + ); + } + + Future 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; } } diff --git a/lib/pages/mine/view.dart b/lib/pages/mine/view.dart index 091b2149..757fbbc5 100644 --- a/lib/pages/mine/view.dart +++ b/lib/pages/mine/view.dart @@ -1,12 +1,12 @@ -// ignore_for_file: no_leading_underscores_for_local_identifiers - -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:pilipala/common/constants.dart'; import 'package:pilipala/common/widgets/network_img_layer.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/stat.dart'; +import 'package:pilipala/utils/utils.dart'; import 'controller.dart'; class MinePage extends StatefulWidget { @@ -16,19 +16,23 @@ class MinePage extends StatefulWidget { State createState() => _MinePageState(); } -class _MinePageState extends State { - final MineController mineController = Get.put(MineController()); +class _MinePageState extends State + with AutomaticKeepAliveClientMixin { + final MineController ctr = Get.put(MineController()); late Future _futureBuilderFuture; + @override + bool get wantKeepAlive => true; + @override void initState() { super.initState(); - _futureBuilderFuture = mineController.queryUserInfo(); - - mineController.userLogin.listen((status) { + _futureBuilderFuture = ctr.queryUserInfo(); + ctr.queryFavFolder(); + ctr.userLogin.listen((status) { if (mounted) { setState(() { - _futureBuilderFuture = mineController.queryUserInfo(); + _futureBuilderFuture = ctr.queryUserInfo(); }); } }); @@ -36,71 +40,181 @@ class _MinePageState extends State { @override Widget build(BuildContext context) { + super.build(context); return Scaffold( appBar: AppBar( - automaticallyImplyLeading: false, - scrolledUnderElevation: 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: [ IconButton( - onPressed: () => mineController.onChangeTheme(), - icon: Icon( - mineController.themeType.value == ThemeType.dark - ? CupertinoIcons.sun_max - : CupertinoIcons.moon, - size: 22, - ), + icon: const Icon(Icons.dark_mode_outlined), + onPressed: () => ctr.onChangeTheme(), ), IconButton( + icon: const Icon(Icons.settings_outlined), onPressed: () => Get.toNamed('/setting', preventDuplicates: false), - icon: const Icon( - CupertinoIcons.slider_horizontal_3, - ), ), - const SizedBox(width: 10), + const SizedBox(width: 22), ], ), - body: LayoutBuilder( - builder: (context, constraint) { - return SingleChildScrollView( - physics: const NeverScrollableScrollPhysics(), - child: SizedBox( - height: constraint.maxHeight, - child: Column( - children: [ - const SizedBox(height: 10), - FutureBuilder( - future: _futureBuilderFuture, - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.done) { - if (snapshot.data == null) { - return const SizedBox(); - } - if (snapshot.data['status']) { - return Obx( - () => userInfoBuild(mineController, context)); - } else { - return userInfoBuild(mineController, context); - } - } else { - return userInfoBuild(mineController, context); - } - }, + body: SingleChildScrollView( + child: Column( + children: [ + Obx(() => _buildProfileSection(context, ctr.userInfo.value)), + const SizedBox(height: 10), + FutureBuilder( + future: _futureBuilderFuture, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + if (snapshot.data == null) { + return const SizedBox(); + } + if (snapshot.data['status']) { + return Obx( + () => _buildStatsSection( + context, + ctr.userStat.value, + ), + ); + } else { + return _buildStatsSection( + context, + ctr.userStat.value, + ); + } + } else { + return _buildStatsSection( + context, + ctr.userStat.value, + ); + } + }, + ), + _buildMenuSection(context), + 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( + '硬币: ${userInfo.money ?? '-'}', + style: TextStyle( + color: Theme.of(context).colorScheme.outline, + ), + ), + ], + ), + 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: [ + _buildStatItem( + context, + (userStat.dynamicCount ?? '-').toString(), + '动态', + ctr.pushDynamic, + ), + _buildStatItem( + context, + (userStat.following ?? '-').toString(), + '关注', + ctr.pushFollow, + ), + _buildStatItem( + context, + (userStat.follower ?? '-').toString(), + '粉丝', + ctr.pushFans, + ), + ], ), ); }, @@ -108,258 +222,286 @@ class _MinePageState extends State { ); } - Widget userInfoBuild(_mineController, context) { - return Column( - children: [ - const SizedBox(height: 5), - GestureDetector( - onTap: () => _mineController.onLogin(), - child: ClipOval( - child: Container( - width: 85, - height: 85, - color: Theme.of(context).colorScheme.onInverseSurface, - child: Center( - child: _mineController.userInfo.value.face != null - ? NetworkImgLayer( - src: _mineController.userInfo.value.face, - width: 85, - height: 85) - : Image.asset('assets/images/noface.jpeg'), - ), - ), - ), - ), - 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, - ), - ), - ), - ], + Widget _buildStatItem( + BuildContext context, + String count, + String label, + Function onTap, + ) { + TextStyle style = TextStyle( + fontSize: Theme.of(context).textTheme.titleMedium!.fontSize, + fontWeight: FontWeight.bold); + return InkWell( + onTap: () => onTap(), + // onTap: () {}, + borderRadius: StyleString.mdRadius, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text(count, style: style), + const SizedBox(height: 2), + Text( + label, + style: Theme.of(context).textTheme.labelMedium!.copyWith( + color: Theme.of(context).colorScheme.outline, ), - ); - }, ), ], - const SizedBox(height: 30), - Padding( - padding: const EdgeInsets.only(left: 12, right: 12), - child: LayoutBuilder( - builder: (context, constraints) { - TextStyle style = TextStyle( - fontSize: Theme.of(context).textTheme.titleMedium!.fontSize, - color: Theme.of(context).colorScheme.primary, - fontWeight: FontWeight.bold); - return SizedBox( - height: constraints.maxWidth / 3 * 0.6, - child: GridView.count( - primary: false, - padding: const EdgeInsets.all(0), - crossAxisCount: 3, - childAspectRatio: 1.67, - children: [ - InkWell( - onTap: () => _mineController.pushDynamic(), - borderRadius: StyleString.mdRadius, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - AnimatedSwitcher( - duration: const Duration(milliseconds: 400), - transitionBuilder: - (Widget child, Animation animation) { - return ScaleTransition( - scale: animation, child: child); - }, - child: Text( - (_mineController.userStat.value.dynamicCount ?? - '-') - .toString(), - key: ValueKey(_mineController - .userStat.value.dynamicCount - .toString()), - style: style), + ), + ); + } + + Widget _buildMenuSection(BuildContext context) { + return Padding( + padding: const EdgeInsets.only(left: 12, right: 12), + child: LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + return SizedBox( + height: constraints.maxWidth / 4 * 0.85, + child: GridView.count( + primary: false, + crossAxisCount: 4, + padding: const EdgeInsets.all(0), + childAspectRatio: 1.2, + children: [ + ...ctr.menuList.map((element) { + return InkWell( + onTap: () { + if (!ctr.userLogin.value) { + SmartDialog.showToast('账号未登录'); + } else { + element['onTap'](); + } + }, + borderRadius: StyleString.mdRadius, + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Padding( + padding: const EdgeInsets.all(10), + child: Icon( + element['icon'], + size: 21, + color: Theme.of(context).colorScheme.primary, ), - const SizedBox(height: 8), - Text( - '动态', - style: Theme.of(context).textTheme.labelMedium, - ), - ], - ), + ), + const SizedBox(height: 4), + Text(element['title']) + ], ), - InkWell( - onTap: () => _mineController.pushFollow(), - borderRadius: StyleString.mdRadius, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - AnimatedSwitcher( - duration: const Duration(milliseconds: 400), - transitionBuilder: - (Widget child, Animation animation) { - return ScaleTransition( - scale: animation, child: child); - }, - child: Text( - (_mineController.userStat.value.following ?? - '-') - .toString(), - key: ValueKey(_mineController - .userStat.value.following - .toString()), - style: style), - ), - const SizedBox(height: 8), - Text( - '关注', - style: Theme.of(context).textTheme.labelMedium, - ), - ], - ), + ); + }).toList(), + ], + ), + ); + }, + ), + ); + } + + Widget _buildFavoritesSection(context) { + return Column( + children: [ + _buildFavoritesTitle(context, 'fav', '收藏夹'), + const SizedBox(height: 4), + SizedBox( + width: double.infinity, + height: MediaQuery.textScalerOf(context).scale(110), + child: FutureBuilder( + future: ctr.queryFavFolder(), + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + Map data = snapshot.data as Map; + if (data['status']) { + List favFolderList = ctr.favFolderData.value.list!; + 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(), - borderRadius: StyleString.mdRadius, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - AnimatedSwitcher( - duration: const Duration(milliseconds: 400), - transitionBuilder: - (Widget child, Animation animation) { - return ScaleTransition( - scale: animation, child: child); - }, - child: Text( - (_mineController.userStat.value.follower ?? '-') - .toString(), - key: ValueKey(_mineController - .userStat.value.follower - .toString()), - style: style), - ), - const SizedBox(height: 8), - Text( - '粉丝', - style: Theme.of(context).textTheme.labelMedium, - ), - ], - ), - ), - ], - ), - ); + ); + } else { + return SizedBox( + height: 110, + child: Center(child: Text(data['msg'])), + ); + } + } else { + // 骨架屏 + return Obx( + () => ListView.builder( + scrollDirection: Axis.horizontal, + itemCount: ctr.favFolderData.value.list!.length, + itemBuilder: (context, index) { + return FavFolderItem( + item: ctr.favFolderData.value.list![index], + index: index, + ); + }, + ), + ); + } }, ), ), ], ); } + + Widget _buildFavoritesTitle( + BuildContext context, + String type, + String title, + ) { + return ListTile( + onTap: () {}, + 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 { - final Icon? icon; - final Function? onTap; - final String? text; - - const ActionItem({ - Key? key, - this.icon, - this.onTap, - this.text, - }) : super(key: key); - +class FavFolderItem extends StatelessWidget { + const FavFolderItem({super.key, this.item, this.index}); + final FavFolderItemData? item; + final int? index; @override Widget build(BuildContext context) { - return InkWell( - onTap: () {}, - borderRadius: StyleString.mdRadius, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(icon!.icon!), - const SizedBox(height: 8), - Text( - text!, - style: Theme.of(context).textTheme.labelMedium, - ), - ], + String heroTag = Utils.makeHeroTag(item!.fid); + return Container( + margin: EdgeInsets.only(left: index == 0 ? 20 : 0, right: 14), + child: InkWell( + onTap: () => Get.toNamed('/favDetail', arguments: item, parameters: { + 'mediaId': item!.id.toString(), + 'heroTag': heroTag, + 'isOwner': '1', + }), + borderRadius: StyleString.mdRadius, + child: Stack( + children: [ + NetworkImgLayer( + src: item!.cover, + width: 175, + height: 110, + ), + // 渐变 + Positioned( + left: 0, + right: 0, + top: 60, + bottom: 0, + child: Container( + padding: const EdgeInsets.only(bottom: 8, left: 10, right: 2), + decoration: BoxDecoration( + borderRadius: StyleString.mdRadius, + gradient: LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Colors.black.withOpacity(0), + Colors.black.withOpacity(0.6), + ], + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.end, + children: [ + RichText( + maxLines: 1, + overflow: TextOverflow.ellipsis, + text: TextSpan( + style: Theme.of(context) + .textTheme + .titleSmall! + .copyWith(color: Colors.white), + children: [ + TextSpan(text: item!.title!), + const TextSpan(text: ' '), + if (item!.mediaCount! > 0) + TextSpan( + text: item!.mediaCount!.toString(), + style: const TextStyle( + fontSize: 11, + fontWeight: FontWeight.bold, + ), + ), + ], + ), + ), + ], + ), + ), + ), + ], + ), ), ); } diff --git a/lib/pages/setting/pages/navigation_bar_set.dart b/lib/pages/setting/pages/navigation_bar_set.dart index 09e92bc3..828500db 100644 --- a/lib/pages/setting/pages/navigation_bar_set.dart +++ b/lib/pages/setting/pages/navigation_bar_set.dart @@ -1,7 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:hive/hive.dart'; -import 'package:pilipala/models/common/tab_type.dart'; import 'package:pilipala/utils/storage.dart'; import '../../../models/common/nav_bar_config.dart'; @@ -23,7 +22,7 @@ class _NavigationbarSetPageState extends State { super.initState(); defaultNavTabs = defaultNavigationBars; navBarSort = settingStorage - .get(SettingBoxKey.navBarSort, defaultValue: [0, 1, 2, 3]); + .get(SettingBoxKey.navBarSort, defaultValue: [0, 1, 2, 3, 4]); // 对 tabData 进行排序 defaultNavTabs.sort((a, b) { int indexA = navBarSort.indexOf(a['id']); diff --git a/lib/pages/webview/controller.dart b/lib/pages/webview/controller.dart index e0ff113c..81105ce1 100644 --- a/lib/pages/webview/controller.dart +++ b/lib/pages/webview/controller.dart @@ -8,7 +8,6 @@ import 'package:hive/hive.dart'; import 'package:pilipala/http/init.dart'; import 'package:pilipala/http/user.dart'; import 'package:pilipala/pages/home/index.dart'; -import 'package:pilipala/pages/media/index.dart'; import 'package:pilipala/utils/cookie.dart'; import 'package:pilipala/utils/event_bus.dart'; import 'package:pilipala/utils/id_utils.dart'; diff --git a/lib/router/app_pages.dart b/lib/router/app_pages.dart index 7840c126..2f7dc782 100644 --- a/lib/router/app_pages.dart +++ b/lib/router/app_pages.dart @@ -8,6 +8,7 @@ import 'package:pilipala/pages/message/at/index.dart'; import 'package:pilipala/pages/message/like/index.dart'; import 'package:pilipala/pages/message/reply/index.dart'; import 'package:pilipala/pages/message/system/index.dart'; +import 'package:pilipala/pages/mine/index.dart'; import 'package:pilipala/pages/setting/pages/logs.dart'; import '../pages/about/index.dart'; @@ -27,7 +28,6 @@ import '../pages/html/index.dart'; import '../pages/later/index.dart'; import '../pages/live_room/view.dart'; import '../pages/login/index.dart'; -import '../pages/media/index.dart'; import '../pages/member/index.dart'; import '../pages/member_archive/index.dart'; import '../pages/member_coin/index.dart'; @@ -75,8 +75,6 @@ class Routes { // 设置 CustomGetPage(name: '/setting', page: () => const SettingPage()), // - CustomGetPage(name: '/media', page: () => const MediaPage()), - // CustomGetPage(name: '/fav', page: () => const FavPage()), // CustomGetPage(name: '/favDetail', page: () => const FavDetailPage()), @@ -183,6 +181,8 @@ class Routes { // 系统通知 CustomGetPage( name: '/messageSystem', page: () => const MessageSystemPage()), + // 我的 + CustomGetPage(name: '/mine', page: () => const MinePage()), ]; } diff --git a/lib/utils/login.dart b/lib/utils/login.dart index 2687a8c2..07afd2dc 100644 --- a/lib/utils/login.dart +++ b/lib/utils/login.dart @@ -10,7 +10,6 @@ import 'package:hive/hive.dart'; import 'package:pilipala/http/user.dart'; import 'package:pilipala/pages/dynamics/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/utils/cookie.dart'; import 'package:pilipala/utils/storage.dart'; @@ -31,9 +30,6 @@ class LoginUtils { DynamicsController dynamicsCtr = Get.find(); dynamicsCtr.userLogin.value = status; - - MediaController mediaCtr = Get.find(); - mediaCtr.userLogin.value = status; } catch (err) { SmartDialog.showToast('refreshLoginStatus error: ${err.toString()}'); } @@ -84,8 +80,6 @@ class LoginUtils { final HomeController homeCtr = Get.find(); homeCtr.updateLoginStatus(true); homeCtr.userFace.value = result['data'].face; - final MediaController mediaCtr = Get.find(); - mediaCtr.mid = result['data'].mid; await LoginUtils.refreshLoginStatus(true); } catch (err) { SmartDialog.show(builder: (BuildContext context) { From 476fef887738bcc9925ad9d46859bc8931a3660a Mon Sep 17 00:00:00 2001 From: guozhigq Date: Wed, 18 Sep 2024 22:17:06 +0800 Subject: [PATCH 02/90] mod --- lib/pages/mine/view.dart | 24 +++++++++++++----------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/lib/pages/mine/view.dart b/lib/pages/mine/view.dart index 757fbbc5..4246e43e 100644 --- a/lib/pages/mine/view.dart +++ b/lib/pages/mine/view.dart @@ -366,16 +366,18 @@ class _MinePageState extends State } else { // 骨架屏 return Obx( - () => ListView.builder( - scrollDirection: Axis.horizontal, - itemCount: ctr.favFolderData.value.list!.length, - itemBuilder: (context, index) { - return FavFolderItem( - item: ctr.favFolderData.value.list![index], - index: index, - ); - }, - ), + () => ctr.favFolderData.value.list != null + ? ListView.builder( + scrollDirection: Axis.horizontal, + itemCount: ctr.favFolderData.value.list!.length, + itemBuilder: (context, index) { + return FavFolderItem( + item: ctr.favFolderData.value.list![index], + index: index, + ); + }, + ) + : const SizedBox(), ); } }, @@ -391,7 +393,7 @@ class _MinePageState extends State String title, ) { return ListTile( - onTap: () {}, + onTap: () => Get.toNamed('/fav'), leading: null, dense: true, title: Padding( From 21ba50c7a9fe5e125bc85936d601f6a74069bbeb Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sat, 21 Sep 2024 16:11:55 +0800 Subject: [PATCH 03/90] =?UTF-8?q?mod:=20=E5=A2=9E=E5=8A=A0=E6=90=9C?= =?UTF-8?q?=E7=B4=A2=E9=A1=B5=E9=9D=A2=E5=85=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/mine/view.dart | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/pages/mine/view.dart b/lib/pages/mine/view.dart index 4246e43e..90fa4958 100644 --- a/lib/pages/mine/view.dart +++ b/lib/pages/mine/view.dart @@ -3,6 +3,7 @@ import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:pilipala/common/constants.dart'; import 'package:pilipala/common/widgets/network_img_layer.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/stat.dart'; @@ -45,7 +46,15 @@ class _MinePageState extends State appBar: AppBar( actions: [ IconButton( - icon: const Icon(Icons.dark_mode_outlined), + icon: const Icon(Icons.search_outlined), + onPressed: () => Get.toNamed('/search'), + ), + IconButton( + icon: Icon( + ctr.themeType.value == ThemeType.dark + ? Icons.wb_sunny_outlined + : Icons.dark_mode_outlined, + ), onPressed: () => ctr.onChangeTheme(), ), IconButton( From dd477247776839e04e09862257743ec506c25219 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Tue, 1 Oct 2024 18:48:01 +0800 Subject: [PATCH 04/90] =?UTF-8?q?feat:=20navBar=E5=B1=95=E7=A4=BA=E5=A4=B4?= =?UTF-8?q?=E5=83=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/main/controller.dart | 12 +++++++++++- lib/pages/main/view.dart | 29 +++++++++++++++-------------- 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/lib/pages/main/controller.dart b/lib/pages/main/controller.dart index ad2a7781..8a0e4418 100644 --- a/lib/pages/main/controller.dart +++ b/lib/pages/main/controller.dart @@ -5,6 +5,7 @@ import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:flutter/material.dart'; import 'package:hive/hive.dart'; +import 'package:pilipala/common/widgets/network_img_layer.dart'; import 'package:pilipala/http/common.dart'; import 'package:pilipala/utils/storage.dart'; import 'package:pilipala/utils/utils.dart'; @@ -24,6 +25,7 @@ class MainController extends GetxController { late PageController pageController; int selectedIndex = 0; Box userInfoCache = GStrorage.userInfo; + dynamic userInfo; RxBool userLogin = false.obs; late Rx dynamicBadgeType = DynamicBadgeMode.number.obs; late bool enableGradientBg; @@ -37,7 +39,7 @@ class MainController extends GetxController { } hideTabBar = setting.get(SettingBoxKey.hideTabBar, defaultValue: false); - var userInfo = userInfoCache.get('userInfoCache'); + userInfo = userInfoCache.get('userInfoCache'); userLogin.value = userInfo != null; dynamicBadgeType.value = DynamicBadgeMode.values[setting.get( SettingBoxKey.dynamicBadgeMode, @@ -71,12 +73,20 @@ class MainController extends GetxController { } int dynamicItemIndex = navigationBars.indexWhere((item) => item['label'] == "动态"); + int mineItemIndex = + navigationBars.indexWhere((item) => item['label'] == "我的"); var res = await CommonHttp.unReadDynamic(); var data = res['data']; if (dynamicItemIndex != -1) { navigationBars[dynamicItemIndex]['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(); } diff --git a/lib/pages/main/view.dart b/lib/pages/main/view.dart index 825e65bc..93ee2d83 100644 --- a/lib/pages/main/view.dart +++ b/lib/pages/main/view.dart @@ -181,20 +181,21 @@ class _MainAppState extends State with SingleTickerProviderStateMixin { destinations: [ ..._mainController.navigationBars.map((e) { return NavigationDestination( - icon: Badge( - label: _mainController - .dynamicBadgeType.value == - DynamicBadgeMode.number - ? Text(e['count'].toString()) - : null, - padding: - const EdgeInsets.fromLTRB(6, 0, 6, 0), - isLabelVisible: _mainController - .dynamicBadgeType.value != - DynamicBadgeMode.hidden && - e['count'] > 0, - child: e['icon'], - ), + icon: _mainController + .dynamicBadgeType.value == + DynamicBadgeMode.number + ? Badge( + label: Text(e['count'].toString()), + padding: const EdgeInsets.fromLTRB( + 6, 0, 6, 0), + isLabelVisible: _mainController + .dynamicBadgeType + .value != + DynamicBadgeMode.hidden && + e['count'] > 0, + child: e['icon'], + ) + : e['icon'], selectedIcon: e['selectIcon'], label: e['label'], ); From fed1bf4e9dde465b8a1ef040320cd64c96dc9134 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Tue, 1 Oct 2024 22:17:47 +0800 Subject: [PATCH 05/90] =?UTF-8?q?feat:=20=E8=B5=84=E6=96=99=E7=BC=96?= =?UTF-8?q?=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/http/api.dart | 6 + lib/http/user.dart | 50 ++++++++ lib/pages/member/widgets/profile.dart | 3 +- lib/pages/mine_edit/controller.dart | 45 +++++++ lib/pages/mine_edit/index.dart | 4 + lib/pages/mine_edit/view.dart | 177 ++++++++++++++++++++++++++ lib/router/app_pages.dart | 3 + 7 files changed, 286 insertions(+), 2 deletions(-) create mode 100644 lib/pages/mine_edit/controller.dart create mode 100644 lib/pages/mine_edit/index.dart create mode 100644 lib/pages/mine_edit/view.dart diff --git a/lib/http/api.dart b/lib/http/api.dart index df0b9c85..0ad15f8f 100644 --- a/lib/http/api.dart +++ b/lib/http/api.dart @@ -592,4 +592,10 @@ class Api { /// 直播间记录 static const String liveRoomEntry = '${HttpString.liveBaseUrl}/xlive/web-room/v1/index/roomEntryAction'; + + /// 用户信息 + static const String accountInfo = '/x/member/web/account'; + + /// 更新用户信息 + static const String updateAccountInfo = '/x/member/web/update'; } diff --git a/lib/http/user.dart b/lib/http/user.dart index f4535905..207274e1 100644 --- a/lib/http/user.dart +++ b/lib/http/user.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'dart:developer'; +import 'package:dio/dio.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:html/parser.dart'; import 'package:pilipala/models/video/later.dart'; @@ -537,4 +538,53 @@ class UserHttp { .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'], + }; + } + } } diff --git a/lib/pages/member/widgets/profile.dart b/lib/pages/member/widgets/profile.dart index 8c6385db..596ace46 100644 --- a/lib/pages/member/widgets/profile.dart +++ b/lib/pages/member/widgets/profile.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart'; import 'package:pilipala/models/live/item.dart'; @@ -233,7 +232,7 @@ class ProfilePanel extends StatelessWidget { if (ctr.ownerMid == ctr.mid && ctr.ownerMid != -1) ...[ TextButton( onPressed: () { - SmartDialog.showToast('功能开发中 💪'); + Get.toNamed('/mineEdit'); }, style: TextButton.styleFrom( padding: const EdgeInsets.only(left: 80, right: 80), diff --git a/lib/pages/mine_edit/controller.dart b/lib/pages/mine_edit/controller.dart new file mode 100644 index 00000000..9bc43246 --- /dev/null +++ b/lib/pages/mine_edit/controller.dart @@ -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(); + 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']}"); + } +} diff --git a/lib/pages/mine_edit/index.dart b/lib/pages/mine_edit/index.dart new file mode 100644 index 00000000..88806448 --- /dev/null +++ b/lib/pages/mine_edit/index.dart @@ -0,0 +1,4 @@ +library mine_edit; + +export './controller.dart'; +export './view.dart'; diff --git a/lib/pages/mine_edit/view.dart b/lib/pages/mine_edit/view.dart new file mode 100644 index 00000000..700c707e --- /dev/null +++ b/lib/pages/mine_edit/view.dart @@ -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 createState() => _MineEditPageState(); +} + +class _MineEditPageState extends State { + 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( + value: ctr.sex, + decoration: const InputDecoration( + labelText: '性别', + border: OutlineInputBorder(), + ), + items: ['男', '女', '保密'].map((String value) { + return DropdownMenuItem( + 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(); + } + })), + ), + ); + } +} diff --git a/lib/router/app_pages.dart b/lib/router/app_pages.dart index dec72b06..7a14b499 100644 --- a/lib/router/app_pages.dart +++ b/lib/router/app_pages.dart @@ -11,6 +11,7 @@ import 'package:pilipala/pages/message/like/index.dart'; import 'package:pilipala/pages/message/reply/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/read/index.dart'; import 'package:pilipala/pages/setting/pages/logs.dart'; @@ -196,6 +197,8 @@ class Routes { // 用户专栏 CustomGetPage( name: '/memberArticle', page: () => const MemberArticlePage()), + // 用户信息编辑 + CustomGetPage(name: '/mineEdit', page: () => const MineEditPage()), ]; } From 1f391cbfd08661aed2417e60d5567d9068b432bc Mon Sep 17 00:00:00 2001 From: guozhigq Date: Wed, 2 Oct 2024 22:26:13 +0800 Subject: [PATCH 06/90] fix --- lib/pages/home/view.dart | 8 ++++---- lib/pages/mine/view.dart | 17 +++++++++++------ 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/lib/pages/home/view.dart b/lib/pages/home/view.dart index 11d36c9b..2c32aa0d 100644 --- a/lib/pages/home/view.dart +++ b/lib/pages/home/view.dart @@ -36,10 +36,10 @@ class _HomePageState extends State showUserBottomSheet() { feedBack(); final MainController mainController = Get.put(MainController()); - if (mainController.navigationBars - .where((item) => item['label'] == "我的") - .isNotEmpty) { - mainController.pageController.jumpToPage(2); + int mineItemIndex = mainController.navigationBars + .indexWhere((item) => item['label'] == "我的"); + if (mineItemIndex != -1) { + mainController.pageController.jumpToPage(mineItemIndex); } else { Get.toNamed('/mine'); } diff --git a/lib/pages/mine/view.dart b/lib/pages/mine/view.dart index 90fa4958..4fcfdcf4 100644 --- a/lib/pages/mine/view.dart +++ b/lib/pages/mine/view.dart @@ -98,9 +98,14 @@ class _MinePageState extends State }, ), _buildMenuSection(context), - Divider( - height: 25, - color: Theme.of(context).dividerColor.withOpacity(0.1), + Obx( + () => Visibility( + visible: ctr.userLogin.value, + child: Divider( + height: 25, + color: Theme.of(context).dividerColor.withOpacity(0.1), + ), + ), ), Obx( () => ctr.userLogin.value @@ -320,8 +325,8 @@ class _MinePageState extends State future: ctr.queryFavFolder(), builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.done) { - Map data = snapshot.data as Map; - if (data['status']) { + Map? data = snapshot.data; + if (data != null && data['status']) { List favFolderList = ctr.favFolderData.value.list!; int favFolderCount = ctr.favFolderData.value.count!; bool flag = favFolderCount > favFolderList.length; @@ -369,7 +374,7 @@ class _MinePageState extends State } else { return SizedBox( height: 110, - child: Center(child: Text(data['msg'])), + child: Center(child: Text(data?['msg'] ?? '')), ); } } else { From c08c3f4c7c61933e7b2157ffccc23e02e0f9d805 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sat, 5 Oct 2024 11:39:09 +0800 Subject: [PATCH 07/90] mod --- lib/pages/mine/view.dart | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/lib/pages/mine/view.dart b/lib/pages/mine/view.dart index 4fcfdcf4..5bf7a536 100644 --- a/lib/pages/mine/view.dart +++ b/lib/pages/mine/view.dart @@ -177,12 +177,18 @@ class _MinePageState extends State ), const SizedBox(height: 2), ], - Text( - '硬币: ${userInfo.money ?? '-'}', - style: TextStyle( - color: Theme.of(context).colorScheme.outline, - ), - ), + 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(), From 676b2f18eb309f8153a8038828d9e3045387fd16 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 6 Oct 2024 21:11:00 +0800 Subject: [PATCH 08/90] mod --- lib/pages/mine/view.dart | 212 ++++++++++++++++++--------------------- 1 file changed, 98 insertions(+), 114 deletions(-) diff --git a/lib/pages/mine/view.dart b/lib/pages/mine/view.dart index 5bf7a536..b96c095a 100644 --- a/lib/pages/mine/view.dart +++ b/lib/pages/mine/view.dart @@ -44,6 +44,8 @@ class _MinePageState extends State super.build(context); return Scaffold( appBar: AppBar( + scrolledUnderElevation: 0, + elevation: 0, actions: [ IconButton( icon: const Icon(Icons.search_outlined), @@ -64,59 +66,71 @@ class _MinePageState extends State const SizedBox(width: 22), ], ), - body: SingleChildScrollView( - child: Column( - children: [ - Obx(() => _buildProfileSection(context, ctr.userInfo.value)), - const SizedBox(height: 10), - FutureBuilder( - future: _futureBuilderFuture, - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.done) { - if (snapshot.data == null) { - return const SizedBox(); - } - if (snapshot.data['status']) { - return Obx( - () => _buildStatsSection( - context, - ctr.userStat.value, + body: RefreshIndicator( + onRefresh: () async { + await ctr.queryUserInfo(); + }, + child: SingleChildScrollView( + physics: const AlwaysScrollableScrollPhysics( + parent: BouncingScrollPhysics()), + child: Padding( + padding: const EdgeInsets.only(bottom: 110), + child: Expanded( + child: Column( + children: [ + Obx(() => _buildProfileSection(context, ctr.userInfo.value)), + const SizedBox(height: 10), + FutureBuilder( + future: _futureBuilderFuture, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + if (snapshot.data == null) { + return const SizedBox(); + } + if (snapshot.data['status']) { + return Obx( + () => _buildStatsSection( + context, + ctr.userStat.value, + ), + ); + } else { + return _buildStatsSection( + context, + ctr.userStat.value, + ); + } + } else { + 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), ), - ); - } else { - return _buildStatsSection( - context, - ctr.userStat.value, - ); - } - } else { - 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, + ) + ], ), ), - Obx( - () => ctr.userLogin.value - ? _buildFavoritesSection(context) - : const SizedBox(), - ), - SizedBox( - height: MediaQuery.of(context).padding.bottom + - kBottomNavigationBarHeight, - ) - ], + ), ), ), ); @@ -326,7 +340,7 @@ class _MinePageState extends State const SizedBox(height: 4), SizedBox( width: double.infinity, - height: MediaQuery.textScalerOf(context).scale(110), + height: MediaQuery.textScalerOf(context).scale(180), child: FutureBuilder( future: ctr.queryFavFolder(), builder: (context, snapshot) { @@ -459,71 +473,41 @@ class FavFolderItem extends StatelessWidget { String heroTag = Utils.makeHeroTag(item!.fid); return Container( margin: EdgeInsets.only(left: index == 0 ? 20 : 0, right: 14), - child: InkWell( - onTap: () => Get.toNamed('/favDetail', arguments: item, parameters: { - 'mediaId': item!.id.toString(), - 'heroTag': heroTag, - 'isOwner': '1', - }), - borderRadius: StyleString.mdRadius, - child: Stack( - children: [ - NetworkImgLayer( - src: item!.cover, - width: 175, - height: 110, - ), - // 渐变 - Positioned( - left: 0, - right: 0, - top: 60, - bottom: 0, - child: Container( - padding: const EdgeInsets.only(bottom: 8, left: 10, right: 2), - decoration: BoxDecoration( - borderRadius: StyleString.mdRadius, - gradient: LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [ - Colors.black.withOpacity(0), - Colors.black.withOpacity(0.6), - ], - ), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.end, - children: [ - RichText( - maxLines: 1, - overflow: TextOverflow.ellipsis, - text: TextSpan( - style: Theme.of(context) - .textTheme - .titleSmall! - .copyWith(color: Colors.white), - children: [ - TextSpan(text: item!.title!), - const TextSpan(text: ' '), - if (item!.mediaCount! > 0) - TextSpan( - text: item!.mediaCount!.toString(), - style: const TextStyle( - fontSize: 11, - fontWeight: FontWeight.bold, - ), - ), - ], - ), - ), - ], - ), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + 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), + Text( + ' ${item!.title}', + overflow: TextOverflow.fade, + maxLines: 1, + ), + Text( + ' 共${item!.mediaCount}条视频', + style: Theme.of(context) + .textTheme + .labelSmall! + .copyWith(color: Theme.of(context).colorScheme.outline), + ) + ], ), ); } From 3f9b35b56bfffbb755e6e2af10f11979f32a7124 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Tue, 15 Oct 2024 00:06:48 +0800 Subject: [PATCH 09/90] =?UTF-8?q?mod:=20ai=E6=80=BB=E7=BB=93=E6=A0=B7?= =?UTF-8?q?=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/video/detail/introduction/view.dart | 6 + lib/pages/video/detail/widgets/ai_detail.dart | 294 +++++++----------- lib/utils/global_data_cache.dart | 2 + lib/utils/utils.dart | 3 + 4 files changed, 122 insertions(+), 183 deletions(-) diff --git a/lib/pages/video/detail/introduction/view.dart b/lib/pages/video/detail/introduction/view.dart index 31edab2f..2f40414e 100644 --- a/lib/pages/video/detail/introduction/view.dart +++ b/lib/pages/video/detail/introduction/view.dart @@ -242,6 +242,12 @@ class _VideoInfoState extends State with TickerProviderStateMixin { showBottomSheet( context: context, enableDrag: true, + shape: const RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(25), + topRight: Radius.circular(25), + ), + ), builder: (BuildContext context) { return AiDetail(modelResult: videoIntroController.modelResult); }, diff --git a/lib/pages/video/detail/widgets/ai_detail.dart b/lib/pages/video/detail/widgets/ai_detail.dart index b71b026d..64327553 100644 --- a/lib/pages/video/detail/widgets/ai_detail.dart +++ b/lib/pages/video/detail/widgets/ai_detail.dart @@ -1,16 +1,11 @@ import 'package:flutter/gestures.dart'; 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/models/video/ai.dart'; import 'package:pilipala/pages/video/detail/index.dart'; -import 'package:pilipala/utils/storage.dart'; +import 'package:pilipala/utils/global_data_cache.dart'; import 'package:pilipala/utils/utils.dart'; -Box localCache = GStrorage.localCache; -late double sheetHeight; - class AiDetail extends StatelessWidget { final ModelResult? modelResult; @@ -21,124 +16,21 @@ class AiDetail extends StatelessWidget { @override Widget build(BuildContext context) { - sheetHeight = localCache.get('sheetHeight'); return Container( - color: Theme.of(context).colorScheme.surface, - padding: const EdgeInsets.only(left: 14, right: 14), - height: sheetHeight, + padding: const EdgeInsets.only(left: 16, right: 16), + height: GlobalDataCache().sheetHeight, child: Column( children: [ - InkWell( - onTap: () => Get.back(), - child: Container( - height: 35, - padding: const EdgeInsets.only(bottom: 2), - child: Center( - child: Container( - width: 32, - height: 3, - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.primary, - borderRadius: const BorderRadius.all(Radius.circular(3)), - ), - ), - ), - ), - ), + _buildHeader(context), Expanded( child: SingleChildScrollView( child: Column( children: [ - if (modelResult!.resultType != 0 && - modelResult!.summary != '') ...[ - SelectableText( - modelResult!.summary!, - style: const TextStyle( - fontSize: 15, - fontWeight: FontWeight.bold, - height: 1.5, - ), - ), + if (modelResult!.summary != '') ...[ + _buildSummaryText(modelResult!.summary!), const SizedBox(height: 20), ], - ListView.builder( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - itemCount: modelResult!.outline!.length, - itemBuilder: (context, index) { - final outline = modelResult!.outline![index]; - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SelectableText( - outline.title!, - style: const TextStyle( - fontSize: 14, - fontWeight: FontWeight.bold, - height: 1.5, - ), - ), - const SizedBox(height: 6), - ListView.builder( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - itemCount: outline.partOutline!.length, - itemBuilder: (context, i) { - final part = outline.partOutline![i]; - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - GestureDetector( - onTap: () { - try { - final controller = - Get.find( - tag: Get.arguments['heroTag'], - ); - controller.plPlayerController.seekTo( - Duration( - seconds: Utils.duration( - Utils.tampToSeektime( - part.timestamp!), - ).toInt(), - ), - ); - } catch (_) {} - }, - child: SelectableText.rich( - TextSpan( - style: TextStyle( - fontSize: 13, - color: Theme.of(context) - .colorScheme - .onSurface, - height: 1.5, - ), - children: [ - TextSpan( - text: Utils.tampToSeektime( - part.timestamp!), - style: TextStyle( - color: Theme.of(context) - .colorScheme - .primary, - ), - ), - const TextSpan(text: ' '), - TextSpan(text: part.content!), - ], - ), - ), - ), - const SizedBox(height: 20), - ], - ); - }, - ), - ], - ); - }, - ) + _buildOutlineList(context), ], ), ), @@ -148,77 +40,113 @@ class AiDetail extends StatelessWidget { ); } - InlineSpan buildContent(BuildContext context, content) { - List descV2 = content.descV2; - // type - // 1 普通文本 - // 2 @用户 - List spanChilds = List.generate(descV2.length, (index) { - final currentDesc = descV2[index]; - switch (currentDesc.type) { - case 1: - List spanChildren = []; - RegExp urlRegExp = RegExp(r'https?://\S+\b'); - Iterable matches = urlRegExp.allMatches(currentDesc.rawText); + Widget _buildHeader(BuildContext context) { + return Center( + child: Container( + decoration: BoxDecoration( + color: Theme.of(context).hintColor, + borderRadius: const BorderRadius.all(Radius.circular(10)), + ), + height: 4, + width: 40, + margin: const EdgeInsets.symmetric(vertical: 16), + ), + ); + } - int previousEndIndex = 0; - for (Match match in matches) { - if (match.start > previousEndIndex) { - spanChildren.add(TextSpan( - text: currentDesc.rawText - .substring(previousEndIndex, match.start))); - } - spanChildren.add( - TextSpan( - text: match.group(0), - style: TextStyle( - color: Theme.of(context).colorScheme.primary), // 设置颜色为蓝色 - recognizer: TapGestureRecognizer() - ..onTap = () { - // 处理点击事件 - try { - Get.toNamed( - '/webview', - parameters: { - 'url': match.group(0)!, - 'type': 'url', - 'pageTitle': match.group(0)!, - }, - ); - } catch (err) { - SmartDialog.showToast(err.toString()); - } - }, - ), - ); - previousEndIndex = match.end; - } + Widget _buildSummaryText(String summary) { + return SelectableText( + summary, + textAlign: TextAlign.justify, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + height: 1.6, + ), + ); + } - if (previousEndIndex < currentDesc.rawText.length) { - spanChildren.add(TextSpan( - text: currentDesc.rawText.substring(previousEndIndex))); - } + Widget _buildOutlineList(BuildContext context) { + return ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: modelResult!.outline!.length, + itemBuilder: (context, index) { + final outline = modelResult!.outline![index]; + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildOutlineTitle(outline.title!), + const SizedBox(height: 20), + _buildPartOutlineList(context, outline.partOutline!), + ], + ); + }, + ); + } - TextSpan result = TextSpan(children: spanChildren); - return result; - case 2: - final colorSchemePrimary = Theme.of(context).colorScheme.primary; - final heroTag = Utils.makeHeroTag(currentDesc.bizId); - return TextSpan( - text: '@${currentDesc.rawText}', - style: TextStyle(color: colorSchemePrimary), + Widget _buildOutlineTitle(String title) { + return SelectableText( + title, + textAlign: TextAlign.justify, + style: const TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + height: 1.5, + ), + ); + } + + Widget _buildPartOutlineList( + BuildContext context, List partOutline) { + return ListView.builder( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: partOutline.length, + itemBuilder: (context, i) { + final part = partOutline[i]; + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildPartText(context, part), + const SizedBox(height: 20), + ], + ); + }, + ); + } + + void _onPartTap(BuildContext context, int timestamp) { + try { + final controller = Get.find( + tag: Get.arguments['heroTag'], + ); + controller.plPlayerController.seekTo( + Duration(seconds: timestamp), + ); + } catch (_) {} + } + + Widget _buildPartText(BuildContext context, PartOutline part) { + return SelectableText.rich( + TextSpan( + style: TextStyle( + fontSize: 15, + color: Theme.of(context).colorScheme.onSurface, + ), + children: [ + TextSpan( + text: Utils.tampToSeektime(part.timestamp!), + style: TextStyle( + color: Theme.of(context).colorScheme.primary, + ), recognizer: TapGestureRecognizer() - ..onTap = () { - Get.toNamed( - '/member?mid=${currentDesc.bizId}', - arguments: {'face': '', 'heroTag': heroTag}, - ); - }, - ); - default: - return const TextSpan(); - } - }); - return TextSpan(children: spanChilds); + ..onTap = () => _onPartTap(context, part.timestamp!), + ), + const TextSpan(text: ' '), + TextSpan(text: part.content!), + ], + ), + ); } } diff --git a/lib/utils/global_data_cache.dart b/lib/utils/global_data_cache.dart index ea2eda37..20bbea4d 100644 --- a/lib/utils/global_data_cache.dart +++ b/lib/utils/global_data_cache.dart @@ -15,6 +15,7 @@ class GlobalDataCache { late FullScreenGestureMode fullScreenGestureMode; late bool enablePlayerControlAnimation; late List actionTypeSort; + late double sheetHeight; String? wWebid; /// 播放器相关 @@ -103,5 +104,6 @@ class GlobalDataCache { speedsList.addAll(playSpeedSystem); userInfo = userInfoCache.get('userInfoCache'); + sheetHeight = localCache.get('sheetHeight'); } } diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart index 7a254f7d..c4cf7e16 100644 --- a/lib/utils/utils.dart +++ b/lib/utils/utils.dart @@ -351,6 +351,9 @@ class Utils { // 时间戳转时间 static tampToSeektime(number) { + if (number is String && int.tryParse(number) == null) { + return number; + } int hours = number ~/ 60; int minutes = number % 60; From 63a6c048e6efda70e9bb456ea6337b7374e06968 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Tue, 15 Oct 2024 00:17:46 +0800 Subject: [PATCH 10/90] =?UTF-8?q?feat:=20=E7=A8=8D=E5=90=8E=E5=86=8D?= =?UTF-8?q?=E7=9C=8B=E4=B8=8B=E6=8B=89=E5=88=B7=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/later/controller.dart | 4 +- lib/pages/later/view.dart | 121 +++++++++++++++++--------------- 2 files changed, 66 insertions(+), 59 deletions(-) diff --git a/lib/pages/later/controller.dart b/lib/pages/later/controller.dart index dd8f3c5a..e8170d0a 100644 --- a/lib/pages/later/controller.dart +++ b/lib/pages/later/controller.dart @@ -22,11 +22,11 @@ class LaterController extends GetxController { userInfo = userInfoCache.get('userInfoCache'); } - Future queryLaterList() async { + Future queryLaterList({type = 'init'}) async { if (userInfo == null) { return {'status': false, 'msg': '账号未登录', 'code': -101}; } - isLoading.value = true; + isLoading.value = type == 'init'; var res = await UserHttp.seeYouLater(); if (res['status']) { count = res['data']['count']; diff --git a/lib/pages/later/view.dart b/lib/pages/later/view.dart index 771d8631..db82258d 100644 --- a/lib/pages/later/view.dart +++ b/lib/pages/later/view.dart @@ -66,67 +66,74 @@ class _LaterPageState extends State { const SizedBox(width: 8), ], ), - body: CustomScrollView( - controller: _laterController.scrollController, - slivers: [ - FutureBuilder( - future: _futureBuilderFuture, - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.done) { - Map? data = snapshot.data; - if (data != null && data['status']) { - return Obx( - () => _laterController.laterList.isNotEmpty && - !_laterController.isLoading.value - ? SliverList( - delegate: - SliverChildBuilderDelegate((context, index) { - var videoItem = _laterController.laterList[index]; - return VideoCardH( - videoItem: videoItem, - source: 'later', - onPressedFn: () => _laterController.toViewDel( - aid: videoItem.aid)); - }, childCount: _laterController.laterList.length), - ) - : _laterController.isLoading.value - ? const SliverToBoxAdapter( - child: Center(child: Text('加载中')), - ) - : const NoData(), - ); + body: RefreshIndicator( + onRefresh: () async { + await _laterController.queryLaterList(type: 'onRefresh'); + }, + child: CustomScrollView( + controller: _laterController.scrollController, + physics: const AlwaysScrollableScrollPhysics(), + slivers: [ + FutureBuilder( + future: _futureBuilderFuture, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + Map? data = snapshot.data; + if (data != null && data['status']) { + return Obx( + () => _laterController.laterList.isNotEmpty && + !_laterController.isLoading.value + ? SliverList( + delegate: + SliverChildBuilderDelegate((context, index) { + var videoItem = + _laterController.laterList[index]; + return VideoCardH( + videoItem: videoItem, + source: 'later', + onPressedFn: () => _laterController + .toViewDel(aid: videoItem.aid)); + }, childCount: _laterController.laterList.length), + ) + : _laterController.isLoading.value + ? const SliverToBoxAdapter( + child: Center(child: Text('加载中')), + ) + : const NoData(), + ); + } else { + return HttpError( + errMsg: data?['msg'] ?? '请求异常', + btnText: data?['code'] == -101 ? '去登录' : null, + fn: () { + if (data?['code'] == -101) { + RoutePush.loginRedirectPush(); + } else { + setState(() { + _futureBuilderFuture = + _laterController.queryLaterList(); + }); + } + }, + ); + } } else { - return HttpError( - errMsg: data?['msg'] ?? '请求异常', - btnText: data?['code'] == -101 ? '去登录' : null, - fn: () { - if (data?['code'] == -101) { - RoutePush.loginRedirectPush(); - } else { - setState(() { - _futureBuilderFuture = - _laterController.queryLaterList(); - }); - } - }, + // 骨架屏 + return SliverList( + delegate: SliverChildBuilderDelegate((context, index) { + return const VideoCardHSkeleton(); + }, childCount: 10), ); } - } else { - // 骨架屏 - return SliverList( - delegate: SliverChildBuilderDelegate((context, index) { - return const VideoCardHSkeleton(); - }, childCount: 10), - ); - } - }, - ), - SliverToBoxAdapter( - child: SizedBox( - height: MediaQuery.of(context).padding.bottom + 10, + }, ), - ) - ], + SliverToBoxAdapter( + child: SizedBox( + height: MediaQuery.of(context).padding.bottom + 10, + ), + ) + ], + ), ), floatingActionButton: Obx( () => _laterController.laterList.isNotEmpty From dc8f034df74e88937602943a98928f99ce27c29b Mon Sep 17 00:00:00 2001 From: guozhigq Date: Tue, 15 Oct 2024 00:24:31 +0800 Subject: [PATCH 11/90] typo --- lib/http/msg.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/http/msg.dart b/lib/http/msg.dart index 6426a6f2..5568e226 100644 --- a/lib/http/msg.dart +++ b/lib/http/msg.dart @@ -278,10 +278,10 @@ class MsgHttp { 'data': MessageLikeModel.fromJson(res.data['data']), }; } catch (err) { - return {'status': false, 'date': [], 'msg': err.toString()}; + return {'status': false, 'data': [], 'msg': err.toString()}; } } else { - return {'status': false, 'date': [], 'msg': res.data['message']}; + return {'status': false, 'data': [], 'msg': res.data['message']}; } } From eb2a51e5e2f5be5aef7bfe99fed037a934b8a948 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Wed, 16 Oct 2024 00:52:10 +0800 Subject: [PATCH 12/90] =?UTF-8?q?opt:=20=E5=BF=AB=E8=BF=9B=E5=BF=AB?= =?UTF-8?q?=E9=80=80=E9=9D=A2=E6=9D=BF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/plugin/pl_player/panels/seek_panel.dart | 76 ++++++++++ lib/plugin/pl_player/view.dart | 132 +++++------------- .../pl_player/widgets/backward_seek.dart | 91 ------------ .../pl_player/widgets/forward_seek.dart | 91 ------------ .../pl_player/widgets/seek_indicator.dart | 94 +++++++++++++ 5 files changed, 205 insertions(+), 279 deletions(-) create mode 100644 lib/plugin/pl_player/panels/seek_panel.dart delete mode 100644 lib/plugin/pl_player/widgets/backward_seek.dart delete mode 100644 lib/plugin/pl_player/widgets/forward_seek.dart create mode 100644 lib/plugin/pl_player/widgets/seek_indicator.dart diff --git a/lib/plugin/pl_player/panels/seek_panel.dart b/lib/plugin/pl_player/panels/seek_panel.dart new file mode 100644 index 00000000..0d5e7c9e --- /dev/null +++ b/lib/plugin/pl_player/panels/seek_panel.dart @@ -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(), + ); + } +} diff --git a/lib/plugin/pl_player/view.dart b/lib/plugin/pl_player/view.dart index 6b57a8e1..a88bc86f 100644 --- a/lib/plugin/pl_player/view.dart +++ b/lib/plugin/pl_player/view.dart @@ -22,12 +22,11 @@ import 'package:screen_brightness/screen_brightness.dart'; import '../../utils/global_data_cache.dart'; import 'models/bottom_control_type.dart'; import 'models/bottom_progress_behavior.dart'; +import 'panels/seek_panel.dart'; import 'widgets/app_bar_ani.dart'; -import 'widgets/backward_seek.dart'; import 'widgets/bottom_control.dart'; import 'widgets/common_btn.dart'; import 'widgets/control_bar.dart'; -import 'widgets/forward_seek.dart'; import 'widgets/play_pause_btn.dart'; class PLVideoPlayer extends StatefulWidget { @@ -69,8 +68,8 @@ class _PLVideoPlayerState extends State final RxBool _mountSeekBackwardButton = false.obs; final RxBool _mountSeekForwardButton = false.obs; - final RxBool _hideSeekBackwardButton = false.obs; - final RxBool _hideSeekForwardButton = false.obs; + final RxBool _hideSeekBackwardButton = true.obs; + final RxBool _hideSeekForwardButton = true.obs; final RxDouble _brightnessValue = 0.0.obs; final RxBool _brightnessIndicator = false.obs; @@ -97,10 +96,12 @@ class _PLVideoPlayerState extends State void onDoubleTapSeekBackward() { _mountSeekBackwardButton.value = true; + _hideSeekBackwardButton.value = false; } void onDoubleTapSeekForward() { _mountSeekForwardButton.value = true; + _hideSeekForwardButton.value = false; } // 双击播放、暂停 @@ -375,6 +376,29 @@ class _PLVideoPlayerState extends State 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 Widget build(BuildContext context) { final PlPlayerController _ = widget.controller; @@ -865,99 +889,13 @@ class _PLVideoPlayerState extends State } }), - /// 点击 快进/快退 - Obx( - () => Visibility( - visible: - _mountSeekBackwardButton.value || _mountSeekForwardButton.value, - child: Positioned.fill( - child: Row( - children: [ - Expanded( - child: _mountSeekBackwardButton.value - ? TweenAnimationBuilder( - tween: Tween( - 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( - tween: Tween( - 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(), - ), - ], - ), - ), - ), + /// 快进/快退面板 + SeekPanel( + mountSeekBackwardButton: _mountSeekBackwardButton, + mountSeekForwardButton: _mountSeekForwardButton, + hideSeekBackwardButton: _hideSeekBackwardButton, + hideSeekForwardButton: _hideSeekForwardButton, + onSubmittedcb: _handleSubmittedCallback, ), ], ); diff --git a/lib/plugin/pl_player/widgets/backward_seek.dart b/lib/plugin/pl_player/widgets/backward_seek.dart deleted file mode 100644 index 8289d77c..00000000 --- a/lib/plugin/pl_player/widgets/backward_seek.dart +++ /dev/null @@ -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 createState() => BackwardSeekIndicatorState(); -} - -class BackwardSeekIndicatorState extends State { - 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), - ), - ), - ], - ), - ), - ), - ); - } -} diff --git a/lib/plugin/pl_player/widgets/forward_seek.dart b/lib/plugin/pl_player/widgets/forward_seek.dart deleted file mode 100644 index 3f68fe0d..00000000 --- a/lib/plugin/pl_player/widgets/forward_seek.dart +++ /dev/null @@ -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 createState() => ForwardSeekIndicatorState(); -} - -class ForwardSeekIndicatorState extends State { - 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), - ), - ), - ], - ), - ), - ), - ); - } -} diff --git a/lib/plugin/pl_player/widgets/seek_indicator.dart b/lib/plugin/pl_player/widgets/seek_indicator.dart new file mode 100644 index 00000000..45fa5a16 --- /dev/null +++ b/lib/plugin/pl_player/widgets/seek_indicator.dart @@ -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 createState() => _SeekIndicatorState(); +} + +class _SeekIndicatorState extends State { + 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, + ), + ], + ), + ), + ], + ), + ), + ); + } +} From 174eff71510e259101a6d3e773ddf2da4efb3ba4 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Wed, 16 Oct 2024 14:17:24 +0800 Subject: [PATCH 13/90] Merge branch 'main' into feature-minePage --- .github/workflows/beta_ci.yml | 3 +- .github/workflows/release_ci.yml | 4 +- android/app/src/main/AndroidManifest.xml | 14 +- change_log/1.0.25.1010.md | 39 ++ ios/Podfile.lock | 6 + ios/Runner/Info.plist | 49 +- lib/common/widgets/network_img_layer.dart | 6 +- lib/common/widgets/video_card_v.dart | 5 +- lib/http/api.dart | 3 + lib/http/init.dart | 14 +- lib/http/member.dart | 31 +- lib/http/reply.dart | 21 + lib/http/video.dart | 3 +- lib/main.dart | 1 + lib/models/common/gesture_mode.dart | 7 +- lib/models/common/search_type.dart | 20 +- lib/models/video/later.dart | 12 +- lib/pages/about/index.dart | 4 +- lib/pages/bangumi/introduction/view.dart | 6 +- lib/pages/bangumi/view.dart | 8 +- lib/pages/bangumi/widgets/bangumi_panel.dart | 8 +- lib/pages/bangumi/widgets/bangumu_card_v.dart | 2 +- lib/pages/home/controller.dart | 2 +- lib/pages/live_room/controller.dart | 2 +- lib/pages/live_room/view.dart | 214 +++--- lib/pages/main/controller.dart | 5 +- lib/pages/main/view.dart | 40 +- lib/pages/member/controller.dart | 118 ++-- lib/pages/member/view.dart | 651 ++++++++---------- lib/pages/member/widgets/commen_widget.dart | 24 + lib/pages/member/widgets/profile.dart | 476 +++++++------ lib/pages/member_article/controller.dart | 16 - lib/pages/message/like/view.dart | 32 +- lib/pages/opus/view.dart | 2 +- lib/pages/read/controller.dart | 2 +- lib/pages/read/view.dart | 11 +- lib/pages/search_panel/controller.dart | 4 +- lib/pages/search_panel/view.dart | 13 +- .../search_panel/widgets/article_panel.dart | 317 ++++++--- lib/pages/setting/pages/play_gesture_set.dart | 4 +- lib/pages/video/detail/controller.dart | 1 - .../video/detail/introduction/controller.dart | 74 +- lib/pages/video/detail/introduction/view.dart | 169 +---- lib/pages/video/detail/reply/view.dart | 12 +- .../detail/reply/widgets/reply_item.dart | 122 +++- lib/pages/video/detail/view.dart | 64 +- .../detail/widgets/watch_later_list.dart | 4 +- lib/pages/whisper/view.dart | 4 +- lib/plugin/pl_player/controller.dart | 8 + lib/plugin/pl_player/view.dart | 16 +- lib/utils/app_scheme.dart | 244 ++++--- lib/utils/cache_manage.dart | 77 ++- lib/utils/global_data_cache.dart | 3 +- lib/utils/subtitle.dart | 56 +- lib/utils/utils.dart | 2 +- linux/flutter/generated_plugin_registrant.cc | 4 + linux/flutter/generated_plugins.cmake | 1 + macos/Flutter/GeneratedPluginRegistrant.swift | 2 + pubspec.lock | 40 ++ pubspec.yaml | 3 +- .../flutter/generated_plugin_registrant.cc | 3 + windows/flutter/generated_plugins.cmake | 1 + 62 files changed, 1738 insertions(+), 1371 deletions(-) create mode 100644 change_log/1.0.25.1010.md create mode 100644 lib/pages/member/widgets/commen_widget.dart diff --git a/.github/workflows/beta_ci.yml b/.github/workflows/beta_ci.yml index 14b51780..9c40de6b 100644 --- a/.github/workflows/beta_ci.yml +++ b/.github/workflows/beta_ci.yml @@ -12,7 +12,6 @@ on: - ".idea/**" - "!.github/workflows/**" - jobs: update_version: name: Read and update version @@ -96,7 +95,7 @@ jobs: if: steps.cache-flutter.outputs.cache-hit != 'true' uses: subosito/flutter-action@v2 with: - flutter-version: 3.16.5 + flutter-version: 3.19.6 channel: any - name: 下载项目依赖 diff --git a/.github/workflows/release_ci.yml b/.github/workflows/release_ci.yml index 78230645..f7c06d29 100644 --- a/.github/workflows/release_ci.yml +++ b/.github/workflows/release_ci.yml @@ -36,7 +36,7 @@ jobs: if: steps.cache-flutter.outputs.cache-hit != 'true' uses: subosito/flutter-action@v2 with: - flutter-version: 3.16.5 + flutter-version: 3.19.6 channel: any - name: 下载项目依赖 @@ -98,7 +98,7 @@ jobs: uses: subosito/flutter-action@v2.10.0 with: cache: true - flutter-version: 3.16.5 + flutter-version: 3.19.6 - name: flutter build ipa run: | diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index f119eb1e..c948bc8f 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -47,13 +47,14 @@ + + + + + + + + + diff --git a/change_log/1.0.25.1010.md b/change_log/1.0.25.1010.md new file mode 100644 index 00000000..951efcb1 --- /dev/null +++ b/change_log/1.0.25.1010.md @@ -0,0 +1,39 @@ +## 1.0.25 + +### 功能 ++ 直播弹幕 ++ 稍后再看、收藏夹播放全部 ++ 收藏夹新建、编辑 ++ 评论删除 ++ 评论保存为图片 ++ 动态页滑动切换up ++ up投稿筛选充电视频 ++ 直播tab展示关注up ++ up主页专栏展示 + +### 优化 ++ 视频详情页一键三连 ++ 动态页标识充电视频 ++ 播放器亮度、音量调整百分比展示 ++ 封面预览时视频标题可复制 ++ 竖屏直播布局 ++ 图片预览 ++ 专栏渲染优化 ++ 私信图片查看 + +### 修复 ++ 收藏夹点击异常 ++ 搜索up异常 ++ 系统通知已读异常 ++ [赞了我的]展示错误 ++ 部分up合集无法打开 ++ 切换合集视频投币个数未重置 ++ 搜索条件筛选面板无法滚动 ++ 部分机型导航条未沉浸 ++ 专栏图片渲染问题 ++ 专栏浏览历史记录 ++ 直播间历史记录 + + +更多更新日志可在Github上查看 +问题反馈、功能建议请查看「关于」页面。 diff --git a/ios/Podfile.lock b/ios/Podfile.lock index a400600f..040c0297 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -1,4 +1,6 @@ PODS: + - app_links (0.0.2): + - Flutter - appscheme (1.0.4): - Flutter - audio_service (0.0.1): @@ -66,6 +68,7 @@ PODS: - Flutter DEPENDENCIES: + - app_links (from `.symlinks/plugins/app_links/ios`) - appscheme (from `.symlinks/plugins/appscheme/ios`) - audio_service (from `.symlinks/plugins/audio_service/ios`) - audio_session (from `.symlinks/plugins/audio_session/ios`) @@ -102,6 +105,8 @@ SPEC REPOS: - Toast EXTERNAL SOURCES: + app_links: + :path: ".symlinks/plugins/app_links/ios" appscheme: :path: ".symlinks/plugins/appscheme/ios" audio_service: @@ -160,6 +165,7 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/webview_flutter_wkwebview/ios" SPEC CHECKSUMS: + app_links: e7a6750a915a9e161c58d91bc610e8cd1d4d0ad0 appscheme: b1c3f8862331cb20430cf9e0e4af85dbc1572ad8 audio_service: f509d65da41b9521a61f1c404dd58651f265a567 audio_session: 4f3e461722055d21515cf3261b64c973c062f345 diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 1e7d9fed..24dceb17 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -65,44 +65,29 @@ CFBundleURLName - + bilibili CFBundleURLSchemes http https - - CFBundleURLTypes - - - CFBundleURLName - - CFBundleURLSchemes - - m.bilibili.com - bilibili.com - www.bilibili.com - bangumi.bilibili.com - bilibili.cn - www.bilibili.cn - bangumi.bilibili.cn - bilibili.tv - www.bilibili.tv - bangumi.bilibili.tv - miniapp.bilibili.com - live.bilibili.com - - - - - - - - CFBundleURLName - bilibili - CFBundleURLSchemes - bilibili + m.bilibili.com + bilibili.com + www.bilibili.com + bangumi.bilibili.com + bilibili.cn + www.bilibili.cn + bangumi.bilibili.cn + bilibili.tv + www.bilibili.tv + bangumi.bilibili.tv + miniapp.bilibili.com + live.bilibili.com + pili + pilipala + FlutterDeepLinkingEnabled + UIBackgroundModes diff --git a/lib/common/widgets/network_img_layer.dart b/lib/common/widgets/network_img_layer.dart index 0b715a89..fbedfbba 100644 --- a/lib/common/widgets/network_img_layer.dart +++ b/lib/common/widgets/network_img_layer.dart @@ -33,7 +33,11 @@ class NetworkImgLayer extends StatelessWidget { @override Widget build(BuildContext context) { - final int defaultImgQuality = GlobalDataCache().imgQuality; + int defaultImgQuality = 10; + try { + defaultImgQuality = GlobalDataCache().imgQuality; + } catch (_) {} + if (src == '' || src == null) { return placeholder(context); } diff --git a/lib/common/widgets/video_card_v.dart b/lib/common/widgets/video_card_v.dart index 378c9f75..8cec3523 100644 --- a/lib/common/widgets/video_card_v.dart +++ b/lib/common/widgets/video_card_v.dart @@ -282,9 +282,10 @@ class VideoStat extends StatelessWidget { Widget build(BuildContext context) { return Row( children: [ - StatView(view: videoItem.stat.view), + if (videoItem.stat.view != null) StatView(view: videoItem.stat.view), const SizedBox(width: 8), - StatDanMu(danmu: videoItem.stat.danmu), + if (videoItem.stat.danmu != null) + StatDanMu(danmu: videoItem.stat.danmu), if (videoItem is RecVideoItemModel) ...[ crossAxisCount > 1 ? const Spacer() : const SizedBox(width: 8), RichText( diff --git a/lib/http/api.dart b/lib/http/api.dart index 0ad15f8f..e2e24b12 100644 --- a/lib/http/api.dart +++ b/lib/http/api.dart @@ -598,4 +598,7 @@ class Api { /// 更新用户信息 static const String updateAccountInfo = '/x/member/web/update'; + + /// 删除评论 + static const String replyDel = '/x/v2/reply/del'; } diff --git a/lib/http/init.dart b/lib/http/init.dart index eae94ae4..abe8d019 100644 --- a/lib/http/init.dart +++ b/lib/http/init.dart @@ -217,12 +217,13 @@ class Request { /* * get请求 */ - get(url, {data, options, cancelToken, extra}) async { + get(url, {data, Options? options, cancelToken, extra}) async { Response response; - final Options options = Options(); + options ??= Options(); // 如果 options 为 null,则初始化一个新的 Options 对象 ResponseType resType = ResponseType.json; + if (extra != null) { - resType = extra!['resType'] ?? ResponseType.json; + resType = extra['resType'] ?? ResponseType.json; if (extra['ua'] != null) { options.headers = {'user-agent': headerUa(type: extra['ua'])}; } @@ -238,14 +239,11 @@ class Request { ); return response; } on DioException catch (e) { - Response errResponse = Response( - data: { - 'message': await ApiInterceptor.dioError(e) - }, // 将自定义 Map 数据赋值给 Response 的 data 属性 + return Response( + data: {'message': await ApiInterceptor.dioError(e)}, statusCode: 200, requestOptions: RequestOptions(), ); - return errResponse; } } diff --git a/lib/http/member.dart b/lib/http/member.dart index c7b22359..fc99c987 100644 --- a/lib/http/member.dart +++ b/lib/http/member.dart @@ -4,6 +4,7 @@ import 'package:hive/hive.dart'; import 'package:html/parser.dart'; import 'package:pilipala/models/member/article.dart'; import 'package:pilipala/models/member/like.dart'; +import 'package:pilipala/utils/global_data_cache.dart'; import '../common/constants.dart'; import '../models/dynamics/result.dart'; import '../models/follow/result.dart'; @@ -19,14 +20,20 @@ import 'index.dart'; class MemberHttp { static Future memberInfo({ - int? mid, + required int mid, String token = '', }) async { + String? wWebid; + if ((await getWWebid(mid: mid))['status']) { + wWebid = GlobalDataCache().wWebid; + } + Map params = await WbiSign().makSign({ 'mid': mid, 'token': token, 'platform': 'web', 'web_location': 1550101, + ...wWebid != null ? {'w_webid': wWebid} : {}, }); var res = await Request().get( Api.memberInfo, @@ -566,6 +573,10 @@ class MemberHttp { } static Future getWWebid({required int mid}) async { + String? wWebid = GlobalDataCache().wWebid; + if (wWebid != null) { + return {'status': true, 'data': wWebid}; + } var res = await Request().get('https://space.bilibili.com/$mid/article'); String? headContent = parse(res.data).head?.outerHtml; final regex = RegExp( @@ -576,6 +587,7 @@ class MemberHttp { final content = match.group(1); String decodedString = Uri.decodeComponent(content!); Map map = jsonDecode(decodedString); + GlobalDataCache().wWebid = map['access_id']; return {'status': true, 'data': map['access_id']}; } else { return {'status': false, 'data': '请检查登录状态'}; @@ -588,25 +600,20 @@ class MemberHttp { static Future getMemberArticle({ required int mid, required int pn, - required String wWebid, String? offset, }) async { + String? wWebid; + if ((await getWWebid(mid: mid))['status']) { + wWebid = GlobalDataCache().wWebid; + } Map params = await WbiSign().makSign({ 'host_mid': mid, 'page': pn, 'offset': offset, 'web_location': 333.999, - 'w_webid': wWebid, - }); - var res = await Request().get(Api.opusList, data: { - 'host_mid': mid, - 'page': pn, - 'offset': offset, - 'web_location': 333.999, - 'w_webid': wWebid, - 'w_rid': params['w_rid'], - 'wts': params['wts'], + ...wWebid != null ? {'w_webid': wWebid} : {}, }); + var res = await Request().get(Api.opusList, data: params); if (res.data['code'] == 0) { return { 'status': true, diff --git a/lib/http/reply.dart b/lib/http/reply.dart index fc00f06b..c07d9e81 100644 --- a/lib/http/reply.dart +++ b/lib/http/reply.dart @@ -115,4 +115,25 @@ class ReplyHttp { }; } } + + static Future replyDel({ + required int type, //replyType + required int oid, + required int rpid, + }) async { + var res = await Request().post( + Api.replyDel, + queryParameters: { + 'type': type, //type.index + 'oid': oid, + 'rpid': rpid, + 'csrf': await Request.getCsrf(), + }, + ); + if (res.data['code'] == 0) { + return {'status': true, 'msg': '删除成功'}; + } else { + return {'status': false, 'msg': res.data['message']}; + } + } } diff --git a/lib/http/video.dart b/lib/http/video.dart index 95ea6782..406e59f6 100644 --- a/lib/http/video.dart +++ b/lib/http/video.dart @@ -536,7 +536,8 @@ class VideoHttp { // 获取字幕内容 static Future> getSubtitleContent(url) async { var res = await Request().get('https:$url'); - final String content = SubTitleUtils.convertToWebVTT(res.data['body']); + final String content = + await SubTitleUtils.convertToWebVTT(res.data['body']); final List body = res.data['body']; return {'content': content, 'body': body}; } diff --git a/lib/main.dart b/lib/main.dart index fe93da22..3ac97b25 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -60,6 +60,7 @@ void main() async { systemNavigationBarColor: Colors.transparent, systemNavigationBarDividerColor: Colors.transparent, statusBarColor: Colors.transparent, + systemNavigationBarContrastEnforced: false, )); } diff --git a/lib/models/common/gesture_mode.dart b/lib/models/common/gesture_mode.dart index 1149ae12..bf51fd96 100644 --- a/lib/models/common/gesture_mode.dart +++ b/lib/models/common/gesture_mode.dart @@ -4,9 +4,12 @@ enum FullScreenGestureMode { /// 从下滑到上 fromBottomtoTop, + + /// 关闭手势 + none, } extension FullScreenGestureModeExtension on FullScreenGestureMode { - String get values => ['fromToptoBottom', 'fromBottomtoTop'][index]; - String get labels => ['从上往下滑进入全屏', '从下往上滑进入全屏'][index]; + String get values => ['fromToptoBottom', 'fromBottomtoTop', 'none'][index]; + String get labels => ['从上往下滑进入全屏', '从下往上滑进入全屏', '关闭手势'][index]; } diff --git a/lib/models/common/search_type.dart b/lib/models/common/search_type.dart index d7d13aec..843e7954 100644 --- a/lib/models/common/search_type.dart +++ b/lib/models/common/search_type.dart @@ -28,7 +28,7 @@ extension SearchTypeExtension on SearchType { String get label => ['视频', '番剧', '直播间', '用户', '专栏'][index]; } -// 搜索类型为视频、专栏及相簿时 +// 搜索类型为视频时 enum ArchiveFilterType { totalrank, click, @@ -44,3 +44,21 @@ extension ArchiveFilterTypeExtension on ArchiveFilterType { String get description => ['默认排序', '播放多', '新发布', '弹幕多', '收藏多', '评论多', '最多喜欢'][index]; } + +// 搜索类型为专栏时 +enum ArticleFilterType { + // 综合排序 + totalrank, + // 最新发布 + pubdate, + // 最多点击 + click, + // 最多喜欢 + attention, + // 最多评论 + scores, +} + +extension ArticleFilterTypeExtension on ArticleFilterType { + String get description => ['综合排序', '最新发布', '最多点击', '最多喜欢', '最多评论'][index]; +} diff --git a/lib/models/video/later.dart b/lib/models/video/later.dart index f722a231..7829e1c7 100644 --- a/lib/models/video/later.dart +++ b/lib/models/video/later.dart @@ -1,6 +1,7 @@ class MediaVideoItemModel { MediaVideoItemModel({ this.id, + this.aid, this.offset, this.index, this.intro, @@ -14,12 +15,13 @@ class MediaVideoItemModel { this.likeState, this.favState, this.page, + this.cid, this.pages, this.title, this.type, this.upper, this.link, - this.bvId, + this.bvid, this.shortLink, this.rights, this.elecInfo, @@ -32,6 +34,7 @@ class MediaVideoItemModel { }); int? id; + int? aid; int? offset; int? index; String? intro; @@ -45,12 +48,13 @@ class MediaVideoItemModel { int? likeState; int? favState; int? page; + int? cid; List? pages; String? title; int? type; Upper? upper; String? link; - String? bvId; + String? bvid; String? shortLink; Rights? rights; dynamic elecInfo; @@ -64,6 +68,7 @@ class MediaVideoItemModel { factory MediaVideoItemModel.fromJson(Map json) => MediaVideoItemModel( id: json["id"], + aid: json["id"], offset: json["offset"], index: json["index"], intro: json["intro"], @@ -77,6 +82,7 @@ class MediaVideoItemModel { likeState: json["like_state"], favState: json["fav_state"], page: json["page"], + cid: json["pages"] == null ? -1 : json["pages"].first['id'], // json["pages"] 可能为null pages: json["pages"] == null ? [] @@ -85,7 +91,7 @@ class MediaVideoItemModel { type: json["type"], upper: Upper.fromJson(json["upper"]), link: json["link"], - bvId: json["bv_id"], + bvid: json["bv_id"], shortLink: json["short_link"], rights: Rights.fromJson(json["rights"]), elecInfo: json["elec_info"], diff --git a/lib/pages/about/index.dart b/lib/pages/about/index.dart index 81d3c3f4..9164d4e9 100644 --- a/lib/pages/about/index.dart +++ b/lib/pages/about/index.dart @@ -295,7 +295,7 @@ class AboutController extends GetxController { displayTime: const Duration(milliseconds: 500), ).then( (value) => launchUrl( - Uri.parse('https://www.123pan.com/s/9sVqVv-flu0A.html'), + Uri.parse('https://www.123684.com/s/9sVqVv-DEZ0A'), mode: LaunchMode.externalApplication, ), ); @@ -349,7 +349,7 @@ class AboutController extends GetxController { // 官网 webSiteUrl() { launchUrl( - Uri.parse('https://pilipalanet.mysxl.cn'), + Uri.parse('https://pilipala.life'), mode: LaunchMode.externalApplication, ); } diff --git a/lib/pages/bangumi/introduction/view.dart b/lib/pages/bangumi/introduction/view.dart index 94ee24de..188debef 100644 --- a/lib/pages/bangumi/introduction/view.dart +++ b/lib/pages/bangumi/introduction/view.dart @@ -189,8 +189,8 @@ class _BangumiInfoState extends State { Stack( children: [ NetworkImgLayer( - width: 105, - height: 160, + width: 115, + height: 115 / 0.75, src: widget.bangumiDetail!.cover!, ), PBadge( @@ -208,7 +208,7 @@ class _BangumiInfoState extends State { child: InkWell( onTap: () => showIntroDetail(), child: SizedBox( - height: 158, + height: 115 / 0.75, child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, diff --git a/lib/pages/bangumi/view.dart b/lib/pages/bangumi/view.dart index 3adfdc1f..9ec72350 100644 --- a/lib/pages/bangumi/view.dart +++ b/lib/pages/bangumi/view.dart @@ -96,7 +96,8 @@ class _BangumiPageState extends State ), ), SizedBox( - height: 268, + height: Get.size.width / 3 / 0.75 + + MediaQuery.textScalerOf(context).scale(50.0), child: FutureBuilder( future: _futureBuilderFutureFollow, builder: @@ -117,7 +118,6 @@ class _BangumiPageState extends State itemBuilder: (context, index) { return Container( width: Get.size.width / 3, - height: 254, margin: EdgeInsets.only( left: StyleString.safeSpace, right: index == @@ -208,8 +208,8 @@ class _BangumiPageState extends State crossAxisSpacing: StyleString.cardSpace, // 列数 crossAxisCount: 3, - mainAxisExtent: Get.size.width / 3 / 0.65 + - MediaQuery.textScalerOf(context).scale(32.0), + mainAxisExtent: Get.size.width / 3 / 0.75 + + MediaQuery.textScalerOf(context).scale(42.0), ), delegate: SliverChildBuilderDelegate( (BuildContext context, int index) { diff --git a/lib/pages/bangumi/widgets/bangumi_panel.dart b/lib/pages/bangumi/widgets/bangumi_panel.dart index 3df7ce25..3a589db6 100644 --- a/lib/pages/bangumi/widgets/bangumi_panel.dart +++ b/lib/pages/bangumi/widgets/bangumi_panel.dart @@ -86,9 +86,11 @@ class _BangumiPanelState extends State { item.aid, item.cover, ); - if (_bottomSheetController != null) { - _bottomSheetController?.close(); - } + try { + if (_bottomSheetController != null) { + _bottomSheetController?.close(); + } + } catch (_) {} currentIndex.value = i; scrollToIndex(); } diff --git a/lib/pages/bangumi/widgets/bangumu_card_v.dart b/lib/pages/bangumi/widgets/bangumu_card_v.dart index a1d7a931..10d95a1c 100644 --- a/lib/pages/bangumi/widgets/bangumu_card_v.dart +++ b/lib/pages/bangumi/widgets/bangumu_card_v.dart @@ -37,7 +37,7 @@ class BangumiCardV extends StatelessWidget { StyleString.imgRadius, ), child: AspectRatio( - aspectRatio: 0.65, + aspectRatio: 0.75, child: LayoutBuilder(builder: (context, boxConstraints) { final double maxWidth = boxConstraints.maxWidth; final double maxHeight = boxConstraints.maxHeight; diff --git a/lib/pages/home/controller.dart b/lib/pages/home/controller.dart index f197fdfa..ea6e72ba 100644 --- a/lib/pages/home/controller.dart +++ b/lib/pages/home/controller.dart @@ -8,7 +8,7 @@ import 'package:pilipala/utils/storage.dart'; import '../../http/index.dart'; class HomeController extends GetxController with GetTickerProviderStateMixin { - bool flag = false; + bool flag = true; late RxList tabs = [].obs; RxInt initialIndex = 1.obs; late TabController tabController; diff --git a/lib/pages/live_room/controller.dart b/lib/pages/live_room/controller.dart index 34379f64..0d347bf2 100644 --- a/lib/pages/live_room/controller.dart +++ b/lib/pages/live_room/controller.dart @@ -64,7 +64,7 @@ class LiveRoomController extends GetxController { ? liveItem.pic : (liveItem.cover != null && liveItem.cover != '') ? liveItem.cover - : null; + : ''; } Request.getBuvid().then((value) => buvid = value); } diff --git a/lib/pages/live_room/view.dart b/lib/pages/live_room/view.dart index cff0c410..1e0814e9 100644 --- a/lib/pages/live_room/view.dart +++ b/lib/pages/live_room/view.dart @@ -108,6 +108,12 @@ class _LiveRoomPageState extends State @override Widget build(BuildContext context) { + final mediaQuery = MediaQuery.of(context); + final isPortrait = mediaQuery.orientation == Orientation.portrait; + final isLandscape = mediaQuery.orientation == Orientation.landscape; + + final padding = mediaQuery.padding; + Widget videoPlayerPanel = FutureBuilder( future: _futureBuilderFuture, builder: (BuildContext context, AsyncSnapshot snapshot) { @@ -187,10 +193,8 @@ class _LiveRoomPageState extends State children: [ Obx( () => SizedBox( - height: MediaQuery.of(context).padding.top + - (_liveRoomController.isPortrait.value || - MediaQuery.of(context).orientation == - Orientation.landscape + height: padding.top + + (_liveRoomController.isPortrait.value || isLandscape ? 0 : kToolbarHeight), ), @@ -201,21 +205,18 @@ class _LiveRoomPageState extends State if (plPlayerController.isFullScreen.value == true) { plPlayerController.triggerFullScreen(status: false); } - if (MediaQuery.of(context).orientation == - Orientation.landscape) { + if (isLandscape) { verticalScreen(); } }, child: Obx( () => Container( width: Get.size.width, - height: MediaQuery.of(context).orientation == - Orientation.landscape + height: isLandscape ? Get.size.height : !_liveRoomController.isPortrait.value ? Get.size.width * 9 / 16 - : Get.size.height - - MediaQuery.of(context).padding.top, + : Get.size.height - padding.top, clipBehavior: Clip.hardEdge, decoration: const BoxDecoration( borderRadius: BorderRadius.all(Radius.circular(6)), @@ -229,7 +230,7 @@ class _LiveRoomPageState extends State // 定位 快速滑动到底部 Positioned( right: 20, - bottom: MediaQuery.of(context).padding.bottom + 80, + bottom: padding.bottom + 80, child: SlideTransition( position: Tween( begin: const Offset(0, 4), @@ -262,10 +263,7 @@ class _LiveRoomPageState extends State titleSpacing: 0, backgroundColor: Colors.transparent, foregroundColor: Colors.white, - toolbarHeight: - MediaQuery.of(context).orientation == Orientation.portrait - ? 56 - : 0, + toolbarHeight: isPortrait ? 56 : 0, title: FutureBuilder( future: _futureBuilder, builder: (context, snapshot) { @@ -317,35 +315,38 @@ class _LiveRoomPageState extends State ), // 消息列表 Obx( - () => Positioned( - top: MediaQuery.of(context).padding.top + - kToolbarHeight + - (_liveRoomController.isPortrait.value - ? Get.size.width - : Get.size.width * 9 / 16), - bottom: 90 + MediaQuery.of(context).padding.bottom, - left: 0, - right: 0, - child: buildMessageListUI( - context, - _liveRoomController, - _scrollController, + () => Align( + alignment: Alignment.bottomCenter, + child: Container( + margin: EdgeInsets.only( + bottom: 90 + padding.bottom, + ), + height: Get.size.height - + (padding.top + + kToolbarHeight + + (_liveRoomController.isPortrait.value + ? Get.size.width + : Get.size.width * 9 / 16) + + 100 + + padding.bottom), + child: buildMessageListUI( + context, + _liveRoomController, + _scrollController, + ), ), ), ), // 消息输入框 Visibility( - visible: MediaQuery.of(context).orientation == Orientation.portrait, + visible: isPortrait, child: Positioned( bottom: 0, left: 0, right: 0, child: Container( padding: EdgeInsets.only( - left: 14, - right: 14, - top: 4, - bottom: MediaQuery.of(context).padding.bottom + 20), + left: 14, right: 14, top: 4, bottom: padding.bottom + 20), decoration: BoxDecoration( color: Colors.grey.withOpacity(0.1), borderRadius: const BorderRadius.all(Radius.circular(20)), @@ -421,6 +422,7 @@ class _LiveRoomPageState extends State ], ), ); + if (Platform.isAndroid) { return PiPSwitcher( childWhenDisabled: childWhenDisabled, @@ -438,84 +440,82 @@ Widget buildMessageListUI( LiveRoomController liveRoomController, ScrollController scrollController, ) { - return Expanded( - child: Obx( - () => MediaQuery.removePadding( - context: context, - removeTop: true, - removeBottom: true, - child: ShaderMask( - shaderCallback: (Rect bounds) { - return LinearGradient( - begin: Alignment.topCenter, - end: Alignment.bottomCenter, - colors: [ - Colors.transparent, - Colors.black.withOpacity(0.5), - Colors.black, - ], - stops: const [0.01, 0.05, 0.2], - ).createShader(bounds); + return Obx( + () => MediaQuery.removePadding( + context: context, + removeTop: true, + removeBottom: true, + child: ShaderMask( + shaderCallback: (Rect bounds) { + return LinearGradient( + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ + Colors.transparent, + Colors.black.withOpacity(0.5), + Colors.black, + ], + stops: const [0.01, 0.05, 0.2], + ).createShader(bounds); + }, + blendMode: BlendMode.dstIn, + child: GestureDetector( + onTap: () { + // 键盘失去焦点 + FocusScope.of(context).requestFocus(FocusNode()); }, - blendMode: BlendMode.dstIn, - child: GestureDetector( - onTap: () { - // 键盘失去焦点 - FocusScope.of(context).requestFocus(FocusNode()); - }, - child: ListView.builder( - controller: scrollController, - itemCount: liveRoomController.messageList.length, - itemBuilder: (context, index) { - final LiveMessageModel liveMsgItem = - liveRoomController.messageList[index]; - return Align( - alignment: Alignment.centerLeft, - child: Container( - decoration: BoxDecoration( - color: liveRoomController.isPortrait.value - ? Colors.black.withOpacity(0.3) - : Colors.grey.withOpacity(0.1), - borderRadius: const BorderRadius.all(Radius.circular(20)), - ), - margin: EdgeInsets.only( - top: index == 0 ? 20.0 : 0.0, - bottom: 6.0, - left: 14.0, - right: 14.0, - ), - padding: const EdgeInsets.symmetric( - vertical: 3.0, - horizontal: 10.0, - ), - child: Text.rich( - TextSpan( - style: const TextStyle(color: Colors.white), - children: [ - TextSpan( - text: '${liveMsgItem.userName}: ', - style: TextStyle( - color: Colors.white.withOpacity(0.6), - ), - recognizer: TapGestureRecognizer() - ..onTap = () { - // 处理点击事件 - print('Text clicked'); - }, + child: ListView.builder( + controller: scrollController, + itemCount: liveRoomController.messageList.length, + itemBuilder: (context, index) { + final LiveMessageModel liveMsgItem = + liveRoomController.messageList[index]; + return Align( + alignment: Alignment.centerLeft, + child: Container( + decoration: BoxDecoration( + color: liveRoomController.isPortrait.value + ? Colors.black.withOpacity(0.3) + : Colors.grey.withOpacity(0.1), + borderRadius: const BorderRadius.all(Radius.circular(20)), + ), + margin: EdgeInsets.only( + top: index == 0 ? 20.0 : 0.0, + bottom: 6.0, + left: 14.0, + right: 14.0, + ), + padding: const EdgeInsets.symmetric( + vertical: 3.0, + horizontal: 10.0, + ), + child: Text.rich( + TextSpan( + style: const TextStyle(color: Colors.white), + children: [ + TextSpan( + text: '${liveMsgItem.userName}: ', + style: TextStyle( + color: Colors.white.withOpacity(0.6), ), - TextSpan( - children: [ - ...buildMessageTextSpan(context, liveMsgItem) - ], - // text: liveMsgItem.message, - ), - ], - ), + recognizer: TapGestureRecognizer() + ..onTap = () { + // 处理点击事件 + print('Text clicked'); + }, + ), + TextSpan( + children: [ + ...buildMessageTextSpan(context, liveMsgItem) + ], + // text: liveMsgItem.message, + ), + ], ), ), - ); - }, - ), + ), + ); + }, ), ), ), diff --git a/lib/pages/main/controller.dart b/lib/pages/main/controller.dart index 8a0e4418..37c8d174 100644 --- a/lib/pages/main/controller.dart +++ b/lib/pages/main/controller.dart @@ -14,6 +14,7 @@ import '../../models/common/nav_bar_config.dart'; class MainController extends GetxController { List pages = []; + List pagesIds = []; RxList navigationBars = [].obs; late List defaultNavTabs; late List navBarSort; @@ -45,7 +46,8 @@ class MainController extends GetxController { SettingBoxKey.dynamicBadgeMode, defaultValue: DynamicBadgeMode.number.code)]; setNavBarConfig(); - if (dynamicBadgeType.value != DynamicBadgeMode.hidden) { + if (dynamicBadgeType.value != DynamicBadgeMode.hidden && + pagesIds.contains(2)) { getUnreadDynamic(); } enableGradientBg = @@ -114,6 +116,7 @@ class MainController extends GetxController { // 如果找不到匹配项,默认索引设置为0或其他合适的值 selectedIndex = defaultIndex != -1 ? defaultIndex : 0; pages = navigationBars.map((e) => e['page']).toList(); + pagesIds = navigationBars.map((e) => e['id']).toList(); } @override diff --git a/lib/pages/main/view.dart b/lib/pages/main/view.dart index 93ee2d83..b4f028d4 100644 --- a/lib/pages/main/view.dart +++ b/lib/pages/main/view.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:get/get.dart'; import 'package:hive/hive.dart'; import 'package:pilipala/models/common/dynamic_badge_mode.dart'; @@ -22,10 +23,10 @@ class MainApp extends StatefulWidget { class _MainAppState extends State with SingleTickerProviderStateMixin { final MainController _mainController = Get.put(MainController()); - final HomeController _homeController = Get.put(HomeController()); - final RankController _rankController = Get.put(RankController()); - final DynamicsController _dynamicController = Get.put(DynamicsController()); - final MineController _mineController = Get.put(MineController()); + late HomeController _homeController; + RankController? _rankController; + late DynamicsController _dynamicController; + late MineController _mineController; int? _lastSelectTime; //上次点击时间 Box setting = GStrorage.setting; @@ -38,6 +39,7 @@ class _MainAppState extends State with SingleTickerProviderStateMixin { _mainController.pageController = PageController(initialPage: _mainController.selectedIndex); enableMYBar = setting.get(SettingBoxKey.enableMYBar, defaultValue: true); + controllerInit(); } void setIndex(int value) async { @@ -60,18 +62,18 @@ class _MainAppState extends State with SingleTickerProviderStateMixin { } if (currentPage is RankPage) { - if (_rankController.flag) { + if (_rankController!.flag) { // 单击返回顶部 双击并刷新 if (DateTime.now().millisecondsSinceEpoch - _lastSelectTime! < 500) { - _rankController.onRefresh(); + _rankController!.onRefresh(); } else { - _rankController.animateToTop(); + _rankController!.animateToTop(); } _lastSelectTime = DateTime.now().millisecondsSinceEpoch; } - _rankController.flag = true; + _rankController!.flag = true; } else { - _rankController.flag = false; + _rankController?.flag = false; } if (currentPage is DynamicsPage) { @@ -96,6 +98,18 @@ class _MainAppState extends State with SingleTickerProviderStateMixin { } } + void controllerInit() { + _homeController = Get.put(HomeController()); + _dynamicController = Get.put(DynamicsController()); + _mineController = Get.put(MineController()); + if (_mainController.pagesIds.contains(1)) { + _rankController = Get.put(RankController()); + } + if (_mainController.pagesIds.contains(2)) { + _dynamicController = Get.put(DynamicsController()); + } + } + @override void dispose() async { await GStrorage.close(); @@ -112,6 +126,14 @@ class _MainAppState extends State with SingleTickerProviderStateMixin { MediaQuery.sizeOf(context).width * 9 / 16; localCache.put('sheetHeight', sheetHeight); localCache.put('statusBarHeight', statusBarHeight); + + SystemChrome.setSystemUIOverlayStyle( + SystemUiOverlayStyle( + systemNavigationBarColor: Colors.transparent, + systemNavigationBarIconBrightness: + Get.isDarkMode ? Brightness.light : Brightness.dark, + ), + ); return PopScope( canPop: false, onPopInvoked: (bool didPop) async { diff --git a/lib/pages/member/controller.dart b/lib/pages/member/controller.dart index 3b7f24a4..8454cebe 100644 --- a/lib/pages/member/controller.dart +++ b/lib/pages/member/controller.dart @@ -49,6 +49,8 @@ class MemberController extends GetxController { if (res['status']) { memberInfo.value = res['data']; face.value = res['data'].face; + } else { + SmartDialog.showToast('用户信息请求异常:${res['msg']}'); } return res; } @@ -78,42 +80,10 @@ class MemberController extends GetxController { return; } if (attribute.value == 128) { - blockUser(); - return; + modifyRelation('block'); + } else { + modifyRelation('follow'); } - SmartDialog.show( - useSystem: true, - animationType: SmartAnimationType.centerFade_otherSlide, - builder: (BuildContext context) { - return AlertDialog( - title: const Text('提示'), - content: Text(memberInfo.value.isFollowed! ? '取消关注UP主?' : '关注UP主?'), - actions: [ - TextButton( - onPressed: () => SmartDialog.dismiss(), - child: Text( - '点错了', - style: TextStyle(color: Theme.of(context).colorScheme.outline), - ), - ), - TextButton( - onPressed: () async { - await VideoHttp.relationMod( - mid: mid, - act: memberInfo.value.isFollowed! ? 2 : 1, - reSrc: 11, - ); - memberInfo.value.isFollowed = !memberInfo.value.isFollowed!; - relationSearch(); - SmartDialog.dismiss(); - memberInfo.update((val) {}); - }, - child: const Text('确认'), - ) - ], - ); - }, - ); } // 关系查询 @@ -123,24 +93,15 @@ class MemberController extends GetxController { var res = await UserHttp.hasFollow(mid); if (res['status']) { attribute.value = res['data']['attribute']; - switch (attribute.value) { - case 1: - attributeText.value = '悄悄关注'; - break; - case 2: - attributeText.value = '已关注'; - break; - case 6: - attributeText.value = '已互关'; - break; - case 128: - attributeText.value = '已拉黑'; - break; - default: - attributeText.value = '关注'; - } + final Map attributeTextMap = { + 1: '悄悄关注', + 2: '已关注', + 6: '已互关', + 128: '已拉黑', + }; + attributeText.value = attributeTextMap[attribute.value] ?? '关注'; if (res['data']['special'] == 1) { - attributeText.value += 'SP'; + attributeText.value = '特别关注'; } } } @@ -151,16 +112,37 @@ class MemberController extends GetxController { SmartDialog.showToast('账号未登录'); return; } - SmartDialog.show( - useSystem: true, - animationType: SmartAnimationType.centerFade_otherSlide, + modifyRelation('block'); + } + +// 合并关注/取关和拉黑逻辑 + Future modifyRelation(String actionType) async { + if (userInfo == null) { + SmartDialog.showToast('账号未登录'); + return; + } + + String contentText; + int act; + if (actionType == 'follow') { + contentText = memberInfo.value.isFollowed! ? '确定取消关注UP主?' : '确定关注UP主?'; + act = memberInfo.value.isFollowed! ? 2 : 1; + } else if (actionType == 'block') { + contentText = attribute.value != 128 ? '确定拉黑UP主?' : '确定从黑名单移除UP主?'; + act = attribute.value != 128 ? 5 : 6; + } else { + return; + } + + showDialog( + context: Get.context!, builder: (BuildContext context) { return AlertDialog( title: const Text('提示'), - content: Text(attribute.value != 128 ? '确定拉黑UP主?' : '从黑名单移除UP主'), + content: Text(contentText), actions: [ TextButton( - onPressed: () => SmartDialog.dismiss(), + onPressed: () => Navigator.of(context).pop(), child: Text( '点错了', style: TextStyle(color: Theme.of(context).colorScheme.outline), @@ -170,19 +152,26 @@ class MemberController extends GetxController { onPressed: () async { var res = await VideoHttp.relationMod( mid: mid, - act: attribute.value != 128 ? 5 : 6, + act: act, reSrc: 11, ); SmartDialog.dismiss(); if (res['status']) { - attribute.value = attribute.value != 128 ? 128 : 0; - attributeText.value = attribute.value == 128 ? '已拉黑' : '关注'; - memberInfo.value.isFollowed = false; + if (actionType == 'follow') { + memberInfo.value.isFollowed = !memberInfo.value.isFollowed!; + } else if (actionType == 'block') { + attribute.value = attribute.value != 128 ? 128 : 0; + attributeText.value = attribute.value == 128 ? '已拉黑' : '关注'; + memberInfo.value.isFollowed = false; + } relationSearch(); + if (context.mounted) { + Navigator.of(context).pop(); + } memberInfo.update((val) {}); } }, - child: const Text('确认'), + child: const Text('确定'), ) ], ); @@ -228,17 +217,14 @@ class MemberController extends GetxController { // 跳转查看动态 void pushDynamicsPage() => Get.toNamed('/memberDynamics?mid=$mid'); - // 跳转查看投稿 void pushArchivesPage() => Get.toNamed('/memberArchive?mid=$mid'); - - // 跳转查看专栏 - void pushSeasonsPage() {} // 跳转查看最近投币 void pushRecentCoinsPage() async { if (recentCoinsList.isNotEmpty) {} } + // 跳转查看收藏夹 void pushfavPage() => Get.toNamed('/fav?mid=$mid'); // 跳转图文专栏 void pushArticlePage() => Get.toNamed('/memberArticle?mid=$mid'); diff --git a/lib/pages/member/view.dart b/lib/pages/member/view.dart index fafba9dc..df501253 100644 --- a/lib/pages/member/view.dart +++ b/lib/pages/member/view.dart @@ -1,13 +1,13 @@ import 'dart:async'; - import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:get/get.dart'; import 'package:pilipala/common/constants.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart'; +import 'package:pilipala/models/member/info.dart'; import 'package:pilipala/pages/member/index.dart'; import 'package:pilipala/utils/utils.dart'; - +import 'widgets/commen_widget.dart'; import 'widgets/conis.dart'; import 'widgets/like.dart'; import 'widgets/profile.dart'; @@ -65,259 +65,233 @@ class _MemberPageState extends State @override Widget build(BuildContext context) { return Scaffold( - primary: true, - body: Column( - children: [ - AppBar( - title: StreamBuilder( - stream: appbarStream.stream.distinct(), - initialData: false, - builder: (BuildContext context, AsyncSnapshot snapshot) { - return AnimatedOpacity( - opacity: snapshot.data ? 1 : 0, - curve: Curves.easeOut, - duration: const Duration(milliseconds: 500), - child: Row( - children: [ - Row( - children: [ - Obx( - () => NetworkImgLayer( - width: 35, - height: 35, - type: 'avatar', - src: _memberController.face.value, - ), - ), - const SizedBox(width: 10), - Obx( - () => Text( - _memberController.memberInfo.value.name ?? '', - style: TextStyle( - color: - Theme.of(context).colorScheme.onSurface, - fontSize: 14), - ), - ), - ], - ) - ], + appBar: AppBar( + title: StreamBuilder( + stream: appbarStream.stream.distinct(), + initialData: false, + builder: (BuildContext context, AsyncSnapshot snapshot) { + return AnimatedOpacity( + opacity: snapshot.data ? 1 : 0, + curve: Curves.easeOut, + duration: const Duration(milliseconds: 500), + child: Row( + children: [ + Obx( + () => NetworkImgLayer( + width: 35, + height: 35, + type: 'avatar', + src: _memberController.face.value, + ), ), - ); - }, - ), - actions: [ - IconButton( - onPressed: () => Get.toNamed( - '/memberSearch?mid=$mid&uname=${_memberController.memberInfo.value.name!}'), - icon: const Icon(Icons.search_outlined), - ), - PopupMenuButton( - icon: const Icon(Icons.more_vert), - itemBuilder: (BuildContext context) => [ - if (_memberController.ownerMid != _memberController.mid) ...[ - PopupMenuItem( - onTap: () => _memberController.blockUser(), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon(Icons.block, size: 19), - const SizedBox(width: 10), - Text(_memberController.attribute.value != 128 - ? '加入黑名单' - : '移除黑名单'), - ], - ), - ) - ], - PopupMenuItem( - onTap: () => _memberController.shareUser(), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - const Icon(Icons.share_outlined, size: 19), - const SizedBox(width: 10), - Text(_memberController.ownerMid != _memberController.mid - ? '分享UP主' - : '分享我的主页'), - ], + const SizedBox(width: 10), + Obx( + () => Text( + _memberController.memberInfo.value.name ?? '', + style: TextStyle( + color: Theme.of(context).colorScheme.onSurface, + fontSize: 14), ), ), ], ), - const SizedBox(width: 4), - ], + ); + }, + ), + actions: [ + IconButton( + onPressed: () => Get.toNamed( + '/memberSearch?mid=$mid&uname=${_memberController.memberInfo.value.name!}'), + icon: const Icon(Icons.search_outlined), ), - Expanded( - child: SingleChildScrollView( - controller: _extendNestCtr, - child: Padding( - padding: EdgeInsets.only( - bottom: MediaQuery.of(context).padding.bottom + 20, - ), - child: Column( + PopupMenuButton( + icon: const Icon(Icons.more_vert), + itemBuilder: (BuildContext context) => [ + if (_memberController.ownerMid != _memberController.mid) ...[ + PopupMenuItem( + onTap: () => _memberController.blockUser(), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.block, size: 19), + const SizedBox(width: 10), + Text(_memberController.attribute.value != 128 + ? '加入黑名单' + : '移除黑名单'), + ], + ), + ) + ], + PopupMenuItem( + onTap: () => _memberController.shareUser(), + child: Row( + mainAxisSize: MainAxisSize.min, children: [ - profileWidget(), - - /// 动态链接 - Obx( - () => ListTile( - onTap: _memberController.pushDynamicsPage, - title: Text( - '${_memberController.isOwner.value ? '我' : 'Ta'}的动态'), - trailing: - const Icon(Icons.arrow_forward_outlined, size: 19), - ), - ), - - /// 视频 - Obx( - () => ListTile( - onTap: _memberController.pushArchivesPage, - title: Text( - '${_memberController.isOwner.value ? '我' : 'Ta'}的投稿'), - trailing: - const Icon(Icons.arrow_forward_outlined, size: 19), - ), - ), - - /// 他的收藏夹 - Obx( - () => ListTile( - onTap: _memberController.pushfavPage, - title: Text( - '${_memberController.isOwner.value ? '我' : 'Ta'}的收藏'), - trailing: - const Icon(Icons.arrow_forward_outlined, size: 19), - ), - ), - - /// 专栏 - Obx( - () => ListTile( - onTap: _memberController.pushArticlePage, - title: Text( - '${_memberController.isOwner.value ? '我' : 'Ta'}的专栏'), - trailing: - const Icon(Icons.arrow_forward_outlined, size: 19), - ), - ), - - /// 合集 - Obx( - () => ListTile( - title: Text( - '${_memberController.isOwner.value ? '我' : 'Ta'}的合集')), - ), - MediaQuery.removePadding( - removeTop: true, - removeBottom: true, - context: context, - child: FutureBuilder( - future: _memberSeasonsFuture, - builder: (context, snapshot) { - if (snapshot.connectionState == - ConnectionState.done) { - if (snapshot.data == null) { - return const SizedBox(); - } - if (snapshot.data['status']) { - Map data = snapshot.data as Map; - if (data['data'].seasonsList.isEmpty) { - return commenWidget('用户没有设置合集'); - } else { - return MemberSeasonsPanel(data: data['data']); - } - } else { - // 请求错误 - return const SizedBox(); - } - } else { - return const SizedBox(); - } - }, - ), - ), - - /// 追番 - /// 最近投币 - Obx( - () => _memberController.recentCoinsList.isNotEmpty - ? const ListTile(title: Text('最近投币的视频')) - : const SizedBox(), - ), - MediaQuery.removePadding( - removeTop: true, - removeBottom: true, - context: context, - child: Padding( - padding: const EdgeInsets.only( - left: StyleString.safeSpace, - right: StyleString.safeSpace, - ), - child: FutureBuilder( - future: _memberCoinsFuture, - builder: (context, snapshot) { - if (snapshot.connectionState == - ConnectionState.done) { - if (snapshot.data == null) { - return const SizedBox(); - } - if (snapshot.data['status']) { - Map data = snapshot.data as Map; - return MemberCoinsPanel(data: data['data']); - } else { - // 请求错误 - return const SizedBox(); - } - } else { - return const SizedBox(); - } - }, - ), - ), - ), - - /// 最近点赞 - Obx( - () => _memberController.recentLikeList.isNotEmpty - ? const ListTile(title: Text('最近点赞的视频')) - : const SizedBox(), - ), - MediaQuery.removePadding( - removeTop: true, - removeBottom: true, - context: context, - child: Padding( - padding: const EdgeInsets.only( - left: StyleString.safeSpace, - right: StyleString.safeSpace, - ), - child: FutureBuilder( - future: _memberLikeFuture, - builder: (context, snapshot) { - if (snapshot.connectionState == - ConnectionState.done) { - if (snapshot.data == null) { - return const SizedBox(); - } - if (snapshot.data['status']) { - Map data = snapshot.data as Map; - return MemberLikePanel(data: data['data']); - } else { - // 请求错误 - return const SizedBox(); - } - } else { - return const SizedBox(); - } - }, - ), - ), - ), + const Icon(Icons.share_outlined, size: 19), + const SizedBox(width: 10), + Text(_memberController.ownerMid != _memberController.mid + ? '分享UP主' + : '分享我的主页'), ], ), ), + ], + ), + const SizedBox(width: 4), + ], + ), + primary: true, + body: ListView( + controller: _extendNestCtr, + padding: EdgeInsets.only( + bottom: MediaQuery.of(context).padding.bottom + 20, + ), + children: [ + profileWidget(), + + /// 动态链接 + Obx( + () => ListTile( + onTap: _memberController.pushDynamicsPage, + title: Text('${_memberController.isOwner.value ? '我' : 'Ta'}的动态'), + trailing: const Icon(Icons.arrow_forward_outlined, size: 19), + ), + ), + + /// 视频 + Obx( + () => ListTile( + onTap: _memberController.pushArchivesPage, + title: Text('${_memberController.isOwner.value ? '我' : 'Ta'}的投稿'), + trailing: const Icon(Icons.arrow_forward_outlined, size: 19), + ), + ), + + /// 他的收藏夹 + Obx( + () => ListTile( + onTap: _memberController.pushfavPage, + title: Text('${_memberController.isOwner.value ? '我' : 'Ta'}的收藏'), + trailing: const Icon(Icons.arrow_forward_outlined, size: 19), + ), + ), + + /// 专栏 + Obx( + () => ListTile( + onTap: _memberController.pushArticlePage, + title: Text('${_memberController.isOwner.value ? '我' : 'Ta'}的专栏'), + trailing: const Icon(Icons.arrow_forward_outlined, size: 19), + ), + ), + + /// 合集 + Obx( + () => ListTile( + title: Text('${_memberController.isOwner.value ? '我' : 'Ta'}的合集'), + ), + ), + MediaQuery.removePadding( + removeTop: true, + removeBottom: true, + context: context, + child: FutureBuilder( + future: _memberSeasonsFuture, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + if (snapshot.data == null) { + return const SizedBox(); + } + if (snapshot.data['status']) { + Map data = snapshot.data as Map; + if (data['data'].seasonsList.isEmpty) { + return const CommenWidget(msg: '用户没有设置合集'); + } else { + return MemberSeasonsPanel(data: data['data']); + } + } else { + // 请求错误 + return const SizedBox(); + } + } else { + return const SizedBox(); + } + }, + ), + ), + + /// 追番 + /// 最近投币 + Obx( + () => _memberController.recentCoinsList.isNotEmpty + ? const ListTile(title: Text('最近投币的视频')) + : const SizedBox(), + ), + MediaQuery.removePadding( + removeTop: true, + removeBottom: true, + context: context, + child: Padding( + padding: const EdgeInsets.only( + left: StyleString.safeSpace, + right: StyleString.safeSpace, + ), + child: FutureBuilder( + future: _memberCoinsFuture, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + if (snapshot.data == null) { + return const SizedBox(); + } + if (snapshot.data['status']) { + Map data = snapshot.data as Map; + return MemberCoinsPanel(data: data['data']); + } else { + // 请求错误 + return const SizedBox(); + } + } else { + return const SizedBox(); + } + }, + ), + ), + ), + + /// 最近点赞 + Obx( + () => _memberController.recentLikeList.isNotEmpty + ? const ListTile(title: Text('最近点赞的视频')) + : const SizedBox(), + ), + MediaQuery.removePadding( + removeTop: true, + removeBottom: true, + context: context, + child: Padding( + padding: const EdgeInsets.only( + left: StyleString.safeSpace, + right: StyleString.safeSpace, + ), + child: FutureBuilder( + future: _memberLikeFuture, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + if (snapshot.data == null) { + return const SizedBox(); + } + if (snapshot.data['status']) { + Map data = snapshot.data as Map; + return MemberLikePanel(data: data['data']); + } else { + // 请求错误 + return const SizedBox(); + } + } else { + return const SizedBox(); + } + }, + ), ), ), ], @@ -334,115 +308,90 @@ class _MemberPageState extends State if (snapshot.connectionState == ConnectionState.done) { Map? data = snapshot.data; if (data != null && data['status']) { + Rx memberInfo = _memberController.memberInfo; return Obx( - () => Stack( - alignment: AlignmentDirectional.center, + () => Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, + ProfilePanel(ctr: _memberController), + const SizedBox(height: 20), + Row( children: [ - ProfilePanel(ctr: _memberController), - const SizedBox(height: 20), - Row( - children: [ - Flexible( - child: Text( - _memberController.memberInfo.value.name!, - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: Theme.of(context) - .textTheme - .titleMedium! - .copyWith( - fontWeight: FontWeight.bold, - color: _memberController.memberInfo.value - .vip!.nicknameColor != - null - ? Color(_memberController.memberInfo - .value.vip!.nicknameColor!) - : null), - )), - const SizedBox(width: 2), - if (_memberController.memberInfo.value.sex == '女') - const Icon( - FontAwesomeIcons.venus, - size: 14, - color: Colors.pink, - ), - if (_memberController.memberInfo.value.sex == '男') - const Icon( - FontAwesomeIcons.mars, - size: 14, - color: Colors.blue, - ), - const SizedBox(width: 4), - Image.asset( - 'assets/images/lv/lv${_memberController.memberInfo.value.level}.png', - height: 11, - ), - const SizedBox(width: 6), - if (_memberController - .memberInfo.value.vip!.status == - 1 && - _memberController.memberInfo.value.vip! - .label!['img_label_uri_hans'] != - '') ...[ - Image.network( - _memberController.memberInfo.value.vip! - .label!['img_label_uri_hans'], - height: 20, - ), - ] else if (_memberController - .memberInfo.value.vip!.status == - 1 && - _memberController.memberInfo.value.vip! - .label!['img_label_uri_hans_static'] != - '') ...[ - Image.network( - _memberController.memberInfo.value.vip! - .label!['img_label_uri_hans_static'], - height: 20, - ), - ] - ], + Flexible( + child: Text( + memberInfo.value.name!, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: Theme.of(context) + .textTheme + .titleMedium! + .copyWith( + fontWeight: FontWeight.bold, + color: memberInfo.value.vip!.nicknameColor != + null + ? Color(_memberController + .memberInfo.value.vip!.nicknameColor!) + : null), + )), + const SizedBox(width: 2), + if (memberInfo.value.sex == '女') + const Icon( + FontAwesomeIcons.venus, + size: 14, + color: Colors.pink, + ), + if (memberInfo.value.sex == '男') + const Icon( + FontAwesomeIcons.mars, + size: 14, + color: Colors.blue, + ), + const SizedBox(width: 4), + Image.asset( + 'assets/images/lv/lv${memberInfo.value.level}.png', + height: 11, ), - if (_memberController - .memberInfo.value.official!['title'] != - '') ...[ - const SizedBox(height: 6), - Text.rich( - maxLines: 2, - TextSpan( - text: _memberController - .memberInfo.value.official!['role'] == - 1 - ? '个人认证:' - : '企业认证:', - style: TextStyle( - color: Theme.of(context).colorScheme.primary, - ), - children: [ - TextSpan( - text: _memberController - .memberInfo.value.official!['title'], - ), - ], - ), - softWrap: true, + const SizedBox(width: 6), + if (memberInfo.value.vip!.status == 1 && + memberInfo + .value.vip!.label!['img_label_uri_hans'] != + '') ...[ + Image.network( + memberInfo.value.vip!.label!['img_label_uri_hans'], + height: 20, ), - ], - const SizedBox(height: 6), - if (_memberController.memberInfo.value.sign != '') - SelectableText( - _memberController.memberInfo.value.sign!, + ] else if (memberInfo.value.vip!.status == 1 && + memberInfo.value.vip! + .label!['img_label_uri_hans_static'] != + '') ...[ + Image.network( + memberInfo + .value.vip!.label!['img_label_uri_hans_static'], + height: 20, ), + ] ], ), + if (memberInfo.value.official!['title'] != '') ...[ + const SizedBox(height: 6), + Text( + memberInfo.value.official!['role'] == 1 + ? '个人认证:${memberInfo.value.official!['title']}' + : '企业认证:${memberInfo.value.official!['title']}', + maxLines: 2, + softWrap: true, + style: TextStyle( + color: Theme.of(context).colorScheme.primary, + ), + ), + ], + const SizedBox(height: 6), + SelectableText(memberInfo.value.sign ?? ''), ], ), ); } else { - return const SizedBox(); + return ProfilePanel(ctr: _memberController, loadingStatus: true); } } else { // 骨架屏 @@ -452,22 +401,4 @@ class _MemberPageState extends State ), ); } - - Widget commenWidget(msg) { - return Padding( - padding: const EdgeInsets.only( - top: 20, - bottom: 30, - ), - child: Center( - child: Text( - msg, - style: Theme.of(context) - .textTheme - .labelMedium! - .copyWith(color: Theme.of(context).colorScheme.outline), - ), - ), - ); - } } diff --git a/lib/pages/member/widgets/commen_widget.dart b/lib/pages/member/widgets/commen_widget.dart new file mode 100644 index 00000000..0c92803e --- /dev/null +++ b/lib/pages/member/widgets/commen_widget.dart @@ -0,0 +1,24 @@ +import 'package:flutter/material.dart'; + +class CommenWidget extends StatelessWidget { + final String msg; + + const CommenWidget({required this.msg, Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 20.0, horizontal: 10.0), + child: Center( + child: Text( + msg, + textAlign: TextAlign.center, + style: Theme.of(context) + .textTheme + .labelMedium! + .copyWith(color: Theme.of(context).colorScheme.outline), + ), + ), + ); + } +} diff --git a/lib/pages/member/widgets/profile.dart b/lib/pages/member/widgets/profile.dart index 596ace46..2509160f 100644 --- a/lib/pages/member/widgets/profile.dart +++ b/lib/pages/member/widgets/profile.dart @@ -17,253 +17,245 @@ class ProfilePanel extends StatelessWidget { @override Widget build(BuildContext context) { MemberInfoModel memberInfo = ctr.memberInfo.value; - return Builder( - builder: ((context) { - return Padding( - padding: - EdgeInsets.only(top: MediaQuery.of(context).padding.top - 20), - child: Row( - children: [ - Hero( - tag: ctr.heroTag!, - child: Stack( - children: [ - NetworkImgLayer( - width: 90, - height: 90, - type: 'avatar', - src: !loadingStatus ? memberInfo.face : ctr.face.value, - ), - if (!loadingStatus && - memberInfo.liveRoom != null && - memberInfo.liveRoom!.liveStatus == 1) - Positioned( - bottom: 0, - left: 14, - child: GestureDetector( - onTap: () { - LiveItemModel liveItem = LiveItemModel.fromJson({ - 'title': memberInfo.liveRoom!.title, - 'uname': memberInfo.name, - 'face': memberInfo.face, - 'roomid': memberInfo.liveRoom!.roomId, - 'watched_show': memberInfo.liveRoom!.watchedShow, - }); - Get.toNamed( - '/liveRoom?roomid=${memberInfo.liveRoom!.roomId}', - arguments: {'liveItem': liveItem}, - ); - }, - child: Container( - padding: const EdgeInsets.fromLTRB(6, 2, 6, 2), - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.primary, - borderRadius: - const BorderRadius.all(Radius.circular(10)), - ), - child: Row(children: [ - Image.asset( - 'assets/images/live.gif', - height: 10, - ), - Text( - ' 直播中', - style: TextStyle( - color: Colors.white, - fontSize: Theme.of(context) - .textTheme - .labelSmall! - .fontSize), - ) - ]), - ), - ), - ) - ], + final int? mid = memberInfo.mid; + final String? name = memberInfo.name; + + Map buildStatItem({ + required String label, + required String value, + required VoidCallback onTap, + }) { + return { + 'label': label, + 'value': value, + 'fn': onTap, + }; + } + + final List> statList = [ + buildStatItem( + label: '关注', + value: !loadingStatus ? "${ctr.userStat!['following']}" : '-', + onTap: () { + Get.toNamed('/follow?mid=$mid&name=$name'); + }, + ), + buildStatItem( + label: '粉丝', + value: !loadingStatus + ? ctr.userStat!['follower'] != null + ? Utils.numFormat(ctr.userStat!['follower']) + : '-' + : '-', + onTap: () { + Get.toNamed('/fan?mid=$mid&name=$name'); + }, + ), + buildStatItem( + label: '获赞', + value: !loadingStatus + ? ctr.userStat!['likes'] != null + ? Utils.numFormat(ctr.userStat!['likes']) + : '-' + : '-', + onTap: () {}, + ), + ]; + + return Padding( + padding: const EdgeInsets.only(top: 30, left: 4), + child: Row( + children: [ + Hero( + tag: ctr.heroTag!, + child: Stack( + children: [ + NetworkImgLayer( + width: 90, + height: 90, + type: 'avatar', + src: !loadingStatus ? memberInfo.face : ctr.face.value, ), - ), - const SizedBox(width: 12), - Expanded( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - padding: - const EdgeInsets.only(top: 10, left: 10, right: 10), - child: Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - InkWell( - onTap: () { - Get.toNamed( - '/follow?mid=${memberInfo.mid}&name=${memberInfo.name}'); - }, - child: Column( - children: [ - Text( - !loadingStatus - ? ctr.userStat!['following'].toString() - : '-', - style: const TextStyle( - fontWeight: FontWeight.bold), - ), - Text( - '关注', - style: TextStyle( - fontSize: Theme.of(context) - .textTheme - .labelMedium! - .fontSize), - ) - ], - ), + if (!loadingStatus && + memberInfo.liveRoom != null && + memberInfo.liveRoom!.liveStatus == 1) + Positioned( + bottom: 0, + left: 14, + child: GestureDetector( + onTap: () { + LiveItemModel liveItem = LiveItemModel( + title: memberInfo.liveRoom!.title, + uname: memberInfo.name, + face: memberInfo.face, + roomId: memberInfo.liveRoom!.roomId, + watchedShow: memberInfo.liveRoom!.watchedShow, + ); + Get.toNamed( + '/liveRoom?roomid=${memberInfo.liveRoom!.roomId}', + arguments: {'liveItem': liveItem}, + ); + }, + child: Container( + padding: const EdgeInsets.fromLTRB(6, 2, 6, 2), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.primary, + borderRadius: + const BorderRadius.all(Radius.circular(10)), + ), + child: Row(children: [ + Image.asset( + 'assets/images/live.gif', + height: 10, ), - InkWell( - onTap: () { - Get.toNamed( - '/fan?mid=${memberInfo.mid}&name=${memberInfo.name}'); - }, - child: Column( - children: [ - Text( - !loadingStatus - ? ctr.userStat!['follower'] != null - ? Utils.numFormat( - ctr.userStat!['follower'], - ) - : '-' - : '-', - style: const TextStyle( - fontWeight: FontWeight.bold)), - Text( - '粉丝', - style: TextStyle( - fontSize: Theme.of(context) - .textTheme - .labelMedium! - .fontSize), - ) - ], - ), - ), - Column( - children: [ - Text( - !loadingStatus - ? ctr.userStat!['likes'] != null - ? Utils.numFormat( - ctr.userStat!['likes'], - ) - : '-' - : '-', - style: const TextStyle( - fontWeight: FontWeight.bold)), - Text( - '获赞', - style: TextStyle( - fontSize: Theme.of(context) - .textTheme - .labelMedium! - .fontSize), - ) - ], - ), - ], + Text( + ' 直播中', + style: TextStyle( + color: Colors.white, + fontSize: Theme.of(context) + .textTheme + .labelSmall! + .fontSize), + ) + ]), ), ), - const SizedBox(height: 10), - if (ctr.ownerMid != ctr.mid && ctr.ownerMid != -1) ...[ - Row( - children: [ - Obx( - () => Expanded( - child: TextButton( - onPressed: () => loadingStatus - ? null - : ctr.actionRelationMod(), - style: TextButton.styleFrom( - foregroundColor: ctr.attribute.value == -1 - ? Colors.transparent - : ctr.attribute.value != 0 - ? Theme.of(context) - .colorScheme - .outline - : Theme.of(context) - .colorScheme - .onPrimary, - backgroundColor: ctr.attribute.value != 0 - ? Theme.of(context) - .colorScheme - .onInverseSurface - : Theme.of(context) - .colorScheme - .primary, // 设置按钮背景色 - ), - child: Obx(() => Text(ctr.attributeText.value)), - ), - ), - ), - const SizedBox(width: 8), - Expanded( - child: TextButton( - onPressed: () { - Get.toNamed( - '/whisperDetail', - parameters: { - 'name': memberInfo.name!, - 'face': memberInfo.face!, - 'mid': memberInfo.mid.toString(), - 'heroTag': ctr.heroTag!, - }, - ); - }, - style: TextButton.styleFrom( - backgroundColor: Theme.of(context) - .colorScheme - .onInverseSurface, - ), - child: const Text('发消息'), - ), - ) - ], - ) - ], - if (ctr.ownerMid == ctr.mid && ctr.ownerMid != -1) ...[ - TextButton( - onPressed: () { - Get.toNamed('/mineEdit'); - }, - style: TextButton.styleFrom( - padding: const EdgeInsets.only(left: 80, right: 80), - foregroundColor: - Theme.of(context).colorScheme.onPrimary, - backgroundColor: - Theme.of(context).colorScheme.primary, - ), - child: const Text('编辑资料'), - ) - ], - if (ctr.ownerMid == -1) ...[ - TextButton( - onPressed: () {}, - style: TextButton.styleFrom( - padding: const EdgeInsets.only(left: 80, right: 80), - foregroundColor: - Theme.of(context).colorScheme.outline, - backgroundColor: - Theme.of(context).colorScheme.onInverseSurface, - ), - child: const Text('未登录'), - ) - ] - ], - ), - ), - ], + ) + ], + ), ), - ); - }), + const SizedBox(width: 12), + Expanded( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 10), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: statList.map((item) { + return buildStatColumn( + context, + item['label'], + item['value'], + item['fn'], + ); + }).toList(), + ), + ), + const SizedBox(height: 16), + if (ctr.ownerMid != ctr.mid && ctr.ownerMid != -1) + buildActionButtons(context, ctr, memberInfo), + if (ctr.ownerMid == ctr.mid && ctr.ownerMid != -1) + buildEditProfileButton(context), + if (ctr.ownerMid == -1) buildNotLoggedInButton(context), + ], + ), + ), + ], + ), + ); + } + + Widget buildStatColumn( + BuildContext context, + String label, + String value, + VoidCallback? onTap, + ) { + return InkWell( + onTap: onTap, + child: Column( + children: [ + Text( + value, + style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 16), + ), + Text( + label, + style: TextStyle( + fontSize: Theme.of(context).textTheme.labelMedium!.fontSize, + ), + ), + ], + ), + ); + } + + Widget buildActionButtons( + BuildContext context, + dynamic ctr, + MemberInfoModel memberInfo, + ) { + ColorScheme colorScheme = Theme.of(context).colorScheme; + return Row( + children: [ + const SizedBox(width: 20), + Obx( + () => Expanded( + child: TextButton( + onPressed: () => loadingStatus ? null : ctr.actionRelationMod(), + style: TextButton.styleFrom( + foregroundColor: ctr.attribute.value == -1 + ? Colors.transparent + : ctr.attribute.value != 0 + ? colorScheme.outline + : colorScheme.onPrimary, + backgroundColor: ctr.attribute.value != 0 + ? colorScheme.onInverseSurface + : colorScheme.primary, + ), + child: Obx(() => Text(ctr.attributeText.value)), + ), + ), + ), + const SizedBox(width: 8), + Expanded( + child: TextButton( + onPressed: () { + Get.toNamed( + '/whisperDetail', + parameters: { + 'name': memberInfo.name!, + 'face': memberInfo.face!, + 'mid': memberInfo.mid.toString(), + 'heroTag': ctr.heroTag!, + }, + ); + }, + style: TextButton.styleFrom( + backgroundColor: colorScheme.onInverseSurface, + ), + child: const Text('发消息'), + ), + ), + ], + ); + } + + Widget buildEditProfileButton(BuildContext context) { + return TextButton( + onPressed: () { + Get.toNamed('/mineEdit'); + }, + style: TextButton.styleFrom( + padding: const EdgeInsets.symmetric(horizontal: 80), + foregroundColor: Theme.of(context).colorScheme.onPrimary, + backgroundColor: Theme.of(context).colorScheme.primary, + ), + child: const Text('编辑资料'), + ); + } + + Widget buildNotLoggedInButton(BuildContext context) { + return TextButton( + onPressed: () {}, + style: TextButton.styleFrom( + padding: const EdgeInsets.symmetric(horizontal: 80), + foregroundColor: Theme.of(context).colorScheme.outline, + backgroundColor: Theme.of(context).colorScheme.onInverseSurface, + ), + child: const Text('未登录'), ); } } diff --git a/lib/pages/member_article/controller.dart b/lib/pages/member_article/controller.dart index cffce2fe..d79fb4a6 100644 --- a/lib/pages/member_article/controller.dart +++ b/lib/pages/member_article/controller.dart @@ -10,7 +10,6 @@ class MemberArticleController extends GetxController { int pn = 1; String? offset; bool hasMore = true; - String? wWebid; RxBool isLoading = false.obs; RxList articleList = [].obs; @@ -20,25 +19,11 @@ class MemberArticleController extends GetxController { mid = int.parse(Get.parameters['mid']!); } - // 获取wWebid - Future getWWebid() async { - var res = await MemberHttp.getWWebid(mid: mid); - if (res['status']) { - wWebid = res['data']; - } else { - wWebid = '-1'; - SmartDialog.showToast(res['msg']); - } - } - Future getMemberArticle(type) async { if (isLoading.value) { return; } isLoading.value = true; - if (wWebid == null) { - await getWWebid(); - } if (type == 'init') { pn = 1; articleList.clear(); @@ -47,7 +32,6 @@ class MemberArticleController extends GetxController { mid: mid, pn: pn, offset: offset, - wWebid: wWebid!, ); if (res['status']) { offset = res['data'].offset; diff --git a/lib/pages/message/like/view.dart b/lib/pages/message/like/view.dart index 8b061505..b30a0c4f 100644 --- a/lib/pages/message/like/view.dart +++ b/lib/pages/message/like/view.dart @@ -125,21 +125,29 @@ class LikeItem extends StatelessWidget { Color outline = Theme.of(context).colorScheme.outline; final nickNameList = item.users!.map((e) => e.nickname).take(2).toList(); int usersLen = item.users!.length > 3 ? 3 : item.users!.length; - final String bvid = item.item!.uri!.split('/').last; + final Uri uri = Uri.parse(item.item!.uri!); + final String path = uri.path; + final String bvid = path.split('/').last; + + /// bilibili:// + final Uri nativeUri = Uri.parse(item.item!.nativeUri!); + final Map queryParameters = nativeUri.queryParameters; + final String type = item.item!.type!; + // cid + final String? argCid = queryParameters['cid']; // 页码 - final String page = - item.item!.nativeUri!.split('page=').last.split('&').first; + final String? page = queryParameters['page']; // 根评论id - final String commentRootId = - item.item!.nativeUri!.split('comment_root_id=').last.split('&').first; + final String? commentRootId = queryParameters['comment_root_id']; // 二级评论id - final String commentSecondaryId = - item.item!.nativeUri!.split('comment_secondary_id=').last; + final String? commentSecondaryId = queryParameters['comment_secondary_id']; return InkWell( onTap: () async { try { - final int cid = await SearchHttp.ab2c(bvid: bvid); + final int cid = argCid != null + ? int.parse(argCid) + : await SearchHttp.ab2c(bvid: bvid); final String heroTag = Utils.makeHeroTag(bvid); Get.toNamed( '/video?bvid=$bvid&cid=$cid', @@ -148,8 +156,8 @@ class LikeItem extends StatelessWidget { 'heroTag': heroTag, }, ); - } catch (_) { - SmartDialog.showToast('视频可能失效了'); + } catch (e) { + SmartDialog.showToast('视频可能失效了$e'); } }, child: Stack( @@ -222,7 +230,7 @@ class LikeItem extends StatelessWidget { ), ), const SizedBox(width: 25), - if (item.item!.type! == 'reply') + if (type == 'reply' || type == 'danmu') Container( width: 60, height: 60, @@ -234,7 +242,7 @@ class LikeItem extends StatelessWidget { overflow: TextOverflow.ellipsis, ), ), - if (item.item!.type! == 'video') + if (type == 'video') NetworkImgLayer( width: 60, height: 60, diff --git a/lib/pages/opus/view.dart b/lib/pages/opus/view.dart index 434a9405..42c0c419 100644 --- a/lib/pages/opus/view.dart +++ b/lib/pages/opus/view.dart @@ -157,7 +157,7 @@ class _OpusPageState extends State { Container( alignment: TextHelper.getAlignment(paragraph.align), margin: const EdgeInsets.only(bottom: 10), - child: Text.rich( + child: SelectableText.rich( TextSpan( children: paragraph.text?.nodes?.map((node) { return TextHelper.buildTextSpan( diff --git a/lib/pages/read/controller.dart b/lib/pages/read/controller.dart index 178ebfda..b580b964 100644 --- a/lib/pages/read/controller.dart +++ b/lib/pages/read/controller.dart @@ -20,7 +20,7 @@ class ReadPageController extends GetxController { super.onInit(); title.value = Get.parameters['title'] ?? ''; id = Get.parameters['id']!; - articleType = Get.parameters['articleType']!; + articleType = Get.parameters['articleType'] ?? 'read'; url = 'https://www.bilibili.com/read/cv$id'; scrollController.addListener(_scrollListener); fetchViewInfo(); diff --git a/lib/pages/read/view.dart b/lib/pages/read/view.dart index d37eeae5..710934eb 100644 --- a/lib/pages/read/view.dart +++ b/lib/pages/read/view.dart @@ -126,7 +126,6 @@ class _ReadPageState extends State { Widget _buildContent(ReadDataModel cvData) { final List picList = _extractPicList(cvData); final List imgList = extractDataSrc(cvData.readInfo!.content!); - return Padding( padding: EdgeInsets.fromLTRB( 16, 0, 16, MediaQuery.of(context).padding.bottom + 40), @@ -163,9 +162,11 @@ class _ReadPageState extends State { padding: const EdgeInsets.only(bottom: 20), child: _buildAuthorWidget(cvData), ), - HtmlRender( - htmlContent: cvData.readInfo!.content!, - imgList: imgList, + SelectionArea( + child: HtmlRender( + htmlContent: cvData.readInfo!.content!, + imgList: imgList, + ), ), ], ); @@ -206,7 +207,7 @@ class _ReadPageState extends State { return Container( alignment: TextHelper.getAlignment(paragraph.align), margin: const EdgeInsets.only(bottom: 10), - child: Text.rich( + child: SelectableText.rich( TextSpan( children: paragraph.text?.nodes?.map((node) { return TextHelper.buildTextSpan(node, paragraph.align, context); diff --git a/lib/pages/search_panel/controller.dart b/lib/pages/search_panel/controller.dart index dc0b2bac..2d1aa228 100644 --- a/lib/pages/search_panel/controller.dart +++ b/lib/pages/search_panel/controller.dart @@ -24,7 +24,9 @@ class SearchPanelController extends GetxController { searchType: searchType!, keyword: keyword!, page: page.value, - order: searchType!.type != 'video' ? null : order.value, + order: !['video', 'article'].contains(searchType!.type) + ? null + : (order.value == '' ? null : order.value), duration: searchType!.type != 'video' ? null : duration.value, tids: searchType!.type != 'video' ? null : tids.value, ); diff --git a/lib/pages/search_panel/view.dart b/lib/pages/search_panel/view.dart index c5824d70..fa669489 100644 --- a/lib/pages/search_panel/view.dart +++ b/lib/pages/search_panel/view.dart @@ -1,3 +1,5 @@ +// ignore_for_file: invalid_use_of_protected_member + import 'package:easy_debounce/easy_throttle.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; @@ -45,6 +47,11 @@ class _SearchPanelState extends State ), tag: widget.searchType!.type + widget.keyword!, ); + + /// 专栏默认排序 + if (widget.searchType == SearchType.article) { + _searchPanelController.order.value = 'totalrank'; + } scrollController = _searchPanelController.scrollController; scrollController.addListener(() async { if (scrollController.position.pixels >= @@ -84,7 +91,6 @@ class _SearchPanelState extends State case SearchType.video: return SearchVideoPanel( ctr: _searchPanelController, - // ignore: invalid_use_of_protected_member list: list.value, ); case SearchType.media_bangumi: @@ -94,7 +100,10 @@ class _SearchPanelState extends State case SearchType.live_room: return searchLivePanel(context, ctr, list); case SearchType.article: - return searchArticlePanel(context, ctr, list); + return SearchArticlePanel( + ctr: _searchPanelController, + list: list.value, + ); default: return const SizedBox(); } diff --git a/lib/pages/search_panel/widgets/article_panel.dart b/lib/pages/search_panel/widgets/article_panel.dart index dd53de66..be08ed56 100644 --- a/lib/pages/search_panel/widgets/article_panel.dart +++ b/lib/pages/search_panel/widgets/article_panel.dart @@ -1,106 +1,249 @@ import 'package:flutter/material.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:pilipala/common/constants.dart'; +import 'package:pilipala/common/widgets/http_error.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart'; +import 'package:pilipala/models/common/search_type.dart'; +import 'package:pilipala/pages/search_panel/index.dart'; import 'package:pilipala/utils/utils.dart'; +class SearchArticlePanel extends StatelessWidget { + SearchArticlePanel({ + required this.ctr, + this.list, + Key? key, + }) : super(key: key); + + final SearchPanelController ctr; + final List? list; + + final ArticlePanelController controller = Get.put(ArticlePanelController()); + + @override + Widget build(BuildContext context) { + return Stack( + alignment: Alignment.topCenter, + children: [ + searchArticlePanel(context, ctr, list), + Container( + width: double.infinity, + height: 36, + padding: const EdgeInsets.only(left: 8, top: 0, right: 8), + child: Row( + children: [ + Expanded( + child: SingleChildScrollView( + scrollDirection: Axis.horizontal, + child: Obx( + () => Wrap( + // spacing: , + children: [ + for (var i in controller.filterList) ...[ + CustomFilterChip( + label: i['label'], + type: i['type'], + selectedType: controller.selectedType.value, + callFn: (bool selected) async { + controller.selectedType.value = i['type']; + ctr.order.value = + i['type'].toString().split('.').last; + SmartDialog.showLoading(msg: 'loading'); + await ctr.onRefresh(); + SmartDialog.dismiss(); + }, + ), + ] + ], + ), + ), + ), + ), + ], + ), + ), + ], + ); + } +} + Widget searchArticlePanel(BuildContext context, ctr, list) { TextStyle textStyle = TextStyle( fontSize: Theme.of(context).textTheme.labelSmall!.fontSize, color: Theme.of(context).colorScheme.outline); - return ListView.builder( - controller: ctr!.scrollController, - itemCount: list.length, - itemBuilder: (context, index) { - return InkWell( - onTap: () { - Get.toNamed('/read', parameters: { - 'title': list[index].subTitle, - 'id': list[index].id.toString(), - 'articleType': 'read' - }); - }, - child: Padding( - padding: const EdgeInsets.fromLTRB( - StyleString.safeSpace, 5, StyleString.safeSpace, 5), - child: LayoutBuilder(builder: (context, boxConstraints) { - final double width = (boxConstraints.maxWidth - - StyleString.cardSpace * - 6 / - MediaQuery.textScalerOf(context).scale(1.0)) / - 2; - return Container( - constraints: const BoxConstraints(minHeight: 88), - height: width / StyleString.aspectRatio, - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (list[index].imageUrls != null && - list[index].imageUrls.isNotEmpty) - AspectRatio( - aspectRatio: StyleString.aspectRatio, - child: LayoutBuilder(builder: (context, boxConstraints) { - double maxWidth = boxConstraints.maxWidth; - double maxHeight = boxConstraints.maxHeight; - return NetworkImgLayer( - width: maxWidth, - height: maxHeight, - src: list[index].imageUrls.first, - ); - }), - ), - Expanded( - child: Padding( - padding: const EdgeInsets.fromLTRB(10, 2, 6, 0), - child: Column( - mainAxisSize: MainAxisSize.min, + return Padding( + padding: const EdgeInsets.only(top: 36), + child: list!.isNotEmpty + ? ListView.builder( + controller: ctr!.scrollController, + addAutomaticKeepAlives: false, + addRepaintBoundaries: false, + itemCount: list.length, + itemBuilder: (context, index) { + return InkWell( + onTap: () { + Get.toNamed('/read', parameters: { + 'title': list[index].subTitle, + 'id': list[index].id.toString(), + 'articleType': 'read' + }); + }, + child: Padding( + padding: const EdgeInsets.fromLTRB( + StyleString.safeSpace, 5, StyleString.safeSpace, 5), + child: LayoutBuilder(builder: (context, boxConstraints) { + final double width = (boxConstraints.maxWidth - + StyleString.cardSpace * + 6 / + MediaQuery.textScalerOf(context).scale(1.0)) / + 2; + return Container( + constraints: const BoxConstraints(minHeight: 88), + height: width / StyleString.aspectRatio, + child: Row( crossAxisAlignment: CrossAxisAlignment.start, - children: [ - RichText( - maxLines: 2, - text: TextSpan( - children: [ - for (var i in list[index].title) ...[ - TextSpan( - text: i['text'], - style: TextStyle( - fontWeight: FontWeight.w500, - letterSpacing: 0.3, - color: i['type'] == 'em' - ? Theme.of(context) - .colorScheme - .primary - : Theme.of(context) - .colorScheme - .onSurface, + children: [ + if (list[index].imageUrls != null && + list[index].imageUrls.isNotEmpty) + AspectRatio( + aspectRatio: StyleString.aspectRatio, + child: LayoutBuilder( + builder: (context, boxConstraints) { + double maxWidth = boxConstraints.maxWidth; + double maxHeight = boxConstraints.maxHeight; + return NetworkImgLayer( + width: maxWidth, + height: maxHeight, + src: list[index].imageUrls.first, + ); + }), + ), + Expanded( + child: Padding( + padding: const EdgeInsets.fromLTRB(10, 2, 6, 0), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + RichText( + maxLines: 2, + text: TextSpan( + children: [ + for (var i in list[index].title) ...[ + TextSpan( + text: i['text'], + style: TextStyle( + fontWeight: FontWeight.w500, + letterSpacing: 0.3, + color: i['type'] == 'em' + ? Theme.of(context) + .colorScheme + .primary + : Theme.of(context) + .colorScheme + .onSurface, + ), + ), + ] + ], ), ), - ] - ], + const Spacer(), + Text( + Utils.dateFormat(list[index].pubTime, + formatType: 'detail'), + style: textStyle), + Row( + children: [ + Text('${list[index].view}浏览', + style: textStyle), + Text(' • ', style: textStyle), + Text('${list[index].reply}评论', + style: textStyle), + ], + ), + ], + ), ), ), - const Spacer(), - Text( - Utils.dateFormat(list[index].pubTime, - formatType: 'detail'), - style: textStyle), - Row( - children: [ - Text('${list[index].view}浏览', style: textStyle), - Text(' • ', style: textStyle), - Text('${list[index].reply}评论', style: textStyle), - ], - ), ], ), - ), - ), - ], - ), - ); - }), - ), - ); - }, + ); + }), + ), + ); + }, + ) + : CustomScrollView( + slivers: [ + HttpError( + errMsg: '没有数据', + isShowBtn: false, + fn: () => {}, + ) + ], + ), ); } + +class CustomFilterChip extends StatelessWidget { + const CustomFilterChip({ + this.label, + this.type, + this.selectedType, + this.callFn, + Key? key, + }) : super(key: key); + + final String? label; + final ArticleFilterType? type; + final ArticleFilterType? selectedType; + final Function? callFn; + + @override + Widget build(BuildContext context) { + return SizedBox( + height: 34, + child: FilterChip( + padding: const EdgeInsets.only(left: 11, right: 11), + labelPadding: EdgeInsets.zero, + label: Text( + label!, + style: const TextStyle(fontSize: 13), + ), + labelStyle: TextStyle( + color: type == selectedType + ? Theme.of(context).colorScheme.primary + : Theme.of(context).colorScheme.outline), + selected: type == selectedType, + showCheckmark: false, + shape: ContinuousRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + selectedColor: Colors.transparent, + // backgroundColor: + // Theme.of(context).colorScheme.surfaceVariant.withOpacity(0.5), + backgroundColor: Colors.transparent, + side: BorderSide.none, + onSelected: (bool selected) => callFn!(selected), + ), + ); + } +} + +class ArticlePanelController extends GetxController { + RxList filterList = [{}].obs; + Rx selectedType = ArticleFilterType.values.first.obs; + + @override + void onInit() { + List> list = ArticleFilterType.values + .map((type) => { + 'label': type.description, + 'type': type, + }) + .toList(); + filterList.value = list; + super.onInit(); + } +} diff --git a/lib/pages/setting/pages/play_gesture_set.dart b/lib/pages/setting/pages/play_gesture_set.dart index e671bfb2..9659e58a 100644 --- a/lib/pages/setting/pages/play_gesture_set.dart +++ b/lib/pages/setting/pages/play_gesture_set.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:hive/hive.dart'; import 'package:pilipala/utils/global_data_cache.dart'; @@ -22,7 +23,7 @@ class _PlayGesturePageState extends State { void initState() { super.initState(); fullScreenGestureMode = setting.get(SettingBoxKey.fullScreenGestureMode, - defaultValue: FullScreenGestureMode.values.last.index); + defaultValue: FullScreenGestureMode.fromBottomtoTop.index); } @override @@ -71,6 +72,7 @@ class _PlayGesturePageState extends State { GlobalDataCache().fullScreenGestureMode.index; setting.put( SettingBoxKey.fullScreenGestureMode, fullScreenGestureMode); + SmartDialog.showToast('设置成功'); setState(() {}); } }, diff --git a/lib/pages/video/detail/controller.dart b/lib/pages/video/detail/controller.dart index a57ec1de..3fead9c7 100644 --- a/lib/pages/video/detail/controller.dart +++ b/lib/pages/video/detail/controller.dart @@ -675,7 +675,6 @@ class VideoDetailController extends GetxController @override void onClose() { super.onClose(); - plPlayerController.dispose(); tabCtr.removeListener(() { onTabChanged(); }); diff --git a/lib/pages/video/detail/introduction/controller.dart b/lib/pages/video/detail/introduction/controller.dart index c0f6eebb..bc9af109 100644 --- a/lib/pages/video/detail/introduction/controller.dart +++ b/lib/pages/video/detail/introduction/controller.dart @@ -3,7 +3,6 @@ import 'dart:async'; import 'package:bottom_sheet/bottom_sheet.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:get/get.dart'; import 'package:hive/hive.dart'; import 'package:pilipala/http/constants.dart'; @@ -154,11 +153,10 @@ class VideoIntroController extends GetxController { } if (hasLike.value && hasCoin.value && hasFav.value) { // 已点赞、投币、收藏 - SmartDialog.showToast('🙏 UP已经收到了~'); + SmartDialog.showToast('UP已经收到了~'); return false; } var result = await VideoHttp.oneThree(bvid: bvid); - print('🤣🦴:${result["data"]}'); if (result['status']) { hasLike.value = result["data"]["like"]; hasCoin.value = result["data"]["coin"]; @@ -413,7 +411,12 @@ class VideoIntroController extends GetxController { } // 修改分P或番剧分集 - Future changeSeasonOrbangu(bvid, cid, aid, cover) async { + Future changeSeasonOrbangu( + String bvid, + int cid, + int? aid, + String? cover, + ) async { // 重新获取视频资源 final VideoDetailController videoDetailCtr = Get.find(tag: heroTag); @@ -424,13 +427,14 @@ class VideoIntroController extends GetxController { releatedCtr.queryRelatedVideo(); } - videoDetailCtr.bvid = bvid; - videoDetailCtr.oid.value = aid ?? IdUtils.bv2av(bvid); - videoDetailCtr.cid.value = cid; - videoDetailCtr.danmakuCid.value = cid; - videoDetailCtr.cover.value = cover; - videoDetailCtr.queryVideoUrl(); - videoDetailCtr.clearSubtitleContent(); + videoDetailCtr + ..bvid = bvid + ..oid.value = aid ?? IdUtils.bv2av(bvid) + ..cid.value = cid + ..danmakuCid.value = cid + ..cover.value = cover ?? '' + ..queryVideoUrl() + ..clearSubtitleContent(); await videoDetailCtr.getSubtitle(); videoDetailCtr.setSubtitleContent(); // 重新请求评论 @@ -480,7 +484,13 @@ class VideoIntroController extends GetxController { final List episodes = []; bool isPages = false; late String cover; - if (videoDetail.value.ugcSeason != null) { + final VideoDetailController videoDetailCtr = + Get.find(tag: heroTag); + + /// 优先稍后再看、收藏夹 + if (videoDetailCtr.isWatchLaterVisible.value) { + episodes.addAll(videoDetailCtr.mediaList); + } else if (videoDetail.value.ugcSeason != null) { final UgcSeason ugcSeason = videoDetail.value.ugcSeason!; final List sections = ugcSeason.sections!; for (int i = 0; i < sections.length; i++) { @@ -497,10 +507,15 @@ class VideoIntroController extends GetxController { episodes.indexWhere((e) => e.cid == lastPlayCid.value); int nextIndex = currentIndex + 1; cover = episodes[nextIndex].cover; - final VideoDetailController videoDetailCtr = - Get.find(tag: heroTag); final PlayRepeat platRepeat = videoDetailCtr.plPlayerController.playRepeat; + int cid = episodes[nextIndex].cid!; + while (cid == -1) { + nextIndex += 1; + SmartDialog.showToast('当前视频暂不支持播放,自动跳过'); + cid = episodes[nextIndex].cid!; + } + // 列表循环 if (nextIndex >= episodes.length) { if (platRepeat == PlayRepeat.listCycle) { @@ -510,7 +525,6 @@ class VideoIntroController extends GetxController { return; } } - final int cid = episodes[nextIndex].cid!; final String rBvid = isPages ? bvid : episodes[nextIndex].bvid; final int rAid = isPages ? IdUtils.bv2av(bvid) : episodes[nextIndex].aid!; changeSeasonOrbangu(rBvid, cid, rAid, cover); @@ -604,4 +618,34 @@ class VideoIntroController extends GetxController { ).buildShowContent(Get.context!), ); } + + // + oneThreeDialog() { + showDialog( + context: Get.context!, + builder: (context) { + return AlertDialog( + title: const Text('提示'), + content: const Text('是否一键三连'), + actions: [ + TextButton( + onPressed: () => navigator!.pop(), + child: Text( + '取消', + style: TextStyle( + color: Theme.of(Get.context!).colorScheme.outline), + ), + ), + TextButton( + onPressed: () async { + actionOneThree(); + navigator!.pop(); + }, + child: const Text('确认'), + ) + ], + ); + }, + ); + } } diff --git a/lib/pages/video/detail/introduction/view.dart b/lib/pages/video/detail/introduction/view.dart index 04cf92fe..31edab2f 100644 --- a/lib/pages/video/detail/introduction/view.dart +++ b/lib/pages/video/detail/introduction/view.dart @@ -1,7 +1,4 @@ -import 'dart:ffi'; - import 'package:bottom_sheet/bottom_sheet.dart'; -import 'package:easy_debounce/easy_throttle.dart'; import 'package:expandable/expandable.dart'; import 'package:flutter/services.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; @@ -151,11 +148,6 @@ class _VideoInfoState extends State with TickerProviderStateMixin { RxBool isExpand = false.obs; late ExpandableController _expandableCtr; - // 一键三连动画 - late AnimationController _controller; - late Animation _scaleTransition; - final RxDouble _progress = 0.0.obs; - void Function()? handleState(Future Function() action) { return isProcessing ? null @@ -178,26 +170,6 @@ class _VideoInfoState extends State with TickerProviderStateMixin { owner = widget.videoDetail!.owner; enableAi = setting.get(SettingBoxKey.enableAi, defaultValue: true); _expandableCtr = ExpandableController(initialExpanded: false); - - /// 一键三连动画 - _controller = AnimationController( - duration: const Duration(milliseconds: 1500), - reverseDuration: const Duration(milliseconds: 300), - vsync: this, - ); - _scaleTransition = Tween(begin: 0.5, end: 1.5).animate(_controller) - ..addListener(() async { - _progress.value = - double.parse((_scaleTransition.value - 0.5).toStringAsFixed(3)); - if (_progress.value == 1) { - if (_controller.status == AnimationStatus.completed) { - await videoIntroController.actionOneThree(); - } - _progress.value = 0; - _scaleTransition.removeListener(() {}); - _controller.stop(); - } - }); } // 收藏 @@ -279,8 +251,6 @@ class _VideoInfoState extends State with TickerProviderStateMixin { @override void dispose() { _expandableCtr.dispose(); - _controller.dispose(); - _scaleTransition.removeListener(() {}); super.dispose(); } @@ -573,131 +543,34 @@ class _VideoInfoState extends State with TickerProviderStateMixin { Widget actionGrid(BuildContext context, videoIntroController) { final actionTypeSort = GlobalDataCache().actionTypeSort; - Widget progressWidget(progress) { - return SizedBox( - width: const IconThemeData.fallback().size! + 5, - height: const IconThemeData.fallback().size! + 5, - child: CircularProgressIndicator( - value: progress.value, - strokeWidth: 2, - ), - ); - } - Map menuListWidgets = { 'like': Obx( - () { - bool likeStatus = videoIntroController.hasLike.value; - ColorScheme colorScheme = Theme.of(context).colorScheme; - return Stack( - children: [ - Positioned( - top: ((Get.size.width - 24) / 5) / 2 - - (const IconThemeData.fallback().size!), - left: ((Get.size.width - 24) / 5) / 2 - - (const IconThemeData.fallback().size! + 5) / 2, - child: progressWidget(_progress)), - InkWell( - onTapDown: (details) { - feedBack(); - if (videoIntroController.userInfo == null) { - SmartDialog.showToast('账号未登录'); - return; - } - _controller.forward(); - }, - onTapUp: (TapUpDetails details) { - if (_progress.value == 0) { - feedBack(); - EasyThrottle.throttle( - 'my-throttler', const Duration(milliseconds: 200), () { - videoIntroController.actionLikeVideo(); - }); - } - _controller.reverse(); - }, - onTapCancel: () { - _controller.reverse(); - }, - borderRadius: StyleString.mdRadius, - child: SizedBox( - width: (Get.size.width - 24) / 5, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const SizedBox(height: 4), - AnimatedSwitcher( - duration: const Duration(milliseconds: 300), - transitionBuilder: - (Widget child, Animation animation) { - return ScaleTransition( - scale: animation, child: child); - }, - child: Icon( - key: ValueKey(likeStatus), - likeStatus - ? FontAwesomeIcons.solidThumbsUp - : FontAwesomeIcons.thumbsUp, - color: likeStatus - ? colorScheme.primary - : colorScheme.outline, - size: 21, - ), - ), - const SizedBox(height: 6), - Text( - widget.videoDetail!.stat!.like!.toString(), - style: TextStyle( - color: likeStatus ? colorScheme.primary : null, - fontSize: - Theme.of(context).textTheme.labelSmall!.fontSize, - ), - ) - ], - ), - ), - ), - ], - ); - }, + () => ActionItem( + icon: const Icon(FontAwesomeIcons.thumbsUp), + selectIcon: const Icon(FontAwesomeIcons.solidThumbsUp), + onTap: handleState(videoIntroController.actionLikeVideo), + onLongPress: () => videoIntroController.oneThreeDialog(), + selectStatus: videoIntroController.hasLike.value, + text: widget.videoDetail!.stat!.like!.toString(), + ), ), 'coin': Obx( - () => Stack( - children: [ - Positioned( - top: ((Get.size.width - 24) / 5) / 2 - - (const IconThemeData.fallback().size!), - left: ((Get.size.width - 24) / 5) / 2 - - (const IconThemeData.fallback().size! + 5) / 2, - child: progressWidget(_progress)), - ActionItem( - icon: const Icon(FontAwesomeIcons.b), - selectIcon: const Icon(FontAwesomeIcons.b), - onTap: handleState(videoIntroController.actionCoinVideo), - selectStatus: videoIntroController.hasCoin.value, - text: widget.videoDetail!.stat!.coin!.toString(), - ), - ], + () => ActionItem( + icon: const Icon(FontAwesomeIcons.b), + selectIcon: const Icon(FontAwesomeIcons.b), + onTap: handleState(videoIntroController.actionCoinVideo), + selectStatus: videoIntroController.hasCoin.value, + text: widget.videoDetail!.stat!.coin!.toString(), ), ), 'collect': Obx( - () => Stack( - children: [ - Positioned( - top: ((Get.size.width - 24) / 5) / 2 - - (const IconThemeData.fallback().size!), - left: ((Get.size.width - 24) / 5) / 2 - - (const IconThemeData.fallback().size! + 5) / 2, - child: progressWidget(_progress)), - ActionItem( - icon: const Icon(FontAwesomeIcons.star), - selectIcon: const Icon(FontAwesomeIcons.solidStar), - onTap: () => showFavBottomSheet(), - onLongPress: () => showFavBottomSheet(type: 'longPress'), - selectStatus: videoIntroController.hasFav.value, - text: widget.videoDetail!.stat!.favorite!.toString(), - ), - ], + () => ActionItem( + icon: const Icon(FontAwesomeIcons.star), + selectIcon: const Icon(FontAwesomeIcons.solidStar), + onTap: () => showFavBottomSheet(), + onLongPress: () => showFavBottomSheet(type: 'longPress'), + selectStatus: videoIntroController.hasFav.value, + text: widget.videoDetail!.stat!.favorite!.toString(), ), ), 'watchLater': ActionItem( diff --git a/lib/pages/video/detail/reply/view.dart b/lib/pages/video/detail/reply/view.dart index f91ef625..1b70015c 100644 --- a/lib/pages/video/detail/reply/view.dart +++ b/lib/pages/video/detail/reply/view.dart @@ -153,7 +153,17 @@ class _VideoReplyPanelState extends State child: Container( height: 40, padding: const EdgeInsets.fromLTRB(12, 0, 6, 0), - color: Theme.of(context).colorScheme.surface, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surface, + boxShadow: [ + BoxShadow( + color: Theme.of(context).colorScheme.surface, + blurRadius: 0.0, + spreadRadius: 0.0, + offset: const Offset(2, 0), + ), + ], + ), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ diff --git a/lib/pages/video/detail/reply/widgets/reply_item.dart b/lib/pages/video/detail/reply/widgets/reply_item.dart index 8bb6992a..1d23548b 100644 --- a/lib/pages/video/detail/reply/widgets/reply_item.dart +++ b/lib/pages/video/detail/reply/widgets/reply_item.dart @@ -9,6 +9,7 @@ import 'package:get/get.dart'; import 'package:hive/hive.dart'; import 'package:pilipala/common/widgets/badge.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart'; +import 'package:pilipala/http/reply.dart'; import 'package:pilipala/models/common/reply_type.dart'; import 'package:pilipala/models/video/reply/item.dart'; import 'package:pilipala/pages/main/index.dart'; @@ -18,6 +19,7 @@ import 'package:pilipala/plugin/pl_gallery/index.dart'; import 'package:pilipala/plugin/pl_popup/index.dart'; import 'package:pilipala/utils/app_scheme.dart'; import 'package:pilipala/utils/feed_back.dart'; +import 'package:pilipala/utils/global_data_cache.dart'; import 'package:pilipala/utils/id_utils.dart'; import 'package:pilipala/utils/storage.dart'; import 'package:pilipala/utils/url_utils.dart'; @@ -48,6 +50,8 @@ class ReplyItem extends StatelessWidget { @override Widget build(BuildContext context) { + final bool isOwner = int.parse(replyItem!.member!.mid!) == + (GlobalDataCache().userInfo?.mid ?? -1); return Material( child: InkWell( // 点击整个评论区 评论详情/回复 @@ -73,6 +77,7 @@ class ReplyItem extends StatelessWidget { return MorePanel( item: replyItem, mainFloor: true, + isOwner: isOwner, ); }, ); @@ -195,25 +200,36 @@ class ReplyItem extends StatelessWidget { ), ], ), - Row( - children: [ - Text( - Utils.dateFormat(replyItem!.ctime), - style: TextStyle( - fontSize: textTheme.labelSmall!.fontSize, - color: colorScheme.outline, - ), - ), - if (replyItem!.replyControl != null && - replyItem!.replyControl!.location != '') - Text( - ' • ${replyItem!.replyControl!.location!}', + RichText( + text: TextSpan( + children: [ + TextSpan( + text: Utils.dateFormat(replyItem!.ctime), style: TextStyle( - fontSize: textTheme.labelSmall!.fontSize, - color: colorScheme.outline), + fontSize: textTheme.labelSmall!.fontSize, + color: colorScheme.outline, + ), ), - ], - ) + if (replyItem!.replyControl != null && + replyItem!.replyControl!.location != '') + TextSpan( + text: ' • ${replyItem!.replyControl!.location!}', + style: TextStyle( + fontSize: textTheme.labelSmall!.fontSize, + color: colorScheme.outline, + ), + ), + if (replyItem!.invisible!) + TextSpan( + text: ' • 隐藏的评论', + style: TextStyle( + color: colorScheme.outline, + fontSize: textTheme.labelSmall!.fontSize, + ), + ), + ], + ), + ), ], ), ], @@ -698,14 +714,11 @@ InlineSpan buildContent( '', ); } else if (RegExp(r'^cv\d+$').hasMatch(matchStr)) { - Get.toNamed( - '/webview', - parameters: { - 'url': 'https://www.bilibili.com/read/$matchStr', - 'type': 'url', - 'pageTitle': title - }, - ); + Get.toNamed('/read', parameters: { + 'title': title, + 'id': Utils.matchNum(matchStr).first.toString(), + 'articleType': 'read', + }); } else { Uri uri = Uri.parse(matchStr.replaceAll('/?', '?')); SchemeEntity scheme = SchemeEntity( @@ -717,7 +730,7 @@ InlineSpan buildContent( source: '', dataString: matchStr, ); - PiliSchame.fullPathPush(scheme); + PiliSchame.httpsScheme(scheme); } } else { if (appUrlSchema.startsWith('bilibili://search')) { @@ -1004,10 +1017,12 @@ InlineSpan buildContent( class MorePanel extends StatelessWidget { final dynamic item; final bool mainFloor; + final bool isOwner; const MorePanel({ super.key, required this.item, this.mainFloor = false, + this.isOwner = false, }); Future menuActionHandler(String type) async { @@ -1043,9 +1058,43 @@ class MorePanel extends StatelessWidget { // case 'report': // SmartDialog.showToast('举报'); // break; - // case 'delete': - // SmartDialog.showToast('删除'); - // break; + case 'delete': + // 删除评论提示 + await showDialog( + context: Get.context!, + builder: (context) { + return AlertDialog( + title: const Text('删除评论'), + content: const Text('删除评论后,评论下所有回复将被删除,确定删除吗?'), + actions: [ + TextButton( + onPressed: () => Get.back(), + child: Text('取消', + style: TextStyle( + color: Theme.of(context).colorScheme.outline)), + ), + TextButton( + onPressed: () async { + Get.back(); + var result = await ReplyHttp.replyDel( + type: item.type!, + oid: item.oid!, + rpid: item.rpid!, + ); + if (result['status']) { + SmartDialog.showToast('评论删除成功,需手动刷新'); + Get.back(); + } else { + SmartDialog.showToast(result['msg']); + } + }, + child: const Text('确定'), + ), + ], + ); + }, + ); + break; default: } } @@ -1054,6 +1103,7 @@ class MorePanel extends StatelessWidget { Widget build(BuildContext context) { ColorScheme colorScheme = Theme.of(context).colorScheme; TextTheme textTheme = Theme.of(context).textTheme; + Color errorColor = colorScheme.error; return Container( padding: EdgeInsets.only(bottom: MediaQuery.of(context).padding.bottom), child: Column( @@ -1106,12 +1156,14 @@ class MorePanel extends StatelessWidget { // leading: Icon(Icons.report_outlined, color: errorColor), // title: Text('举报', style: TextStyle(color: errorColor)), // ), - // ListTile( - // onTap: () async => await menuActionHandler('del'), - // minLeadingWidth: 0, - // leading: Icon(Icons.delete_outline, color: errorColor), - // title: Text('删除', style: TextStyle(color: errorColor)), - // ), + if (isOwner) + ListTile( + onTap: () async => await menuActionHandler('delete'), + minLeadingWidth: 0, + leading: Icon(Icons.delete_outline, color: errorColor), + title: Text('删除评论', + style: textTheme.titleSmall!.copyWith(color: errorColor)), + ), ], ), ); diff --git a/lib/pages/video/detail/view.dart b/lib/pages/video/detail/view.dart index 5476adc9..7fe76745 100644 --- a/lib/pages/video/detail/view.dart +++ b/lib/pages/video/detail/view.dart @@ -3,6 +3,7 @@ import 'dart:io'; import 'package:extended_nested_scroll_view/extended_nested_scroll_view.dart'; import 'package:floating/floating.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_svg/svg.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; @@ -68,10 +69,6 @@ class _VideoDetailPageState extends State late final AppLifecycleListener _lifecycleListener; late double statusHeight; - // 稍后再看控制器 - // late AnimationController _laterCtr; - // late Animation _laterOffsetAni; - @override void initState() { super.initState(); @@ -85,14 +82,16 @@ class _VideoDetailPageState extends State videoIntroController.videoDetail.listen((value) { videoPlayerServiceHandler.onVideoDetailChange(value, vdCtr.cid.value); }); - bangumiIntroController = Get.put(BangumiIntroController(), tag: heroTag); - bangumiIntroController.bangumiDetail.listen((value) { - videoPlayerServiceHandler.onVideoDetailChange(value, vdCtr.cid.value); - }); - vdCtr.cid.listen((p0) { - videoPlayerServiceHandler.onVideoDetailChange( - bangumiIntroController.bangumiDetail.value, p0); - }); + if (vdCtr.videoType == SearchType.media_bangumi) { + bangumiIntroController = Get.put(BangumiIntroController(), tag: heroTag); + bangumiIntroController.bangumiDetail.listen((value) { + videoPlayerServiceHandler.onVideoDetailChange(value, vdCtr.cid.value); + }); + vdCtr.cid.listen((p0) { + videoPlayerServiceHandler.onVideoDetailChange( + bangumiIntroController.bangumiDetail.value, p0); + }); + } statusBarHeight = localCache.get('statusBarHeight'); autoExitFullcreen = setting.get(SettingBoxKey.enableAutoExit, defaultValue: false); @@ -108,7 +107,6 @@ class _VideoDetailPageState extends State } WidgetsBinding.instance.addObserver(this); lifecycleListener(); - // watchLaterControllerInit(); } // 获取视频资源,初始化播放器 @@ -242,8 +240,6 @@ class _VideoDetailPageState extends State appbarStream.close(); WidgetsBinding.instance.removeObserver(this); _lifecycleListener.dispose(); - // _laterCtr.dispose(); - // _laterOffsetAni.removeListener(() {}); super.dispose(); } @@ -297,6 +293,7 @@ class _VideoDetailPageState extends State plPlayerController?.play(); } plPlayerController?.addStatusLister(playerListener); + appbarStream.add(0); super.didPopNext(); } @@ -490,21 +487,6 @@ class _VideoDetailPageState extends State ); } - /// 稍后再看控制器初始化 - // void watchLaterControllerInit() { - // _laterCtr = AnimationController( - // duration: const Duration(milliseconds: 300), - // vsync: this, - // ); - // _laterOffsetAni = Tween( - // begin: const Offset(0.0, 1.0), - // end: Offset.zero, - // ).animate(CurvedAnimation( - // parent: _laterCtr, - // curve: Curves.easeInOut, - // )); - // } - @override Widget build(BuildContext context) { final sizeContext = MediaQuery.sizeOf(context); @@ -529,6 +511,14 @@ class _VideoDetailPageState extends State exitFullScreen(); } + SystemChrome.setSystemUIOverlayStyle( + SystemUiOverlayStyle( + systemNavigationBarColor: Colors.transparent, + systemNavigationBarIconBrightness: + Get.isDarkMode ? Brightness.light : Brightness.dark, + ), + ); + Widget buildLoadingWidget() { return Center(child: Lottie.asset('assets/loading.json', width: 200)); } @@ -615,10 +605,16 @@ class _VideoDetailPageState extends State key: vdCtr.scaffoldKey, appBar: PreferredSize( preferredSize: const Size.fromHeight(0), - child: AppBar( - backgroundColor: Colors.black, - elevation: 0, - scrolledUnderElevation: 0, + child: StreamBuilder( + stream: appbarStream.stream.distinct(), + initialData: 0, + builder: ((context, snapshot) { + return AppBar( + backgroundColor: Colors.black, + elevation: 0, + scrolledUnderElevation: 0, + ); + }), ), ), body: ExtendedNestedScrollView( diff --git a/lib/pages/video/detail/widgets/watch_later_list.dart b/lib/pages/video/detail/widgets/watch_later_list.dart index b367ae40..8e83af4e 100644 --- a/lib/pages/video/detail/widgets/watch_later_list.dart +++ b/lib/pages/video/detail/widgets/watch_later_list.dart @@ -109,7 +109,7 @@ class _MediaListPanelState extends State { var item = mediaList[index]; return InkWell( onTap: () async { - String bvid = item.bvId!; + String bvid = item.bvid!; int? aid = item.id; String cover = item.cover ?? ''; final int cid = @@ -173,7 +173,7 @@ class _MediaListPanelState extends State { overflow: TextOverflow.ellipsis, style: TextStyle( fontWeight: FontWeight.w500, - color: item.bvId == widget.bvid + color: item.bvid == widget.bvid ? Theme.of(context) .colorScheme .primary diff --git a/lib/pages/whisper/view.dart b/lib/pages/whisper/view.dart index e97aa79b..cbe4d938 100644 --- a/lib/pages/whisper/view.dart +++ b/lib/pages/whisper/view.dart @@ -228,7 +228,7 @@ class SessionItem extends StatelessWidget { parameters: { 'talkerId': sessionItem.talkerId.toString(), 'name': sessionItem.accountInfo.name, - 'face': sessionItem.accountInfo.face, + 'face': sessionItem.accountInfo.face ?? '', 'mid': (sessionItem.accountInfo?.mid ?? 0).toString(), 'heroTag': heroTag, }, @@ -244,7 +244,7 @@ class SessionItem extends StatelessWidget { width: 45, height: 45, type: 'avatar', - src: sessionItem.accountInfo.face, + src: sessionItem.accountInfo.face ?? '', ), ), ), diff --git a/lib/plugin/pl_player/controller.dart b/lib/plugin/pl_player/controller.dart index 8dff954a..0bd7950c 100644 --- a/lib/plugin/pl_player/controller.dart +++ b/lib/plugin/pl_player/controller.dart @@ -1045,6 +1045,14 @@ class PlPlayerController { /// 缓存本次弹幕选项 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.danmakuShowArea, showArea); localCache.put(LocalCacheKey.danmakuOpacity, opacityVal); diff --git a/lib/plugin/pl_player/view.dart b/lib/plugin/pl_player/view.dart index dd5b617e..6b57a8e1 100644 --- a/lib/plugin/pl_player/view.dart +++ b/lib/plugin/pl_player/view.dart @@ -204,6 +204,11 @@ class _PLVideoPlayerState extends State widget.controller.brightness.value = value; } + bool isUsingFullScreenGestures(double tapPosition, double sectionWidth) { + return fullScreenGestureMode != FullScreenGestureMode.none && + tapPosition < sectionWidth * 2; + } + @override void dispose() { animationController.dispose(); @@ -638,7 +643,10 @@ class _PLVideoPlayerState extends State onVerticalDragUpdate: (DragUpdateDetails details) async { final double totalWidth = MediaQuery.sizeOf(context).width; final double tapPosition = details.localPosition.dx; - final double sectionWidth = totalWidth / 3; + final double sectionWidth = + fullScreenGestureMode == FullScreenGestureMode.none + ? totalWidth / 2 + : totalWidth / 3; final double delta = details.delta.dy; /// 锁定时禁用 @@ -660,12 +668,12 @@ class _PLVideoPlayerState extends State _brightnessValue.value - delta / level; final double result = brightness.clamp(0.0, 1.0); setBrightness(result); - } else if (tapPosition < sectionWidth * 2) { + } else if (isUsingFullScreenGestures(tapPosition, sectionWidth)) { // 全屏 final double dy = details.delta.dy; const double threshold = 7.0; // 滑动阈值 - final bool flag = - fullScreenGestureMode != FullScreenGestureMode.values.last; + final bool flag = fullScreenGestureMode != + FullScreenGestureMode.fromBottomtoTop; if (dy > _distance.value && dy > threshold && !_.controlsLock.value) { diff --git a/lib/utils/app_scheme.dart b/lib/utils/app_scheme.dart index 22184139..a8ad1aa0 100644 --- a/lib/utils/app_scheme.dart +++ b/lib/utils/app_scheme.dart @@ -1,3 +1,4 @@ +import 'package:app_links/app_links.dart'; import 'package:appscheme/appscheme.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -10,22 +11,29 @@ import 'url_utils.dart'; import 'utils.dart'; class PiliSchame { + static late AppLinks appLinks; static AppScheme appScheme = AppSchemeImpl.getInstance()!; static Future init() async { - /// - final SchemeEntity? value = await appScheme.getInitScheme(); - if (value != null) { - _routePush(value); - } + appLinks = AppLinks(); + appLinks.uriLinkStream.listen((Uri uri) { + final String scheme = uri.scheme; + 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) { if (value != null) { _routePush(value); } }); - /// 注册从外部打开的Scheme监听信息 # appScheme.registerSchemeListener().listen((SchemeEntity? event) { if (event != null) { _routePush(event); @@ -36,88 +44,11 @@ class PiliSchame { /// 路由跳转 static void _routePush(value) async { final String scheme = value.scheme; - final String host = value.host; - final String path = value.path; if (scheme == 'bilibili') { - switch (host) { - case 'root': - Navigator.popUntil( - Get.context!, (Route route) => route.isFirst); - break; - case 'space': - final String mid = path.split('/').last; - Get.toNamed( - '/member?mid=$mid', - arguments: {'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( - '/liveRoom?roomid=$roomId', - arguments: {'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; - } + biliScheme(value); } if (scheme == 'https') { - fullPathPush(value); + httpsScheme(value); } } @@ -148,7 +79,7 @@ class PiliSchame { } } - static Future fullPathPush(SchemeEntity value) async { + static Future httpsScheme(SchemeEntity value) async { // https://m.bilibili.com/bangumi/play/ss39708 // https | m.bilibili.com | /bangumi/play/ss39708 // final String scheme = value.scheme!; @@ -175,6 +106,11 @@ class PiliSchame { if (lastPathSegment.contains('ep')) { RoutePush.bangumiPush(null, Utils.matchNum(lastPathSegment).first); } + } else if (path.startsWith('/BV')) { + final String bvid = path.split('?').first.split('/').last; + _videoPush(null, bvid); + } else if (path.startsWith('/av')) { + _videoPush(Utils.matchNum(path.split('?').first).first, null); } } else if (host.contains('live')) { int roomId = int.parse(path!.split('/').last); @@ -276,6 +212,140 @@ class PiliSchame { } } + static Future biliScheme(SchemeEntity value) async { + final String host = value.host!; + final String path = value.path!; + switch (host) { + case 'root': + Navigator.popUntil( + Get.context!, (Route route) => route.isFirst); + break; + case 'space': + final String mid = path.split('/').last; + Get.toNamed( + '/member?mid=$mid', + arguments: {'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( + '/liveRoom?roomid=$roomId', + arguments: {'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; + } + } + + static Future 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( + '/member?mid=$mid', + arguments: {'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) { final String seasonId = _extractIdFromPath(lastPathSegment); RoutePush.bangumiPush(null, Utils.matchNum(seasonId).first); diff --git a/lib/utils/cache_manage.dart b/lib/utils/cache_manage.dart index d6bbb816..4603da9f 100644 --- a/lib/utils/cache_manage.dart +++ b/lib/utils/cache_manage.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:io'; import 'package:flutter/material.dart'; +import 'package:get/get.dart'; import 'package:path_provider/path_provider.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; @@ -50,13 +51,17 @@ class CacheManage { Future getTotalSizeOfFilesInDir(final FileSystemEntity file) async { if (file is File) { int length = await file.length(); - return double.parse(length.toString()); + return length.toDouble(); } if (file is Directory) { - final List children = file.listSync(); double total = 0; - for (final FileSystemEntity child in children) { - total += await getTotalSizeOfFilesInDir(child); + try { + await for (final FileSystemEntity child in file.list()) { + total += await getTotalSizeOfFilesInDir(child); + } + } catch (e) { + // 处理错误,例如记录日志或显示错误消息 + print('读取目录时出错: $e'); } return total; } @@ -77,16 +82,17 @@ class CacheManage { // 清除缓存 Future clearCacheAll() async { - bool cleanStatus = await SmartDialog.show( - useSystem: true, - animationType: SmartAnimationType.centerFade_otherSlide, + bool? cleanStatus = await showDialog( + context: Get.context!, builder: (BuildContext context) { return AlertDialog( title: const Text('提示'), content: const Text('该操作将清除图片及网络请求缓存数据,确认清除?'), actions: [ TextButton( - onPressed: (() => {SmartDialog.dismiss()}), + onPressed: () { + Navigator.of(context).pop(false); + }, child: Text( '取消', style: TextStyle(color: Theme.of(context).colorScheme.outline), @@ -94,40 +100,45 @@ class CacheManage { ), TextButton( onPressed: () async { - SmartDialog.dismiss(); - SmartDialog.showLoading(msg: '正在清除...'); - try { - // 清除缓存 图片缓存 - await clearLibraryCache(); - SmartDialog.dismiss().then((res) { - SmartDialog.showToast('清除完成'); - }); - } catch (err) { - SmartDialog.dismiss(); - SmartDialog.showToast(err.toString()); - } + Navigator.of(context).pop(true); }, child: const Text('确认'), - ) + ), ], ); }, - ).then((res) { - return true; - }); - return cleanStatus; + ); + if (cleanStatus != null && cleanStatus) { + SmartDialog.showLoading(msg: '正在清除...'); + try { + // 清除缓存 图片缓存 + await clearLibraryCache(); + SmartDialog.dismiss().then((res) { + SmartDialog.showToast('清除完成'); + }); + } catch (err) { + SmartDialog.dismiss(); + SmartDialog.showToast(err.toString()); + } + } + return cleanStatus!; } /// 清除 Documents 目录下的 DioCache.db - Future clearApplicationCache() async { - Directory directory = await getApplicationDocumentsDirectory(); - if (directory.existsSync()) { - String dioCacheFileName = - '${directory.path}${Platform.pathSeparator}DioCache.db'; - var dioCacheFile = File(dioCacheFileName); - if (dioCacheFile.existsSync()) { - dioCacheFile.delete(); + Future clearApplicationCache() async { + try { + Directory directory = await getApplicationDocumentsDirectory(); + if (directory.existsSync()) { + String dioCacheFileName = + '${directory.path}${Platform.pathSeparator}DioCache.db'; + File dioCacheFile = File(dioCacheFileName); + if (await dioCacheFile.exists()) { + await dioCacheFile.delete(); + } } + } catch (e) { + // 处理错误,例如记录日志或显示错误消息 + print('清除缓存时出错: $e'); } } diff --git a/lib/utils/global_data_cache.dart b/lib/utils/global_data_cache.dart index 9d925a0f..ea2eda37 100644 --- a/lib/utils/global_data_cache.dart +++ b/lib/utils/global_data_cache.dart @@ -15,6 +15,7 @@ class GlobalDataCache { late FullScreenGestureMode fullScreenGestureMode; late bool enablePlayerControlAnimation; late List actionTypeSort; + String? wWebid; /// 播放器相关 // 弹幕开关 @@ -59,7 +60,7 @@ class GlobalDataCache { defaultValue: 10); // 设置全局变量 fullScreenGestureMode = FullScreenGestureMode.values[setting.get( SettingBoxKey.fullScreenGestureMode, - defaultValue: FullScreenGestureMode.values.last.index) as int]; + defaultValue: FullScreenGestureMode.fromBottomtoTop.index)]; enablePlayerControlAnimation = setting .get(SettingBoxKey.enablePlayerControlAnimation, defaultValue: true); actionTypeSort = await setting.get(SettingBoxKey.actionTypeSort, diff --git a/lib/utils/subtitle.dart b/lib/utils/subtitle.dart index 1b4088f3..9743711c 100644 --- a/lib/utils/subtitle.dart +++ b/lib/utils/subtitle.dart @@ -1,21 +1,51 @@ +import 'dart:async'; +import 'dart:isolate'; + class SubTitleUtils { // 格式整理 - static String convertToWebVTT(List jsonData) { - String webVTTContent = 'WEBVTT FILE\n\n'; + static Future convertToWebVTT(List jsonData) async { + final receivePort = ReceivePort(); + await Isolate.spawn(_convertToWebVTTIsolate, receivePort.sendPort); - for (int i = 0; i < jsonData.length; i++) { - final item = jsonData[i]; - double from = double.parse(item['from'].toString()); - double to = double.parse(item['to'].toString()); - int sid = (item['sid'] ?? 0) as int; - String content = item['content'] as String; + final sendPort = await receivePort.first as SendPort; + final response = ReceivePort(); + sendPort.send([jsonData, response.sendPort]); - webVTTContent += '$sid\n'; - webVTTContent += '${formatTime(from)} --> ${formatTime(to)}\n'; - webVTTContent += '$content\n\n'; + return await response.first as String; + } + + static void _convertToWebVTTIsolate(SendPort sendPort) async { + final port = ReceivePort(); + sendPort.send(port.sendPort); + + await for (final message in port) { + final List jsonData = message[0]; + final SendPort replyTo = message[1]; + + String webVTTContent = 'WEBVTT FILE\n\n'; + int chunkSize = 100; // 每次处理100条数据 + int totalChunks = (jsonData.length / chunkSize).ceil(); + + for (int chunk = 0; chunk < totalChunks; chunk++) { + int start = chunk * chunkSize; + int end = start + chunkSize; + if (end > jsonData.length) end = jsonData.length; + + for (int i = start; i < end; i++) { + final item = jsonData[i]; + double from = double.parse(item['from'].toString()); + double to = double.parse(item['to'].toString()); + int sid = (item['sid'] ?? 0) as int; + String content = item['content'] as String; + + webVTTContent += '$sid\n'; + webVTTContent += '${formatTime(from)} --> ${formatTime(to)}\n'; + webVTTContent += '$content\n\n'; + } + } + + replyTo.send(webVTTContent); } - - return webVTTContent; } static String formatTime(num seconds) { diff --git a/lib/utils/utils.dart b/lib/utils/utils.dart index e50c295c..7a254f7d 100644 --- a/lib/utils/utils.dart +++ b/lib/utils/utils.dart @@ -306,7 +306,7 @@ class Utils { onPressed: () async { await SmartDialog.dismiss(); launchUrl( - Uri.parse('https://www.123pan.com/s/9sVqVv-flu0A.html'), + Uri.parse('https://www.123684.com/s/9sVqVv-DEZ0A'), mode: LaunchMode.externalApplication, ); }, diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index c3b56ecd..4dd0e0b9 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -19,6 +20,9 @@ void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) flutter_volume_controller_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterVolumeControllerPlugin"); 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 = fl_plugin_registry_get_registrar_for_plugin(registry, "MediaKitLibsLinuxPlugin"); media_kit_libs_linux_plugin_register_with_registrar(media_kit_libs_linux_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 70cdeb4b..dca43a8e 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -5,6 +5,7 @@ list(APPEND FLUTTER_PLUGIN_LIST dynamic_color flutter_volume_controller + gtk media_kit_libs_linux media_kit_video url_launcher_linux diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 8af2f922..b7538b19 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,6 +5,7 @@ import FlutterMacOS import Foundation +import app_links import audio_service import audio_session import connectivity_plus @@ -22,6 +23,7 @@ import url_launcher_macos import wakelock_plus func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + AppLinksMacosPlugin.register(with: registry.registrar(forPlugin: "AppLinksMacosPlugin")) AudioServicePlugin.register(with: registry.registrar(forPlugin: "AudioServicePlugin")) AudioSessionPlugin.register(with: registry.registrar(forPlugin: "AudioSessionPlugin")) ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin")) diff --git a/pubspec.lock b/pubspec.lock index b9d48c78..9f2036e3 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -17,6 +17,38 @@ packages: url: "https://pub.flutter-io.cn" source: hosted 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: dependency: "direct main" description: @@ -686,6 +718,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted 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: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index 617b0c7d..4af3459b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: "none" # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 1.0.24+1024 +version: 1.0.25+1025 environment: sdk: ">=3.0.0 <4.0.0" @@ -112,6 +112,7 @@ dependencies: flutter_displaymode: ^0.6.0 # scheme跳转 appscheme: ^1.0.8 + app_links: ^6.3.2 # 弹幕 ns_danmaku: git: diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index d2cff3b2..f477ef65 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -6,6 +6,7 @@ #include "generated_plugin_registrant.h" +#include #include #include #include @@ -17,6 +18,8 @@ #include void RegisterPlugins(flutter::PluginRegistry* registry) { + AppLinksPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("AppLinksPluginCApi")); ConnectivityPlusWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin")); DynamicColorPluginCApiRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 5d25e134..1db9f3fe 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + app_links connectivity_plus dynamic_color flutter_volume_controller From 0ec14fe1fa1d395c391873f1bde34e97f1679161 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Wed, 16 Oct 2024 22:59:34 +0800 Subject: [PATCH 14/90] opt: HttpError --- lib/common/widgets/http_error.dart | 67 +++++++++---------- lib/pages/bangumi/view.dart | 6 +- lib/pages/blacklist/index.dart | 19 +++--- lib/pages/fan/view.dart | 19 +++--- lib/pages/fav/view.dart | 30 ++++----- lib/pages/follow/widgets/follow_list.dart | 15 +++-- .../follow/widgets/owner_follow_list.dart | 11 ++- lib/pages/follow_search/view.dart | 22 +++--- lib/pages/html/view.dart | 11 ++- lib/pages/member_article/view.dart | 14 ++-- lib/pages/member_search/view.dart | 11 ++- lib/pages/message/like/view.dart | 20 +++--- lib/pages/message/reply/view.dart | 21 +++--- lib/pages/message/system/view.dart | 21 +++--- lib/pages/search/view.dart | 11 ++- lib/pages/search_panel/view.dart | 40 +++++------ .../search_panel/widgets/article_panel.dart | 13 ++-- .../search_panel/widgets/video_panel.dart | 13 ++-- lib/pages/subscription/view.dart | 39 +++++------ 19 files changed, 175 insertions(+), 228 deletions(-) diff --git a/lib/common/widgets/http_error.dart b/lib/common/widgets/http_error.dart index 0381319e..305d43c8 100644 --- a/lib/common/widgets/http_error.dart +++ b/lib/common/widgets/http_error.dart @@ -7,6 +7,7 @@ class HttpError extends StatelessWidget { required this.fn, this.btnText, this.isShowBtn = true, + this.isInSliver = true, super.key, }); @@ -14,46 +15,42 @@ class HttpError extends StatelessWidget { final Function()? fn; final String? btnText; final bool isShowBtn; + final bool isInSliver; @override Widget build(BuildContext context) { - return SliverToBoxAdapter( - child: SizedBox( - height: 400, - child: Column( - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SvgPicture.asset( - "assets/images/error.svg", - height: 200, - ), - const SizedBox(height: 30), - Text( - errMsg ?? '请求异常', - textAlign: TextAlign.center, - style: Theme.of(context).textTheme.titleSmall, - ), - const SizedBox(height: 20), - if (isShowBtn) - FilledButton.tonal( - onPressed: () { - fn!(); - }, - style: ButtonStyle( - backgroundColor: MaterialStateProperty.resolveWith((states) { - return Theme.of(context).colorScheme.primary.withAlpha(20); - }), - ), - child: Text( - btnText ?? '点击重试', - style: - TextStyle(color: Theme.of(context).colorScheme.primary), - ), + Color primary = Theme.of(context).colorScheme.primary; + final errorContent = SizedBox( + height: 400, + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SvgPicture.asset("assets/images/error.svg", height: 200), + const SizedBox(height: 30), + Text( + errMsg ?? '请求异常', + textAlign: TextAlign.center, + style: Theme.of(context).textTheme.titleSmall, + ), + const SizedBox(height: 20), + if (isShowBtn) + FilledButton.tonal( + onPressed: () => fn?.call(), + style: ButtonStyle( + backgroundColor: MaterialStateProperty.resolveWith((states) { + return primary.withAlpha(20); + }), ), - ], - ), + child: Text(btnText ?? '点击重试', style: TextStyle(color: primary)), + ), + ], ), ); + if (isInSliver) { + return SliverToBoxAdapter(child: errorContent); + } else { + return Center(child: errorContent); + } } } diff --git a/lib/pages/bangumi/view.dart b/lib/pages/bangumi/view.dart index 9ec72350..4092943b 100644 --- a/lib/pages/bangumi/view.dart +++ b/lib/pages/bangumi/view.dart @@ -183,8 +183,10 @@ class _BangumiPageState extends State return HttpError( errMsg: data['msg'], fn: () { - _futureBuilderFuture = - _bangumidController.queryBangumiListFeed(); + setState(() { + _futureBuilderFuture = + _bangumidController.queryBangumiListFeed(); + }); }, ); } diff --git a/lib/pages/blacklist/index.dart b/lib/pages/blacklist/index.dart index fb7e0891..bdd2346f 100644 --- a/lib/pages/blacklist/index.dart +++ b/lib/pages/blacklist/index.dart @@ -77,10 +77,10 @@ class _BlackListPageState extends State { List list = _blackListController.blackList; return Obx( () => list.isEmpty - ? CustomScrollView( - slivers: [ - HttpError(errMsg: '你没有拉黑任何人哦~_~', fn: () => {}) - ], + ? HttpError( + errMsg: '你没有拉黑任何人哦~_~', + fn: () => {}, + isInSliver: false, ) : ListView.builder( controller: scrollController, @@ -119,13 +119,10 @@ class _BlackListPageState extends State { ), ); } else { - return CustomScrollView( - slivers: [ - HttpError( - errMsg: data['msg'], - fn: () => _blackListController.queryBlacklist(), - ) - ], + return HttpError( + errMsg: data['msg'], + fn: () => _blackListController.queryBlacklist(), + isInSliver: false, ); } } else { diff --git a/lib/pages/fan/view.dart b/lib/pages/fan/view.dart index 5d5c02a7..67a1eddd 100644 --- a/lib/pages/fan/view.dart +++ b/lib/pages/fan/view.dart @@ -103,17 +103,14 @@ class _FansPageState extends State { ), ); } else { - return CustomScrollView( - physics: const NeverScrollableScrollPhysics(), - slivers: [ - HttpError( - errMsg: data['msg'], - fn: () { - _futureBuilderFuture = - _fansController.queryFans('init'); - }, - ) - ], + return HttpError( + errMsg: data['msg'], + fn: () { + setState(() { + _futureBuilderFuture = _fansController.queryFans('init'); + }); + }, + isInSliver: false, ); } } else { diff --git a/lib/pages/fav/view.dart b/lib/pages/fav/view.dart index 317ba4d5..9d7534f5 100644 --- a/lib/pages/fav/view.dart +++ b/lib/pages/fav/view.dart @@ -112,23 +112,19 @@ class _FavPageState extends State { ), ); } else { - return CustomScrollView( - physics: const NeverScrollableScrollPhysics(), - slivers: [ - HttpError( - errMsg: data?['msg'] ?? '请求异常', - btnText: data?['code'] == -101 ? '去登录' : null, - fn: () { - if (data?['code'] == -101) { - RoutePush.loginRedirectPush(); - } else { - setState(() { - _futureBuilderFuture = _favController.queryFavFolder(); - }); - } - }, - ), - ], + return HttpError( + errMsg: data?['msg'] ?? '请求异常', + btnText: data?['code'] == -101 ? '去登录' : null, + fn: () { + if (data?['code'] == -101) { + RoutePush.loginRedirectPush(); + } else { + setState(() { + _futureBuilderFuture = _favController.queryFavFolder(); + }); + } + }, + isInSliver: false, ); } } else { diff --git a/lib/pages/follow/widgets/follow_list.dart b/lib/pages/follow/widgets/follow_list.dart index d198bec2..dc9bf3c9 100644 --- a/lib/pages/follow/widgets/follow_list.dart +++ b/lib/pages/follow/widgets/follow_list.dart @@ -94,13 +94,14 @@ class _FollowListState extends State { : const CustomScrollView(slivers: [NoData()]), ); } else { - return CustomScrollView( - slivers: [ - HttpError( - errMsg: data['msg'], - fn: () => widget.ctr.queryFollowings('init'), - ) - ], + return HttpError( + errMsg: data['msg'], + fn: () { + setState(() { + _futureBuilderFuture = widget.ctr.queryFollowings('init'); + }); + }, + isInSliver: false, ); } } else { diff --git a/lib/pages/follow/widgets/owner_follow_list.dart b/lib/pages/follow/widgets/owner_follow_list.dart index ccc11c44..38950cf7 100644 --- a/lib/pages/follow/widgets/owner_follow_list.dart +++ b/lib/pages/follow/widgets/owner_follow_list.dart @@ -112,13 +112,10 @@ class _OwnerFollowListState extends State : const CustomScrollView(slivers: [NoData()]), ); } else { - return CustomScrollView( - slivers: [ - HttpError( - errMsg: data['msg'], - fn: () => widget.ctr.queryFollowings('init'), - ) - ], + return HttpError( + errMsg: data['msg'], + fn: () => widget.ctr.queryFollowings('init'), + isInSliver: false, ); } } else { diff --git a/lib/pages/follow_search/view.dart b/lib/pages/follow_search/view.dart index 6be42676..78cdb11d 100644 --- a/lib/pages/follow_search/view.dart +++ b/lib/pages/follow_search/view.dart @@ -82,10 +82,10 @@ class _FollowSearchPageState extends State { if (snapshot.connectionState == ConnectionState.done) { var data = snapshot.data; if (data == null) { - return CustomScrollView( - slivers: [ - HttpError(errMsg: snapshot.data['msg'], fn: reRequest) - ], + return HttpError( + errMsg: snapshot.data['msg'], + fn: reRequest, + isInSliver: false, ); } if (data['status']) { @@ -101,15 +101,17 @@ class _FollowSearchPageState extends State { ); }), ) - : CustomScrollView( - slivers: [HttpError(errMsg: '未搜索到结果', fn: reRequest)], + : HttpError( + errMsg: '未搜索到结果', + fn: reRequest, + isInSliver: false, ), ); } else { - return CustomScrollView( - slivers: [ - HttpError(errMsg: snapshot.data['msg'], fn: reRequest) - ], + return HttpError( + errMsg: snapshot.data['msg'], + fn: reRequest, + isInSliver: false, ); } } else { diff --git a/lib/pages/html/view.dart b/lib/pages/html/view.dart index 9f0c865c..2c3075c8 100644 --- a/lib/pages/html/view.dart +++ b/lib/pages/html/view.dart @@ -380,13 +380,10 @@ class _HtmlRenderPageState extends State ); } else { // 请求错误 - return CustomScrollView( - slivers: [ - HttpError( - errMsg: data['msg'], - fn: () => setState(() {}), - ) - ], + return HttpError( + errMsg: data['msg'], + fn: () => setState(() {}), + isInSliver: false, ); } } else { diff --git a/lib/pages/member_article/view.dart b/lib/pages/member_article/view.dart index e23e208d..0728a7b6 100644 --- a/lib/pages/member_article/view.dart +++ b/lib/pages/member_article/view.dart @@ -138,16 +138,10 @@ class _MemberArticlePageState extends State { } Widget _buildError(String errMsg) { - return CustomScrollView( - physics: const NeverScrollableScrollPhysics(), - slivers: [ - SliverToBoxAdapter( - child: HttpError( - errMsg: errMsg, - fn: () {}, - ), - ), - ], + return HttpError( + errMsg: errMsg, + fn: () {}, + isInSliver: false, ); } diff --git a/lib/pages/member_search/view.dart b/lib/pages/member_search/view.dart index ff0553a2..192c7346 100644 --- a/lib/pages/member_search/view.dart +++ b/lib/pages/member_search/view.dart @@ -164,13 +164,10 @@ class _MemberSearchPageState extends State ), ); } else { - return CustomScrollView( - slivers: [ - HttpError( - errMsg: data['msg'], - fn: () => setState(() {}), - ) - ], + return HttpError( + errMsg: data['msg'], + fn: () => setState(() {}), + isInSliver: false, ); } } else { diff --git a/lib/pages/message/like/view.dart b/lib/pages/message/like/view.dart index b30a0c4f..273e49f2 100644 --- a/lib/pages/message/like/view.dart +++ b/lib/pages/message/like/view.dart @@ -85,18 +85,14 @@ class _MessageLikePageState extends State { ); } else { // 请求错误 - return CustomScrollView( - slivers: [ - HttpError( - errMsg: snapshot.data['msg'], - fn: () { - setState(() { - _futureBuilderFuture = - _messageLikeCtr.queryMessageLike(); - }); - }, - ) - ], + return HttpError( + errMsg: snapshot.data['msg'], + fn: () { + setState(() { + _futureBuilderFuture = _messageLikeCtr.queryMessageLike(); + }); + }, + isInSliver: false, ); } } else { diff --git a/lib/pages/message/reply/view.dart b/lib/pages/message/reply/view.dart index 9776c7f7..b9112bfc 100644 --- a/lib/pages/message/reply/view.dart +++ b/lib/pages/message/reply/view.dart @@ -82,18 +82,15 @@ class _MessageReplyPageState extends State { ); } else { // 请求错误 - return CustomScrollView( - slivers: [ - HttpError( - errMsg: snapshot.data['msg'], - fn: () { - setState(() { - _futureBuilderFuture = - _messageReplyCtr.queryMessageReply(); - }); - }, - ) - ], + return HttpError( + errMsg: snapshot.data['msg'], + fn: () { + setState(() { + _futureBuilderFuture = + _messageReplyCtr.queryMessageReply(); + }); + }, + isInSliver: false, ); } } else { diff --git a/lib/pages/message/system/view.dart b/lib/pages/message/system/view.dart index ee10b639..a5b46858 100644 --- a/lib/pages/message/system/view.dart +++ b/lib/pages/message/system/view.dart @@ -63,18 +63,15 @@ class _MessageSystemPageState extends State { ); } else { // 请求错误 - return CustomScrollView( - slivers: [ - HttpError( - errMsg: snapshot.data['msg'], - fn: () { - setState(() { - _futureBuilderFuture = - _messageSystemCtr.queryMessageSystem(); - }); - }, - ) - ], + return HttpError( + errMsg: snapshot.data['msg'], + fn: () { + setState(() { + _futureBuilderFuture = + _messageSystemCtr.queryMessageSystem(); + }); + }, + isInSliver: false, ); } } else { diff --git a/lib/pages/search/view.dart b/lib/pages/search/view.dart index 95d3134e..c0f3e5c5 100644 --- a/lib/pages/search/view.dart +++ b/lib/pages/search/view.dart @@ -187,13 +187,10 @@ class _SearchPageState extends State with RouteAware { ), ); } else { - return CustomScrollView( - slivers: [ - HttpError( - errMsg: data['msg'], - fn: () => setState(() {}), - ) - ], + return HttpError( + errMsg: data['msg'], + fn: () => setState(() {}), + isInSliver: false, ); } } else { diff --git a/lib/pages/search_panel/view.dart b/lib/pages/search_panel/view.dart index fa669489..f032b12b 100644 --- a/lib/pages/search_panel/view.dart +++ b/lib/pages/search_panel/view.dart @@ -109,33 +109,25 @@ class _SearchPanelState extends State } }); } else { - return CustomScrollView( - physics: const NeverScrollableScrollPhysics(), - slivers: [ - HttpError( - errMsg: data['msg'], - fn: () { - setState(() { - _searchPanelController.onSearch(); - }); - }, - ), - ], + return HttpError( + errMsg: data['msg'], + fn: () { + setState(() { + _searchPanelController.onSearch(); + }); + }, + isInSliver: false, ); } } else { - return CustomScrollView( - physics: const NeverScrollableScrollPhysics(), - slivers: [ - HttpError( - errMsg: '没有相关数据', - fn: () { - setState(() { - _searchPanelController.onSearch(); - }); - }, - ), - ], + return HttpError( + errMsg: '没有相关数据', + fn: () { + setState(() { + _searchPanelController.onSearch(); + }); + }, + isInSliver: false, ); } } else { diff --git a/lib/pages/search_panel/widgets/article_panel.dart b/lib/pages/search_panel/widgets/article_panel.dart index be08ed56..75df52f9 100644 --- a/lib/pages/search_panel/widgets/article_panel.dart +++ b/lib/pages/search_panel/widgets/article_panel.dart @@ -174,14 +174,11 @@ Widget searchArticlePanel(BuildContext context, ctr, list) { ); }, ) - : CustomScrollView( - slivers: [ - HttpError( - errMsg: '没有数据', - isShowBtn: false, - fn: () => {}, - ) - ], + : HttpError( + errMsg: '没有数据', + isShowBtn: false, + fn: () => {}, + isInSliver: false, ), ); } diff --git a/lib/pages/search_panel/widgets/video_panel.dart b/lib/pages/search_panel/widgets/video_panel.dart index 7da6aaaa..6a7fa445 100644 --- a/lib/pages/search_panel/widgets/video_panel.dart +++ b/lib/pages/search_panel/widgets/video_panel.dart @@ -46,14 +46,11 @@ class SearchVideoPanel extends StatelessWidget { ); }, ) - : CustomScrollView( - slivers: [ - HttpError( - errMsg: '没有数据', - isShowBtn: false, - fn: () => {}, - ) - ], + : HttpError( + errMsg: '没有数据', + isShowBtn: false, + fn: () => {}, + isInSliver: false, ), ), // 分类筛选 diff --git a/lib/pages/subscription/view.dart b/lib/pages/subscription/view.dart index cb9993b0..1bd91cdc 100644 --- a/lib/pages/subscription/view.dart +++ b/lib/pages/subscription/view.dart @@ -68,30 +68,27 @@ class _SubPageState extends State { ), ); } else { - return const CustomScrollView( - physics: NeverScrollableScrollPhysics(), - slivers: [HttpError(errMsg: '', btnText: '没有数据', fn: null)], + return const HttpError( + errMsg: '', + btnText: '没有数据', + fn: null, + isInSliver: false, ); } } else { - return CustomScrollView( - physics: const NeverScrollableScrollPhysics(), - slivers: [ - HttpError( - errMsg: data?['msg'] ?? '请求异常', - btnText: data?['code'] == -101 ? '去登录' : null, - fn: () { - if (data?['code'] == -101) { - RoutePush.loginRedirectPush(); - } else { - setState(() { - _futureBuilderFuture = - _subController.querySubFolder(); - }); - } - }, - ), - ], + return HttpError( + errMsg: data?['msg'] ?? '请求异常', + btnText: data?['code'] == -101 ? '去登录' : null, + fn: () { + if (data?['code'] == -101) { + RoutePush.loginRedirectPush(); + } else { + setState(() { + _futureBuilderFuture = _subController.querySubFolder(); + }); + } + }, + isInSliver: false, ); } } else { From 163b8a3013652c661eeb25e32a45b139e4e0ad25 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Thu, 17 Oct 2024 00:36:02 +0800 Subject: [PATCH 15/90] =?UTF-8?q?feat:=20=E8=AF=84=E8=AE=BA=E6=9F=A5?= =?UTF-8?q?=E7=9C=8B=E6=9B=B4=E5=A4=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/models/video/reply/content.dart | 17 ++- .../detail/reply/widgets/reply_item.dart | 115 +++++++++++++----- 2 files changed, 98 insertions(+), 34 deletions(-) diff --git a/lib/models/video/reply/content.dart b/lib/models/video/reply/content.dart index d62a4bca..3a5f90ab 100644 --- a/lib/models/video/reply/content.dart +++ b/lib/models/video/reply/content.dart @@ -1,6 +1,7 @@ class ReplyContent { ReplyContent({ this.message, + this.message2, this.atNameToMid, // @的用户的mid null this.members, // 被@的用户List 如果有的话 [] this.emote, // 表情包 如果有的话 null @@ -13,6 +14,7 @@ class ReplyContent { }); String? message; + String? message2; Map? atNameToMid; List? members; Map? emote; @@ -24,10 +26,17 @@ class ReplyContent { Map? topicsMeta; ReplyContent.fromJson(Map json) { - message = json['message'] + message = message2 = json['message'] .replaceAll('>', '>') .replaceAll('"', '"') - .replaceAll(''', "'"); + .replaceAll(''', "'") + .replaceAll('&', '&') + .replaceAll('<', '<') + .replaceAll('>', '>') + .replaceAll('"', '"') + .replaceAll(''', "'") + .replaceAll(' ', ' '); + atNameToMid = json['at_name_to_mid'] ?? {}; members = json['members'] != null ? json['members'] @@ -39,8 +48,8 @@ class ReplyContent { pictures = json['pictures'] ?? []; vote = json['vote'] ?? {}; richText = json['rich_text'] ?? {}; - // 不包含@ 笔记 图片的时候,文字可折叠 - isText = atNameToMid!.isEmpty && vote!.isEmpty && pictures!.isEmpty; + // 不包含@ 笔记的时候,文字可折叠 + isText = atNameToMid!.isEmpty && vote!.isEmpty; topicsMeta = json['topics_meta'] ?? {}; } } diff --git a/lib/pages/video/detail/reply/widgets/reply_item.dart b/lib/pages/video/detail/reply/widgets/reply_item.dart index 18242fea..52605121 100644 --- a/lib/pages/video/detail/reply/widgets/reply_item.dart +++ b/lib/pages/video/detail/reply/widgets/reply_item.dart @@ -238,28 +238,53 @@ class ReplyItem extends StatelessWidget { // title Container( margin: const EdgeInsets.only(top: 10, left: 45, right: 6, bottom: 4), - child: Text.rich( - style: const TextStyle(height: 1.75), - maxLines: - replyItem!.content!.isText! && replyLevel == '1' ? 3 : 999, - overflow: TextOverflow.ellipsis, - TextSpan( - children: [ - if (replyItem!.isTop!) - const WidgetSpan( - alignment: PlaceholderAlignment.top, - child: PBadge( - text: 'TOP', - size: 'small', - stack: 'normal', - type: 'line', - fs: 9, + child: LayoutBuilder( + builder: (BuildContext context, BoxConstraints boxConstraints) { + String text = replyItem?.content?.message ?? ''; + bool didExceedMaxLines = false; + final double maxWidth = boxConstraints.maxWidth; + TextPainter? textPainter; + final int maxLines = + replyItem!.content!.isText! && replyLevel == '1' ? 6 : 999; + try { + textPainter = TextPainter( + text: TextSpan(text: text), + maxLines: maxLines, + textDirection: Directionality.of(context), + ); + textPainter.layout(maxWidth: maxWidth); + didExceedMaxLines = textPainter.didExceedMaxLines; + } catch (e) { + debugPrint('Error while measuring text: $e'); + didExceedMaxLines = false; + } + return Text.rich( + style: const TextStyle(height: 1.75), + TextSpan( + children: [ + if (replyItem!.isTop!) + const WidgetSpan( + alignment: PlaceholderAlignment.top, + child: PBadge( + text: 'TOP', + size: 'small', + stack: 'normal', + type: 'line', + fs: 9, + ), ), + buildContent( + context, + replyItem!, + replyReply, + null, + didExceedMaxLines, + textPainter, ), - buildContent(context, replyItem!, replyReply, null), - ], - ), - ), + ], + ), + ); + }), ), // 操作区域 bottonAction(context, replyItem!.replyControl, replySave), @@ -465,8 +490,8 @@ class ReplyItemRow extends StatelessWidget { fs: 9, ), ), - buildContent( - context, replies![i], replyReply, replyItem), + buildContent(context, replies![i], replyReply, + replyItem, false, null), ], ), ), @@ -508,7 +533,13 @@ class ReplyItemRow extends StatelessWidget { } InlineSpan buildContent( - BuildContext context, replyItem, replyReply, fReplyItem) { + BuildContext context, + replyItem, + replyReply, + fReplyItem, + bool didExceedMaxLines, + TextPainter? textPainter, +) { final String routePath = Get.currentRoute; bool isVideoPage = routePath.startsWith('/video'); ColorScheme colorScheme = Theme.of(context).colorScheme; @@ -519,6 +550,25 @@ InlineSpan buildContent( final content = replyItem.content; final List spanChilds = []; + if (didExceedMaxLines && content.message != '') { + final textSize = textPainter!.size; + var position = textPainter.getPositionForOffset( + Offset( + textSize.width, + textSize.height, + ), + ); + final endOffset = textPainter.getOffsetBefore(position.offset); + + if (endOffset != null && endOffset > 0) { + content.message = content.message.substring(0, endOffset); + } else { + content.message = content.message.substring(0, position.offset); + } + } else { + content.message = content.message2; + } + // 投票 if (content.vote.isNotEmpty) { content.message.splitMapJoin(RegExp(r"\{vote:.*?\}"), @@ -547,13 +597,6 @@ InlineSpan buildContent( }); } content.message = content.message.replaceAll(RegExp(r"\{vote:.*?\}"), ' '); - content.message = content.message - .replaceAll('&', '&') - .replaceAll('<', '<') - .replaceAll('>', '>') - .replaceAll('"', '"') - .replaceAll(''', "'") - .replaceAll(' ', ' '); // 构建正则表达式 final List specialTokens = [ ...content.emote.keys, @@ -874,6 +917,18 @@ InlineSpan buildContent( } } } + + if (didExceedMaxLines) { + spanChilds.add( + TextSpan( + text: '\n查看更多', + style: TextStyle( + color: colorScheme.primary, + ), + ), + ); + } + // 图片渲染 if (content.pictures.isNotEmpty) { final List picList = []; From 113a76894aeb8eff00210bc0fd5446d4edb8b263 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Thu, 17 Oct 2024 00:40:42 +0800 Subject: [PATCH 16/90] =?UTF-8?q?feat:=20=E5=B8=A6=E5=9B=BE=E8=AF=84?= =?UTF-8?q?=E8=AE=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ios/Podfile.lock | 6 + lib/http/api.dart | 3 + lib/http/reply.dart | 43 ++++++ lib/http/video.dart | 29 +++- lib/pages/video/detail/reply_new/view.dart | 136 +++++++++++++++++- linux/flutter/generated_plugin_registrant.cc | 4 + linux/flutter/generated_plugins.cmake | 1 + macos/Flutter/GeneratedPluginRegistrant.swift | 2 + pubspec.lock | 96 +++++++++++++ pubspec.yaml | 2 + .../flutter/generated_plugin_registrant.cc | 3 + windows/flutter/generated_plugins.cmake | 1 + 12 files changed, 319 insertions(+), 7 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index a400600f..ef17d378 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -27,6 +27,8 @@ PODS: - Flutter - GT3Captcha-iOS - GT3Captcha-iOS (0.15.8.3) + - image_picker_ios (0.0.1): + - Flutter - media_kit_libs_ios_video (1.0.4): - Flutter - media_kit_native_event_loop (1.0.0): @@ -77,6 +79,7 @@ DEPENDENCIES: - flutter_volume_controller (from `.symlinks/plugins/flutter_volume_controller/ios`) - fluttertoast (from `.symlinks/plugins/fluttertoast/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_native_event_loop (from `.symlinks/plugins/media_kit_native_event_loop/ios`) - media_kit_video (from `.symlinks/plugins/media_kit_video/ios`) @@ -124,6 +127,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/fluttertoast/ios" gt3_flutter_plugin: :path: ".symlinks/plugins/gt3_flutter_plugin/ios" + image_picker_ios: + :path: ".symlinks/plugins/image_picker_ios/ios" media_kit_libs_ios_video: :path: ".symlinks/plugins/media_kit_libs_ios_video/ios" media_kit_native_event_loop: @@ -173,6 +178,7 @@ SPEC CHECKSUMS: FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a gt3_flutter_plugin: 5bd2c08d3c19cbb6ee3b08f4358439e54c8ab2ee GT3Captcha-iOS: 5e3b1077834d8a9d6f4d64a447a30af3e14affe6 + image_picker_ios: c560581cceedb403a6ff17f2f816d7fea1421fc1 media_kit_libs_ios_video: a5fe24bc7875ccd6378a0978c13185e1344651c1 media_kit_native_event_loop: e6b2ab20cf0746eb1c33be961fcf79667304fa2a media_kit_video: 5da63f157170e5bf303bf85453b7ef6971218a2e diff --git a/lib/http/api.dart b/lib/http/api.dart index df0b9c85..e6c09870 100644 --- a/lib/http/api.dart +++ b/lib/http/api.dart @@ -592,4 +592,7 @@ class Api { /// 直播间记录 static const String liveRoomEntry = '${HttpString.liveBaseUrl}/xlive/web-room/v1/index/roomEntryAction'; + + /// 图片上传 + static const String uploadImage = '/x/dynamic/feed/draw/upload_bfs'; } diff --git a/lib/http/reply.dart b/lib/http/reply.dart index 880f9072..ae27ede2 100644 --- a/lib/http/reply.dart +++ b/lib/http/reply.dart @@ -1,3 +1,6 @@ +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/emote.dart'; import 'api.dart'; @@ -115,4 +118,44 @@ class ReplyHttp { }; } } + + // 图片上传 + 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 xFileToMultipartFile(XFile xFile) async { + var file = File(xFile.path); + var bytes = await file.readAsBytes(); + return MultipartFile.fromBytes(bytes, filename: xFile.name); + } } diff --git a/lib/http/video.dart b/lib/http/video.dart index 160f5db2..12c0dce7 100644 --- a/lib/http/video.dart +++ b/lib/http/video.dart @@ -1,4 +1,6 @@ +import 'dart:convert'; import 'dart:developer'; +import 'package:dio/dio.dart'; import 'package:hive/hive.dart'; import '../common/constants.dart'; import '../models/common/reply_type.dart'; @@ -343,18 +345,35 @@ class VideoHttp { required String message, int? root, int? parent, + List>? pictures, }) async { if (message == '') { return {'status': false, 'data': [], 'msg': '请输入评论内容'}; } - var res = await Request().post(Api.replyAdd, queryParameters: { - 'type': type.index, + var params = { + 'plat': 1, 'oid': oid, - 'root': root == null || root == 0 ? '' : root, - 'parent': parent == null || parent == 0 ? '' : parent, + '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( + Api.replyAdd, + queryParameters: { + 'w_rid': sign['w_rid'], + 'wts': sign['wts'], + }, + data: formData, + ); log(res.toString()); if (res.data['code'] == 0) { return {'status': true, 'data': res.data['data']}; diff --git a/lib/pages/video/detail/reply_new/view.dart b/lib/pages/video/detail/reply_new/view.dart index 0f04455f..72a87d89 100644 --- a/lib/pages/video/detail/reply_new/view.dart +++ b/lib/pages/video/detail/reply_new/view.dart @@ -1,13 +1,18 @@ import 'dart:async'; +import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; +import 'package:image_picker/image_picker.dart'; import 'package:pilipala/http/dynamics.dart'; +import 'package:pilipala/http/reply.dart'; import 'package:pilipala/http/video.dart'; import 'package:pilipala/models/common/reply_type.dart'; import 'package:pilipala/models/video/reply/emote.dart'; import 'package:pilipala/models/video/reply/item.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 'toolbar_icon_button.dart'; @@ -44,6 +49,9 @@ class _VideoReplyNewDialogState extends State RxBool isForward = false.obs; RxBool showForward = false.obs; RxString message = ''.obs; + final ImagePicker _picker = ImagePicker(); + RxList imageList = [''].obs; + List> pictures = []; @override void initState() { @@ -60,6 +68,7 @@ class _VideoReplyNewDialogState extends State if (routePath.startsWith('/video')) { showForward.value = true; } + imageList.clear(); } _autoFocus() async { @@ -90,6 +99,7 @@ class _VideoReplyNewDialogState extends State message: widget.replyItem != null && widget.replyItem!.root != 0 ? ' 回复 @${widget.replyItem!.member!.uname!} : ${message.value}' : message.value, + pictures: pictures, ); if (result['status']) { SmartDialog.showToast(result['data']['success_toast']); @@ -125,6 +135,62 @@ class _VideoReplyNewDialogState extends State ); } + void onChooseImage() async { + if (mounted) { + try { + final XFile? pickedFile = + await _picker.pickImage(source: ImageSource.gallery); + print('选择图片成功: ${pickedFile}'); + var res = await ReplyHttp.uploadImage(xFile: pickedFile!); + if (res['status']) { + imageList.add(res['data']['img_src']); + pictures.add(res['data']); + print('imageList: $imageList'); + print('pictures: $pictures'); + } + } catch (e) { + print('选择图片失败: $e'); + } + } + } + + void onPreviewImg(picList, initIndex, context) { + Navigator.of(context).push( + HeroDialogRoute( + 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 void didChangeMetrics() { super.didChangeMetrics(); @@ -175,10 +241,11 @@ class _VideoReplyNewDialogState extends State ), child: Column( mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, children: [ ConstrainedBox( constraints: const BoxConstraints( - maxHeight: 200, + maxHeight: 250, minHeight: 120, ), child: Container( @@ -209,6 +276,64 @@ class _VideoReplyNewDialogState extends State ), ), ), + 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: () { + 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( height: 1, color: Theme.of(context).dividerColor.withOpacity(0.1), @@ -240,7 +365,7 @@ class _VideoReplyNewDialogState extends State toolbarType: toolbarType, selected: toolbarType == 'input', ), - const SizedBox(width: 20), + const SizedBox(width: 10), ToolbarIconButton( onPressed: () { if (toolbarType == 'input') { @@ -254,6 +379,13 @@ class _VideoReplyNewDialogState extends State toolbarType: toolbarType, selected: toolbarType == 'emote', ), + const SizedBox(width: 10), + ToolbarIconButton( + onPressed: onChooseImage, + icon: const Icon(Icons.photo, size: 22), + toolbarType: toolbarType, + selected: toolbarType == 'picture', + ), const SizedBox(width: 6), Obx( () => showForward.value diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index c3b56ecd..1b712752 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -7,6 +7,7 @@ #include "generated_plugin_registrant.h" #include +#include #include #include #include @@ -16,6 +17,9 @@ void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) dynamic_color_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "DynamicColorPlugin"); 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 = fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterVolumeControllerPlugin"); flutter_volume_controller_plugin_register_with_registrar(flutter_volume_controller_registrar); diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index 70cdeb4b..29b0d451 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -4,6 +4,7 @@ list(APPEND FLUTTER_PLUGIN_LIST dynamic_color + file_selector_linux flutter_volume_controller media_kit_libs_linux media_kit_video diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 8af2f922..f52be498 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -10,6 +10,7 @@ import audio_session import connectivity_plus import device_info_plus import dynamic_color +import file_selector_macos import flutter_volume_controller import media_kit_libs_macos_video import media_kit_video @@ -27,6 +28,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { ConnectivityPlusPlugin.register(with: registry.registrar(forPlugin: "ConnectivityPlusPlugin")) DeviceInfoPlusMacosPlugin.register(with: registry.registrar(forPlugin: "DeviceInfoPlusMacosPlugin")) DynamicColorPlugin.register(with: registry.registrar(forPlugin: "DynamicColorPlugin")) + FileSelectorPlugin.register(with: registry.registrar(forPlugin: "FileSelectorPlugin")) FlutterVolumeControllerPlugin.register(with: registry.registrar(forPlugin: "FlutterVolumeControllerPlugin")) MediaKitLibsMacosVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitLibsMacosVideoPlugin")) MediaKitVideoPlugin.register(with: registry.registrar(forPlugin: "MediaKitVideoPlugin")) diff --git a/pubspec.lock b/pubspec.lock index b9d48c78..23a8f1ef 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -513,6 +513,38 @@ packages: url: "https://pub.flutter-io.cn" source: hosted 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: dependency: transitive description: @@ -758,6 +790,70 @@ packages: url: "https://pub.flutter-io.cn" source: hosted 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: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 617b0c7d..79c16273 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -151,6 +151,8 @@ dependencies: brotli: ^0.6.0 # 文本语法高亮 re_highlight: ^0.0.3 + # 图片选择器 + image_picker: ^1.1.2 dev_dependencies: flutter_test: diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index d2cff3b2..29721071 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -21,6 +22,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("ConnectivityPlusWindowsPlugin")); DynamicColorPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("DynamicColorPluginCApi")); + FileSelectorWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("FileSelectorWindows")); FlutterVolumeControllerPluginCApiRegisterWithRegistrar( registry->GetRegistrarForPlugin("FlutterVolumeControllerPluginCApi")); MediaKitLibsWindowsVideoPluginCApiRegisterWithRegistrar( diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 5d25e134..d3c91196 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -5,6 +5,7 @@ list(APPEND FLUTTER_PLUGIN_LIST connectivity_plus dynamic_color + file_selector_windows flutter_volume_controller media_kit_libs_windows_video media_kit_video From b60e693e2e4ff1044724f9fde55f57dfc8b743df Mon Sep 17 00:00:00 2001 From: guozhigq Date: Thu, 17 Oct 2024 23:24:45 +0800 Subject: [PATCH 17/90] mod: reply api --- lib/http/api.dart | 2 +- lib/http/reply.dart | 19 ++-- lib/models/video/reply/data.dart | 94 +++++++++++++++++++- lib/pages/dynamics/detail/controller.dart | 14 +-- lib/pages/html/controller.dart | 14 +-- lib/pages/video/detail/reply/controller.dart | 26 ++---- 6 files changed, 123 insertions(+), 46 deletions(-) diff --git a/lib/http/api.dart b/lib/http/api.dart index d7d60160..3d2a7efa 100644 --- a/lib/http/api.dart +++ b/lib/http/api.dart @@ -104,7 +104,7 @@ class Api { // 评论列表 // https://api.bilibili.com/x/v2/reply/main?csrf=6e22efc1a47225ea25f901f922b5cfdd&mode=3&oid=254175381&pagination_str=%7B%22offset%22:%22%22%7D&plat=1&seek_rpid=0&type=11 - static const String replyList = '/x/v2/reply'; + static const String replyList = '/x/v2/reply/main'; // 楼中楼 static const String replyReplyList = '/x/v2/reply/reply'; diff --git a/lib/http/reply.dart b/lib/http/reply.dart index c07d9e81..4b8061ad 100644 --- a/lib/http/reply.dart +++ b/lib/http/reply.dart @@ -1,3 +1,5 @@ +import 'dart:convert'; + import '../models/video/reply/data.dart'; import '../models/video/reply/emote.dart'; import 'api.dart'; @@ -6,17 +8,16 @@ import 'init.dart'; class ReplyHttp { static Future replyList({ required int oid, - required int pageNum, + required String nextOffset, required int type, int? ps, int sort = 1, }) async { var res = await Request().get(Api.replyList, data: { 'oid': oid, - 'pn': pageNum, 'type': type, - 'sort': sort, - 'ps': ps ?? 20 + 'pagination_str': jsonEncode({'offset': nextOffset}), + 'mode': sort + 2, }); if (res.data['code'] == 0) { return { @@ -52,19 +53,13 @@ class ReplyHttp { if (res.data['code'] == 0) { return { 'status': true, - 'data': ReplyData.fromJson(res.data['data']), + 'data': ReplyReplyData.fromJson(res.data['data']), }; } else { - Map errMap = { - -400: '请求错误', - -404: '无此项', - 12002: '评论区已关闭', - 12009: '评论主体的type不合法', - }; return { 'status': false, 'date': [], - 'msg': errMap[res.data['code']] ?? '请求异常', + 'msg': res.data['message'], }; } } diff --git a/lib/models/video/reply/data.dart b/lib/models/video/reply/data.dart index cc419777..6e069c50 100644 --- a/lib/models/video/reply/data.dart +++ b/lib/models/video/reply/data.dart @@ -6,6 +6,98 @@ import 'upper.dart'; class ReplyData { ReplyData({ + this.cursor, + this.config, + this.replies, + this.topReplies, + this.upper, + }); + + ReplyCursor? cursor; + ReplyConfig? config; + late List? replies; + late List? topReplies; + ReplyUpper? upper; + + ReplyData.fromJson(Map json) { + cursor = ReplyCursor.fromJson(json['cursor']); + config = ReplyConfig.fromJson(json['config']); + replies = json['replies'] != null + ? json['replies'] + .map( + (item) => ReplyItemModel.fromJson(item, json['upper']['mid'])) + .toList() + : []; + topReplies = json['top_replies'] != null + ? json['top_replies'] + .map((item) => ReplyItemModel.fromJson( + item, json['upper']['mid'], + isTopStatus: true)) + .toList() + : []; + upper = ReplyUpper.fromJson(json['upper']); + } +} + +class ReplyCursor { + ReplyCursor({ + this.isBegin, + this.prev, + this.next, + this.isEnd, + this.mode, + this.modeText, + this.allCount, + this.supportMode, + this.name, + this.paginationReply, + this.sessionId, + }); + + bool? isBegin; + int? prev; + int? next; + bool? isEnd; + int? mode; + String? modeText; + int? allCount; + List? supportMode; + String? name; + PaginationReply? paginationReply; + String? sessionId; + + ReplyCursor.fromJson(Map json) { + isBegin = json['is_begin']; + prev = json['prev']; + next = json['next']; + isEnd = json['is_end']; + mode = json['mode']; + modeText = json['mode_text']; + allCount = json['all_count']; + supportMode = json['support_mode'].cast(); + name = json['name']; + paginationReply = json['pagination_reply'] != null + ? PaginationReply.fromJson(json['pagination_reply']) + : null; + sessionId = json['session_id']; + } +} + +class PaginationReply { + PaginationReply({ + this.nextOffset, + this.prevOffset, + }); + String? nextOffset; + String? prevOffset; + PaginationReply.fromJson(Map json) { + nextOffset = json['next_offset']; + prevOffset = json['prev_offset']; + } +} + +class ReplyReplyData { + ReplyReplyData({ this.page, this.config, this.replies, @@ -19,7 +111,7 @@ class ReplyData { late List? topReplies; ReplyUpper? upper; - ReplyData.fromJson(Map json) { + ReplyReplyData.fromJson(Map json) { page = ReplyPage.fromJson(json['page']); config = ReplyConfig.fromJson(json['config']); replies = json['replies'] != null diff --git a/lib/pages/dynamics/detail/controller.dart b/lib/pages/dynamics/detail/controller.dart index f34de061..f2e990cd 100644 --- a/lib/pages/dynamics/detail/controller.dart +++ b/lib/pages/dynamics/detail/controller.dart @@ -14,7 +14,7 @@ class DynamicDetailController extends GetxController { int? type; dynamic item; int? floor; - int currentPage = 0; + String nextOffset = ""; bool isLoadingMore = false; RxString noMore = ''.obs; RxList replyList = [].obs; @@ -49,25 +49,25 @@ class DynamicDetailController extends GetxController { Future queryReplyList({reqType = 'init'}) async { if (reqType == 'init') { - currentPage = 0; + nextOffset = ""; } var res = await ReplyHttp.replyList( oid: oid!, - pageNum: currentPage + 1, + nextOffset: nextOffset, type: type!, sort: _sortType.index, ); if (res['status']) { List replies = res['data'].replies; - acount.value = res['data'].page.acount; + acount.value = res['data'].cursor.allCount; + nextOffset = res['data'].cursor.paginationReply.nextOffset ?? ""; if (replies.isNotEmpty) { - currentPage++; noMore.value = '加载中...'; - if (replies.length < 20) { + if (res['data'].cursor.isEnd == true) { noMore.value = '没有更多了'; } } else { - noMore.value = currentPage == 0 ? '还没有评论' : '没有更多了'; + noMore.value = nextOffset == "" ? '还没有评论' : '没有更多了'; } if (reqType == 'init') { // 添加置顶回复 diff --git a/lib/pages/html/controller.dart b/lib/pages/html/controller.dart index 1175ce29..7ba41966 100644 --- a/lib/pages/html/controller.dart +++ b/lib/pages/html/controller.dart @@ -15,7 +15,7 @@ class HtmlRenderController extends GetxController { RxInt oid = (-1).obs; late Map response; int? floor; - int currentPage = 0; + String nextOffset = ""; bool isLoadingMore = false; RxString noMore = ''.obs; RxList replyList = [].obs; @@ -52,21 +52,21 @@ class HtmlRenderController extends GetxController { Future queryReplyList({reqType = 'init'}) async { var res = await ReplyHttp.replyList( oid: oid.value, - pageNum: currentPage + 1, + nextOffset: nextOffset, type: type, sort: _sortType.index, ); if (res['status']) { List replies = res['data'].replies; - acount.value = res['data'].page.acount; + acount.value = res['data'].cursor.allCount; + nextOffset = res['data'].cursor.paginationReply.nextOffset ?? ""; if (replies.isNotEmpty) { - currentPage++; noMore.value = '加载中...'; - if (replies.length < 20) { + if (res['data'].cursor.isEnd == true) { noMore.value = '没有更多了'; } } else { - noMore.value = currentPage == 0 ? '还没有评论' : '没有更多了'; + noMore.value = nextOffset == "" ? '还没有评论' : '没有更多了'; } if (reqType == 'init') { // 添加置顶回复 @@ -102,7 +102,7 @@ class HtmlRenderController extends GetxController { } sortTypeTitle.value = _sortType.titles; sortTypeLabel.value = _sortType.labels; - currentPage = 0; + nextOffset = ""; replyList.clear(); queryReplyList(reqType: 'init'); } diff --git a/lib/pages/video/detail/reply/controller.dart b/lib/pages/video/detail/reply/controller.dart index c1929434..bb1d6a97 100644 --- a/lib/pages/video/detail/reply/controller.dart +++ b/lib/pages/video/detail/reply/controller.dart @@ -21,11 +21,9 @@ class VideoReplyController extends GetxController { // rpid 请求楼中楼回复 String? rpid; RxList replyList = [].obs; - // 当前页 - int currentPage = 0; + String nextOffset = ""; bool isLoadingMore = false; RxString noMore = ''.obs; - int ps = 20; RxInt count = 0.obs; // 当前回复的回复 ReplyItemModel? currentReplyItem; @@ -57,7 +55,7 @@ class VideoReplyController extends GetxController { } isLoadingMore = true; if (type == 'init') { - currentPage = 0; + nextOffset = ''; noMore.value = ''; } if (noMore.value == '没有更多了') { @@ -66,28 +64,20 @@ class VideoReplyController extends GetxController { } final res = await ReplyHttp.replyList( oid: aid!, - pageNum: currentPage + 1, - ps: ps, + nextOffset: nextOffset, type: ReplyType.video.index, sort: _sortType.index, ); if (res['status']) { final List replies = res['data'].replies; + nextOffset = res['data'].cursor.paginationReply.nextOffset ?? ""; if (replies.isNotEmpty) { noMore.value = '加载中...'; - - /// 第一页回复数小于20 - if (currentPage == 0 && replies.length < 18) { - noMore.value = '没有更多了'; - } - currentPage++; - - if (replyList.length == res['data'].page.acount) { + if (res['data'].cursor.isEnd == true) { noMore.value = '没有更多了'; } } else { - // 未登录状态replies可能返回null - noMore.value = currentPage == 0 ? '还没有评论' : '没有更多了'; + noMore.value = nextOffset == "" ? '还没有评论' : '没有更多了'; } if (type == 'init') { // 添加置顶回复 @@ -99,7 +89,7 @@ class VideoReplyController extends GetxController { } } replies.insertAll(0, res['data'].topReplies); - count.value = res['data'].page.count; + count.value = res['data'].cursor.allCount; replyList.value = replies; } else { replyList.addAll(replies); @@ -130,7 +120,7 @@ class VideoReplyController extends GetxController { } sortTypeTitle.value = _sortType.titles; sortTypeLabel.value = _sortType.labels; - currentPage = 0; + nextOffset = ""; noMore.value = ''; replyList.clear(); queryReplyList(type: 'init'); From fa79b92f826be0811d62cd312e736659496c3653 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Fri, 18 Oct 2024 00:09:34 +0800 Subject: [PATCH 18/90] opt: http --- lib/common/constants.dart | 2 -- lib/http/html.dart | 12 ------- lib/http/init.dart | 30 +++++++++------- lib/http/interceptor.dart | 18 +--------- lib/http/user.dart | 72 --------------------------------------- 5 files changed, 18 insertions(+), 116 deletions(-) diff --git a/lib/common/constants.dart b/lib/common/constants.dart index cac13688..0607206c 100644 --- a/lib/common/constants.dart +++ b/lib/common/constants.dart @@ -15,6 +15,4 @@ class Constants { // 59b43e04ad6965f34319062b478f83dd TV端 static const String appSec = '59b43e04ad6965f34319062b478f83dd'; static const String thirdSign = '04224646d1fea004e79606d3b038c84a'; - static const String thirdApi = - 'https://www.mcbbs.net/template/mcbbs/image/special_photo_bg.png'; } diff --git a/lib/http/html.dart b/lib/http/html.dart index 100887e5..87adacb9 100644 --- a/lib/http/html.dart +++ b/lib/http/html.dart @@ -21,7 +21,6 @@ class HtmlHttp { } try { Document rootTree = parse(response.data); - // log(response.data.body.toString()); Element body = rootTree.body!; Element appDom = body.querySelector('#app')!; Element authorHeader = appDom.querySelector('.fixed-author-header')!; @@ -52,7 +51,6 @@ class HtmlHttp { .className .split(' ')[1] .split('-')[2]; - // List imgList = opusDetail.querySelectorAll('bili-album__preview__picture__img'); return { 'status': true, 'avatar': avatar, @@ -76,20 +74,10 @@ class HtmlHttp { Element body = rootTree.body!; Element appDom = body.querySelector('#app')!; Element authorHeader = appDom.querySelector('.up-left')!; - // 头像 - // String avatar = - // authorHeader.querySelector('.bili-avatar-img')!.attributes['data-src']!; - // print(avatar); - // avatar = 'https:${avatar.split('@')[0]}'; String uname = authorHeader.querySelector('.up-name')!.text.trim(); // 动态详情 Element opusDetail = appDom.querySelector('.article-content')!; // 发布时间 - // String updateTime = - // opusDetail.querySelector('.opus-module-author__pub__text')!.text; - // print(updateTime); - - // String opusContent = opusDetail.querySelector('#read-article-holder')!.innerHtml; RegExp digitRegExp = RegExp(r'\d+'); diff --git a/lib/http/init.dart b/lib/http/init.dart index abe8d019..3117666e 100644 --- a/lib/http/init.dart +++ b/lib/http/init.dart @@ -8,7 +8,6 @@ import 'package:cookie_jar/cookie_jar.dart'; import 'package:dio/dio.dart'; import 'package:dio/io.dart'; import 'package:dio_cookie_manager/dio_cookie_manager.dart'; -// import 'package:dio_http2_adapter/dio_http2_adapter.dart'; import 'package:hive/hive.dart'; import 'package:pilipala/utils/id_utils.dart'; import '../utils/storage.dart'; @@ -171,15 +170,6 @@ class Request { dio = Dio(options); - /// fix 第三方登录 302重定向 跟iOS代理问题冲突 - // ..httpClientAdapter = Http2Adapter( - // ConnectionManager( - // idleTimeout: const Duration(milliseconds: 10000), - // onClientCreate: (_, ClientSetting config) => - // config.onBadCertificate = (_) => true, - // ), - // ); - /// 设置代理 if (enableSystemProxy) { dio.httpClientAdapter = IOHttpClientAdapter( @@ -247,11 +237,26 @@ class Request { } } + /* + * get请求 + */ + getWithoutCookie(url, {data}) { + return get( + url, + data: data, + options: Options( + headers: { + 'cookie': 'buvid3= ; b_nut= ; sid= ', + 'user-agent': headerUa(type: 'pc'), + }, + ), + ); + } + /* * post请求 */ post(url, {data, queryParameters, options, cancelToken, extra}) async { - // print('post-data: $data'); Response response; try { response = await dio.post( @@ -262,7 +267,6 @@ class Request { options ?? Options(contentType: Headers.formUrlEncodedContentType), cancelToken: cancelToken, ); - // print('post success: ${response.data}'); return response; } on DioException catch (e) { Response errResponse = Response( @@ -318,7 +322,7 @@ class Request { } } else { headerUa = - 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.2 Safari/605.1.15'; + 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36'; } return headerUa; } diff --git a/lib/http/interceptor.dart b/lib/http/interceptor.dart index 259a3bf9..9f9bc1c1 100644 --- a/lib/http/interceptor.dart +++ b/lib/http/interceptor.dart @@ -3,8 +3,6 @@ import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:dio/dio.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; -import 'package:hive/hive.dart'; -import '../utils/storage.dart'; class ApiInterceptor extends Interceptor { @override @@ -19,21 +17,7 @@ class ApiInterceptor extends Interceptor { @override void onResponse(Response response, ResponseInterceptorHandler handler) { try { - if (response.statusCode == 302) { - final List locations = response.headers['location']!; - if (locations.isNotEmpty) { - if (locations.first.startsWith('https://www.mcbbs.net')) { - final Uri uri = Uri.parse(locations.first); - final String? accessKey = uri.queryParameters['access_key']; - final String? mid = uri.queryParameters['mid']; - try { - Box localCache = GStrorage.localCache; - localCache.put(LocalCacheKey.accessKey, - {'mid': mid, 'value': accessKey}); - } catch (_) {} - } - } - } + // 在响应之后处理数据 } catch (err) { print('ApiInterceptor: $err'); } diff --git a/lib/http/user.dart b/lib/http/user.dart index 26b79523..b4e52cde 100644 --- a/lib/http/user.dart +++ b/lib/http/user.dart @@ -1,10 +1,6 @@ import 'dart:convert'; -import 'dart:developer'; - -import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:html/parser.dart'; import 'package:pilipala/models/video/later.dart'; -import '../common/constants.dart'; import '../models/model_hot_video_item.dart'; import '../models/user/fav_detail.dart'; import '../models/user/fav_folder.dart'; @@ -218,25 +214,6 @@ class UserHttp { } } - // 获取用户凭证 失效 - static Future thirdLogin() async { - var res = await Request().get( - 'https://passport.bilibili.com/login/app/third', - data: { - 'appkey': Constants.appKey, - 'api': Constants.thirdApi, - 'sign': Constants.thirdSign, - }, - ); - try { - if (res.data['code'] == 0 && res.data['data']['has_login'] == 1) { - Request().get(res.data['data']['confirm_uri']); - } - } catch (err) { - SmartDialog.showNotify(msg: '获取用户凭证: $err', notifyType: NotifyType.error); - } - } - // 清空稍后再看 static Future toViewClear() async { var res = await Request().post( @@ -283,30 +260,6 @@ class UserHttp { return {'status': false, 'msg': res.data['message']}; } } - // // 相互关系查询 - // static Future relationSearch(int mid) async { - // Map params = await WbiSign().makSign({ - // 'mid': mid, - // 'token': '', - // 'platform': 'web', - // 'web_location': 1550101, - // }); - // var res = await Request().get( - // Api.relationSearch, - // data: { - // 'mid': mid, - // 'w_rid': params['w_rid'], - // 'wts': params['wts'], - // }, - // ); - // if (res.data['code'] == 0) { - // // relation 主动状态 - // // 被动状态 - // return {'status': true, 'data': res.data['data']}; - // } else { - // return {'status': false, 'msg': res.data['message']}; - // } - // } // 搜索历史记录 static Future searchHistory( @@ -436,31 +389,6 @@ class UserHttp { } } - // 稍后再看播放全部 - // static Future toViewPlayAll({required int oid, required String bvid}) async { - // var res = await Request().get( - // Api.watchLaterHtml, - // data: { - // 'oid': oid, - // 'bvid': bvid, - // }, - // ); - // String scriptContent = - // extractScriptContents(parse(res.data).body!.outerHtml)[0]; - // int startIndex = scriptContent.indexOf('{'); - // int endIndex = scriptContent.lastIndexOf('};'); - // String jsonContent = scriptContent.substring(startIndex, endIndex + 1); - // // 解析JSON字符串为Map - // Map jsonData = json.decode(jsonContent); - // // 输出解析后的数据 - // return { - // 'status': true, - // 'data': jsonData['resourceList'] - // .map((e) => MediaVideoItemModel.fromJson(e)) - // .toList() - // }; - // } - static List extractScriptContents(String htmlContent) { RegExp scriptRegExp = RegExp(r'