From 8f6fe042cd23f167950c99b7434272684ec3082f Mon Sep 17 00:00:00 2001 From: guozhigq Date: Wed, 18 Sep 2024 00:16:02 +0800 Subject: [PATCH 01/10] =?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/10] 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/10] =?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/10] =?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/10] =?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/10] 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/10] 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/10] 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 174eff71510e259101a6d3e773ddf2da4efb3ba4 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Wed, 16 Oct 2024 14:17:24 +0800 Subject: [PATCH 09/10] 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 99a5891d5203b636cb8083bcc44c2cfd004b4edd Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sat, 19 Oct 2024 02:28:32 +0800 Subject: [PATCH 10/10] =?UTF-8?q?feat:=20up=E5=A4=B4=E5=83=8F=E9=A2=84?= =?UTF-8?q?=E8=A7=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/member/widgets/profile.dart | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/lib/pages/member/widgets/profile.dart b/lib/pages/member/widgets/profile.dart index 2509160f..e06309d0 100644 --- a/lib/pages/member/widgets/profile.dart +++ b/lib/pages/member/widgets/profile.dart @@ -3,6 +3,7 @@ import 'package:get/get.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart'; import 'package:pilipala/models/live/item.dart'; import 'package:pilipala/models/member/info.dart'; +import 'package:pilipala/plugin/pl_gallery/index.dart'; import 'package:pilipala/utils/utils.dart'; class ProfilePanel extends StatelessWidget { @@ -70,11 +71,27 @@ class ProfilePanel extends StatelessWidget { tag: ctr.heroTag!, child: Stack( children: [ - NetworkImgLayer( - width: 90, - height: 90, - type: 'avatar', - src: !loadingStatus ? memberInfo.face : ctr.face.value, + InkWell( + onTap: () { + Navigator.of(context).push( + HeroDialogRoute( + builder: (BuildContext context) => + InteractiveviewerGallery( + sources: [ + !loadingStatus ? memberInfo.face : ctr.face.value + ], + initIndex: 0, + onPageChanged: (int pageIndex) {}, + ), + ), + ); + }, + child: NetworkImgLayer( + width: 90, + height: 90, + type: 'avatar', + src: !loadingStatus ? memberInfo.face : ctr.face.value, + ), ), if (!loadingStatus && memberInfo.liveRoom != null &&