diff --git a/lib/common/skeleton/media_bangumi.dart b/lib/common/skeleton/media_bangumi.dart index cf589254..98282cf0 100644 --- a/lib/common/skeleton/media_bangumi.dart +++ b/lib/common/skeleton/media_bangumi.dart @@ -3,14 +3,9 @@ import 'package:pilipala/common/constants.dart'; import 'skeleton.dart'; -class MediaBangumiSkeleton extends StatefulWidget { +class MediaBangumiSkeleton extends StatelessWidget { const MediaBangumiSkeleton({super.key}); - @override - State createState() => _MediaBangumiSkeletonState(); -} - -class _MediaBangumiSkeletonState extends State { @override Widget build(BuildContext context) { Color bgColor = Theme.of(context).colorScheme.onInverseSurface; @@ -35,25 +30,25 @@ class _MediaBangumiSkeletonState extends State { crossAxisAlignment: CrossAxisAlignment.start, children: [ Container( - color: Theme.of(context).colorScheme.onInverseSurface, + color: bgColor, width: 200, height: 20, margin: const EdgeInsets.only(bottom: 15), ), Container( - color: Theme.of(context).colorScheme.onInverseSurface, + color: bgColor, width: 150, height: 13, margin: const EdgeInsets.only(bottom: 5), ), Container( - color: Theme.of(context).colorScheme.onInverseSurface, + color: bgColor, width: 150, height: 13, margin: const EdgeInsets.only(bottom: 5), ), Container( - color: Theme.of(context).colorScheme.onInverseSurface, + color: bgColor, width: 150, height: 13, ), @@ -64,7 +59,7 @@ class _MediaBangumiSkeletonState extends State { decoration: BoxDecoration( borderRadius: const BorderRadius.all(Radius.circular(20)), - color: Theme.of(context).colorScheme.onInverseSurface, + color: bgColor, ), ), ], diff --git a/lib/common/skeleton/user_list.dart b/lib/common/skeleton/user_list.dart new file mode 100644 index 00000000..cd9d4eb3 --- /dev/null +++ b/lib/common/skeleton/user_list.dart @@ -0,0 +1,42 @@ +import 'package:flutter/material.dart'; +import '../constants.dart'; + +class UserListSkeleton extends StatelessWidget { + const UserListSkeleton({super.key}); + + @override + Widget build(BuildContext context) { + Color bgColor = Theme.of(context).colorScheme.onInverseSurface; + return Padding( + padding: const EdgeInsets.symmetric( + horizontal: StyleString.safeSpace, vertical: 7), + child: Row( + children: [ + ClipOval( + child: Container(width: 42, height: 42, color: bgColor), + ), + const SizedBox(width: 10), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container(color: bgColor, width: 60, height: 13), + const SizedBox(width: 10), + Container(color: bgColor, width: 40, height: 13), + ], + ), + const SizedBox(height: 6), + Container( + color: bgColor, + width: 100, + height: 13, + ), + ], + ), + ), + ], + )); + } +} diff --git a/lib/models/member/info.dart b/lib/models/member/info.dart index 83f94c54..602cdcdb 100644 --- a/lib/models/member/info.dart +++ b/lib/models/member/info.dart @@ -8,6 +8,7 @@ class MemberInfoModel { this.level, this.isFollowed, this.topPhoto, + this.silence, this.official, this.vip, this.liveRoom, @@ -21,6 +22,7 @@ class MemberInfoModel { int? level; bool? isFollowed; String? topPhoto; + int? silence; Map? official; Vip? vip; LiveRoom? liveRoom; @@ -34,6 +36,7 @@ class MemberInfoModel { level = json['level']; isFollowed = json['is_followed']; topPhoto = json['top_photo']; + silence = json['silence'] ?? 0; official = json['official']; vip = Vip.fromJson(json['vip']); liveRoom = diff --git a/lib/pages/blacklist/index.dart b/lib/pages/blacklist/index.dart index bdd2346f..0616c1dc 100644 --- a/lib/pages/blacklist/index.dart +++ b/lib/pages/blacklist/index.dart @@ -87,7 +87,9 @@ class _BlackListPageState extends State { itemCount: list.length, itemBuilder: (BuildContext context, int index) { return ListTile( - onTap: () {}, + onTap: () => Get.toNamed( + '/member?mid=${list[index].mid}', + arguments: {'face': list[index].face}), leading: NetworkImgLayer( width: 45, height: 45, diff --git a/lib/pages/member/controller.dart b/lib/pages/member/controller.dart index 6987337b..b805b33d 100644 --- a/lib/pages/member/controller.dart +++ b/lib/pages/member/controller.dart @@ -122,18 +122,13 @@ class MemberController extends GetxController { // 合并关注/取关和拉黑逻辑 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主?'; + contentText = attribute.value != 128 ? '确定拉黑UP主?' : '确定从黑名单移除UP主?'; act = attribute.value != 128 ? 5 : 6; } else { return; diff --git a/lib/pages/member/view.dart b/lib/pages/member/view.dart index 4ebc6153..9d446b07 100644 --- a/lib/pages/member/view.dart +++ b/lib/pages/member/view.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:get/get.dart'; @@ -7,6 +8,7 @@ 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/feed_back.dart'; import 'package:pilipala/utils/utils.dart'; import 'widgets/commen_widget.dart'; import 'widgets/conis.dart'; @@ -154,6 +156,25 @@ class _MemberPageState extends State bottom: MediaQuery.of(context).padding.bottom + 20, ), children: [ + Obx(() { + Rx memberInfo = _memberController.memberInfo; + return memberInfo.value.silence != null && + memberInfo.value.silence! == 1 + ? Container( + width: double.infinity, + padding: const EdgeInsets.only(top: 10, bottom: 10), + color: Theme.of(context).colorScheme.errorContainer, + child: Text( + '该账号封禁中', + textAlign: TextAlign.center, + style: TextStyle( + color: Theme.of(context).colorScheme.onErrorContainer, + fontSize: 16, + ), + ), + ) + : const SizedBox(); + }), profileWidget(), /// 动态链接 @@ -318,6 +339,7 @@ class _MemberPageState extends State Rx memberInfo = _memberController.memberInfo; return Obx( () => Column( + mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ ProfilePanel(ctr: _memberController), @@ -376,7 +398,7 @@ class _MemberPageState extends State .value.vip!.label!['img_label_uri_hans_static'], height: 20, ), - ] + ], ], ), if (memberInfo.value.official!['title'] != '') ...[ @@ -393,6 +415,39 @@ class _MemberPageState extends State ), ], const SizedBox(height: 6), + InkWell( + onTap: () { + feedBack(); + Clipboard.setData(ClipboardData( + text: memberInfo.value.mid.toString())); + SmartDialog.showToast('uid复制成功'); + }, + borderRadius: BorderRadius.circular(10), + child: Ink( + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surfaceVariant, + borderRadius: BorderRadius.circular(20), + ), + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 10, vertical: 4), + child: SizedBox( + height: 16, + child: Text( + 'uid: ${memberInfo.value.mid}', + overflow: TextOverflow.ellipsis, + style: TextStyle( + color: Theme.of(context) + .colorScheme + .onSurfaceVariant, + fontSize: 12, + ), + ), + ), + ), + ), + ), + const SizedBox(height: 6), SelectableText(memberInfo.value.sign ?? ''), ], ), diff --git a/lib/pages/search/controller.dart b/lib/pages/search/controller.dart index 64590934..8c478fd1 100644 --- a/lib/pages/search/controller.dart +++ b/lib/pages/search/controller.dart @@ -26,7 +26,6 @@ class SSearchController extends GetxController { Box setting = GStrorage.setting; bool enableHotKey = true; bool enableSearchSuggest = true; - late StreamController clearStream = StreamController.broadcast(); @override void onInit() { @@ -42,7 +41,6 @@ class SSearchController extends GetxController { final hint = parameters['hintText']; if (hint != null) { hintText = hint; - searchKeyWord.value = hintText; } } historyCacheList = GlobalDataCache().historyCacheList; @@ -55,10 +53,8 @@ class SSearchController extends GetxController { searchKeyWord.value = value; if (value == '') { searchSuggestList.value = []; - clearStream.add(false); return; } - clearStream.add(true); if (enableSearchSuggest) { _debouncer.call(() => querySearchSuggest(value)); } @@ -68,23 +64,20 @@ class SSearchController extends GetxController { controller.value.clear(); searchKeyWord.value = ''; searchSuggestList.value = []; - clearStream.add(false); } // 搜索 void submit() { - if (searchKeyWord.value == '') { + if (searchKeyWord.value == '' && hintText.isNotEmpty && hintText == '搜索') { return; + } else { + if (searchKeyWord.value == '' && hintText != '搜索') { + searchKeyWord.value = hintText; + controller.value.text = hintText; + } } - List arr = historyCacheList.where((e) => e != searchKeyWord.value).toList(); - arr.insert(0, searchKeyWord.value); - historyCacheList = arr; - - historyList.value = historyCacheList; - // 手动刷新 - historyList.refresh(); - localCache.put('cacheList', historyCacheList); - searchFocusNode.unfocus(); + hintText = '搜索'; + cacheHistory(); Get.toNamed('/searchResult', parameters: {'keyword': searchKeyWord.value}); } @@ -135,6 +128,18 @@ class SSearchController extends GetxController { historyCacheList = []; historyList.refresh(); localCache.put('cacheList', []); + GlobalDataCache().historyCacheList = []; SmartDialog.showToast('搜索历史已清空'); } + + cacheHistory() { + List arr = historyCacheList.where((e) => e != searchKeyWord.value).toList(); + arr.insert(0, searchKeyWord.value); + historyCacheList = arr; + historyList.value = historyCacheList; + historyList.refresh(); + localCache.put('cacheList', historyCacheList); + GlobalDataCache().historyCacheList = historyCacheList; + searchFocusNode.unfocus(); + } } diff --git a/lib/pages/search/view.dart b/lib/pages/search/view.dart index 7baeb13f..373edf15 100644 --- a/lib/pages/search/view.dart +++ b/lib/pages/search/view.dart @@ -63,24 +63,35 @@ class _SearchPageState extends State with RouteAware { focusNode: _searchController.searchFocusNode, controller: _searchController.controller.value, textInputAction: TextInputAction.search, - onChanged: (value) => _searchController.onChange(value), + onChanged: _searchController.onChange, decoration: InputDecoration( hintText: _searchController.hintText, border: InputBorder.none, - suffixIcon: StreamBuilder( - initialData: false, - stream: _searchController.clearStream.stream, - builder: (_, snapshot) { - if (snapshot.data == true) { - return IconButton( + suffix: Obx(() { + RxString searchKeyWord = _searchController.searchKeyWord; + if (searchKeyWord.value.isEmpty) { + return const SizedBox(); + } + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (RegExp(r'^\d+$').hasMatch(searchKeyWord.value)) + IconButton( + tooltip: '直达up主页', + icon: const Icon(Icons.person_outline, size: 22), + onPressed: () { + _searchController.cacheHistory(); + Get.toNamed('/member?mid=${searchKeyWord.value}', + arguments: {'face': null}); + }, + ), + IconButton( icon: const Icon(Icons.clear, size: 22), onPressed: () => _searchController.onClear(), - ); - } else { - return const SizedBox(); - } - }, - ), + ), + ], + ); + }), ), onSubmitted: (String value) => _searchController.submit(), ), diff --git a/lib/pages/search_panel/controller.dart b/lib/pages/search_panel/controller.dart index 2d1aa228..32eadfc0 100644 --- a/lib/pages/search_panel/controller.dart +++ b/lib/pages/search_panel/controller.dart @@ -1,7 +1,9 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:pilipala/http/member.dart'; import 'package:pilipala/http/search.dart'; import 'package:pilipala/models/common/search_type.dart'; +import 'package:pilipala/models/search/result.dart'; import 'package:pilipala/utils/id_utils.dart'; import 'package:pilipala/utils/utils.dart'; @@ -31,7 +33,7 @@ class SearchPanelController extends GetxController { tids: searchType!.type != 'video' ? null : tids.value, ); if (result['status']) { - if (type == 'onRefresh') { + if (type == 'init') { resultList.value = result['data'].list ?? []; } else { resultList.addAll(result['data'].list ?? []); @@ -39,12 +41,36 @@ class SearchPanelController extends GetxController { page.value++; onPushDetail(keyword, resultList); } + if (RegExp(r'^\d+$').hasMatch(keyword!) && + searchType == SearchType.bili_user) { + var res = await MemberHttp.memberInfo(mid: int.parse(keyword!)); + if (res['status']) { + try { + final user = SearchUserItemModel( + mid: res['data'].mid, + uname: res['data'].name, + upic: res['data'].face, + level: res['data'].level, + fans: null, + videos: null, + officialVerify: res['data'].official, + ); + if (resultList.isEmpty) { + resultList = [user].obs; + } else { + resultList.insert(0, user); + } + } catch (err) { + debugPrint('搜索用户信息失败: $err'); + } + } + } return result; } Future onRefresh() async { page.value = 1; - await onSearch(type: 'onRefresh'); + await onSearch(); } // 返回顶部并刷新 diff --git a/lib/pages/search_panel/view.dart b/lib/pages/search_panel/view.dart index f032b12b..6d9a9535 100644 --- a/lib/pages/search_panel/view.dart +++ b/lib/pages/search_panel/view.dart @@ -4,6 +4,7 @@ import 'package:easy_debounce/easy_throttle.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:pilipala/common/skeleton/media_bangumi.dart'; +import 'package:pilipala/common/skeleton/user_list.dart'; import 'package:pilipala/common/skeleton/video_card_h.dart'; import 'package:pilipala/common/widgets/http_error.dart'; import 'package:pilipala/models/common/search_type.dart'; @@ -81,11 +82,11 @@ class _SearchPanelState extends State future: _futureBuilderFuture, builder: (context, snapshot) { if (snapshot.connectionState == ConnectionState.done) { - if (snapshot.data != null) { - Map data = snapshot.data; + Map? data = snapshot.data; + if (data != null && data['status']) { var ctr = _searchPanelController; RxList list = ctr.resultList; - if (data['status']) { + if (list.isNotEmpty) { return Obx(() { switch (widget.searchType) { case SearchType.video: @@ -110,21 +111,18 @@ class _SearchPanelState extends State }); } else { return HttpError( - errMsg: data['msg'], - fn: () { - setState(() { - _searchPanelController.onSearch(); - }); - }, + errMsg: '没有数据', + isShowBtn: false, + fn: () => {}, isInSliver: false, ); } } else { return HttpError( - errMsg: '没有相关数据', + errMsg: data?['msg'] ?? '请求异常', fn: () { setState(() { - _searchPanelController.onSearch(); + _futureBuilderFuture = _searchPanelController.onRefresh(); }); }, isInSliver: false, @@ -143,7 +141,7 @@ class _SearchPanelState extends State case SearchType.media_bangumi: return const MediaBangumiSkeleton(); case SearchType.bili_user: - return const VideoCardHSkeleton(); + return const UserListSkeleton(); case SearchType.live_room: return const VideoCardHSkeleton(); default: diff --git a/lib/pages/search_panel/widgets/media_bangumi_panel.dart b/lib/pages/search_panel/widgets/media_bangumi_panel.dart index 5bba0ab8..0a3704bb 100644 --- a/lib/pages/search_panel/widgets/media_bangumi_panel.dart +++ b/lib/pages/search_panel/widgets/media_bangumi_panel.dart @@ -1,12 +1,7 @@ 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/badge.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart'; -import 'package:pilipala/http/search.dart'; -import 'package:pilipala/models/bangumi/info.dart'; -import 'package:pilipala/models/common/search_type.dart'; import 'package:pilipala/utils/route_push.dart'; import 'package:pilipala/utils/utils.dart'; @@ -30,8 +25,8 @@ Widget searchMbangumiPanel(BuildContext context, ctr, list) { // }); }, child: Padding( - padding: const EdgeInsets.fromLTRB( - StyleString.safeSpace, 7, StyleString.safeSpace, 7), + padding: const EdgeInsets.symmetric( + horizontal: StyleString.safeSpace, vertical: 7), child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ diff --git a/lib/pages/search_panel/widgets/user_panel.dart b/lib/pages/search_panel/widgets/user_panel.dart index 918082bc..561cb1f8 100644 --- a/lib/pages/search_panel/widgets/user_panel.dart +++ b/lib/pages/search_panel/widgets/user_panel.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:pilipala/common/constants.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart'; import 'package:pilipala/utils/utils.dart'; @@ -12,15 +13,16 @@ Widget searchUserPanel(BuildContext context, ctr, list) { controller: ctr!.scrollController, addAutomaticKeepAlives: false, addRepaintBoundaries: false, - itemCount: list!.length, + itemCount: list.length, itemBuilder: (context, index) { - var i = list![index]; - String heroTag = Utils.makeHeroTag(i!.mid); + var i = list[index]; + String heroTag = Utils.makeHeroTag(i.mid); return InkWell( onTap: () => Get.toNamed('/member?mid=${i.mid}', arguments: {'heroTag': heroTag, 'face': i.upic}), child: Padding( - padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 10), + padding: const EdgeInsets.symmetric( + horizontal: StyleString.safeSpace, vertical: 7), child: Row( children: [ Hero( @@ -41,7 +43,7 @@ Widget searchUserPanel(BuildContext context, ctr, list) { Row( children: [ Text( - i!.uname, + i.uname!, style: const TextStyle( fontSize: 14, ), @@ -53,15 +55,16 @@ Widget searchUserPanel(BuildContext context, ctr, list) { ), ], ), - Row( - children: [ - Text('粉丝:${i.fans} ', style: style), - Text(' 视频:${i.videos}', style: style) - ], - ), - if (i.officialVerify['desc'] != '') + if (i.fans != null && i.videos != null) + Row( + children: [ + Text('粉丝:${i.fans} ', style: style), + Text(' 视频:${i.videos}', style: style) + ], + ), + if (i.officialVerify!['desc'] != '') Text( - i.officialVerify['desc'], + i.officialVerify!['desc'], style: style, ), ], diff --git a/lib/pages/search_result/view.dart b/lib/pages/search_result/view.dart index 96fdd91d..9056905f 100644 --- a/lib/pages/search_result/view.dart +++ b/lib/pages/search_result/view.dart @@ -3,6 +3,7 @@ import 'package:get/get.dart'; import 'package:pilipala/models/common/search_type.dart'; import 'package:pilipala/pages/search_panel/index.dart'; import 'controller.dart'; +import 'widget/tab_bar.dart'; class SearchResultPage extends StatefulWidget { const SearchResultPage({super.key}); @@ -29,6 +30,17 @@ class _SearchResultPageState extends State ); } + // tab点击事件 + void _onTap(int index) { + if (index == _searchResultController.tabIndex) { + Get.find( + tag: SearchType.values[index].type + + _searchResultController.keyword!) + .animateToTop(); + } + _searchResultController.tabIndex = index; + } + @override Widget build(BuildContext context) { return Scaffold( @@ -55,50 +67,10 @@ class _SearchResultPageState extends State body: Column( children: [ const SizedBox(height: 4), - Container( - width: double.infinity, - padding: const EdgeInsets.only(left: 8), - color: Theme.of(context).colorScheme.surface, - child: Theme( - data: ThemeData( - splashColor: Colors.transparent, // 点击时的水波纹颜色设置为透明 - highlightColor: Colors.transparent, // 点击时的背景高亮颜色设置为透明 - ), - child: Obx( - () => (TabBar( - controller: _tabController, - tabs: [ - for (var i in _searchResultController.searchTabs) - Tab(text: "${i['label']} ${i['count'] ?? ''}") - ], - isScrollable: true, - indicatorWeight: 0, - indicatorPadding: - const EdgeInsets.symmetric(horizontal: 3, vertical: 8), - indicator: BoxDecoration( - color: Theme.of(context).colorScheme.secondaryContainer, - borderRadius: const BorderRadius.all(Radius.circular(20)), - ), - indicatorSize: TabBarIndicatorSize.tab, - labelColor: - Theme.of(context).colorScheme.onSecondaryContainer, - labelStyle: const TextStyle(fontSize: 13), - dividerColor: Colors.transparent, - unselectedLabelColor: Theme.of(context).colorScheme.outline, - tabAlignment: TabAlignment.start, - onTap: (index) { - if (index == _searchResultController.tabIndex) { - Get.find( - tag: SearchType.values[index].type + - _searchResultController.keyword!) - .animateToTop(); - } - - _searchResultController.tabIndex = index; - }, - )), - ), - ), + TabBarWidget( + onTap: _onTap, + tabController: _tabController!, + searchResultCtr: _searchResultController, ), Expanded( child: TabBarView( diff --git a/lib/pages/search_result/widget/tab_bar.dart b/lib/pages/search_result/widget/tab_bar.dart new file mode 100644 index 00000000..acb6a3de --- /dev/null +++ b/lib/pages/search_result/widget/tab_bar.dart @@ -0,0 +1,53 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:pilipala/pages/search_result/index.dart'; + +class TabBarWidget extends StatelessWidget { + final Function(int) onTap; + final TabController tabController; + final SearchResultController searchResultCtr; + + const TabBarWidget({ + required this.onTap, + required this.tabController, + required this.searchResultCtr, + Key? key, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + ColorScheme colorScheme = Theme.of(context).colorScheme; + Color transparent = Colors.transparent; + return Container( + width: double.infinity, + padding: const EdgeInsets.only(left: 8), + color: colorScheme.surface, + child: Theme( + data: ThemeData(splashColor: transparent, highlightColor: transparent), + child: Obx( + () => TabBar( + controller: tabController, + tabs: [ + for (var i in searchResultCtr.searchTabs) + Tab(text: "${i['label']} ${i['count'] ?? ''}"), + ], + isScrollable: true, + indicatorPadding: + const EdgeInsets.symmetric(horizontal: 3, vertical: 8), + indicator: BoxDecoration( + color: colorScheme.secondaryContainer, + borderRadius: const BorderRadius.all(Radius.circular(20)), + ), + indicatorSize: TabBarIndicatorSize.tab, + labelColor: colorScheme.onSecondaryContainer, + labelStyle: const TextStyle(fontSize: 13), + dividerColor: transparent, + unselectedLabelColor: colorScheme.outline, + tabAlignment: TabAlignment.start, + onTap: onTap, + ), + ), + ), + ); + } +} diff --git a/lib/pages/subscription/widgets/item.dart b/lib/pages/subscription/widgets/item.dart index 0389b4a6..bfa2e46d 100644 --- a/lib/pages/subscription/widgets/item.dart +++ b/lib/pages/subscription/widgets/item.dart @@ -90,52 +90,55 @@ class VideoContent extends StatelessWidget { return Expanded( child: Padding( padding: const EdgeInsets.fromLTRB(10, 2, 6, 0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + child: Stack( children: [ - Text( - subFolderItem.title!, - textAlign: TextAlign.start, - style: const TextStyle( - fontWeight: FontWeight.w500, - letterSpacing: 0.3, - ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + subFolderItem.title!, + textAlign: TextAlign.start, + maxLines: 3, + style: const TextStyle( + fontWeight: FontWeight.w500, + letterSpacing: 0.3, + ), + ), + const SizedBox(height: 2), + Text( + '合集 UP主:${subFolderItem.upper!.name!}', + textAlign: TextAlign.start, + style: TextStyle( + fontSize: Theme.of(context).textTheme.labelMedium!.fontSize, + color: Theme.of(context).colorScheme.outline, + ), + ), + const SizedBox(height: 2), + Text( + '${subFolderItem.mediaCount}个视频', + textAlign: TextAlign.start, + style: TextStyle( + fontSize: Theme.of(context).textTheme.labelMedium!.fontSize, + color: Theme.of(context).colorScheme.outline, + ), + ), + ], ), - const SizedBox(height: 2), - Text( - '合集 UP主:${subFolderItem.upper!.name!}', - textAlign: TextAlign.start, - style: TextStyle( - fontSize: Theme.of(context).textTheme.labelMedium!.fontSize, - color: Theme.of(context).colorScheme.outline, - ), - ), - const SizedBox(height: 2), - Text( - '${subFolderItem.mediaCount}个视频', - textAlign: TextAlign.start, - style: TextStyle( - fontSize: Theme.of(context).textTheme.labelMedium!.fontSize, - color: Theme.of(context).colorScheme.outline, - ), - ), - const Spacer(), isOwner - ? Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - IconButton( - style: ButtonStyle( - padding: MaterialStateProperty.all(EdgeInsets.zero), - ), - onPressed: () => cancelSub?.call(subFolderItem), - icon: Icon( - Icons.clear_outlined, - color: Theme.of(context).colorScheme.outline, - size: 18, - ), - ) - ], + ? Positioned( + right: 0, + bottom: -4, + child: IconButton( + style: ButtonStyle( + padding: MaterialStateProperty.all(EdgeInsets.zero), + ), + onPressed: () => cancelSub?.call(subFolderItem), + icon: Icon( + Icons.clear_outlined, + color: Theme.of(context).colorScheme.outline, + size: 18, + ), + ), ) : const SizedBox() ], diff --git a/lib/pages/video/detail/introduction/controller.dart b/lib/pages/video/detail/introduction/controller.dart index 2c1e47eb..e78d8121 100644 --- a/lib/pages/video/detail/introduction/controller.dart +++ b/lib/pages/video/detail/introduction/controller.dart @@ -338,73 +338,49 @@ class VideoIntroController extends GetxController { return; } final int currentStatus = followStatus['attribute']; - int actionStatus = 0; - switch (currentStatus) { - case 0: - actionStatus = 1; - break; - case 2: - actionStatus = 2; - break; - default: - actionStatus = 0; - break; + if (currentStatus == 128) { + modifyRelation('block', currentStatus); + } else { + modifyRelation('follow', currentStatus); } - SmartDialog.show( - useSystem: true, - animationType: SmartAnimationType.centerFade_otherSlide, + } + + // 操作用户关系 + Future modifyRelation(String actionType, int currentStatus) async { + final int mid = videoDetail.value.owner!.mid!; + String contentText; + int act; + if (actionType == 'follow') { + contentText = currentStatus != 0 ? '确定取消关注UP主?' : '确定关注UP主?'; + act = currentStatus != 0 ? 2 : 1; + } else if (actionType == 'block') { + contentText = '确定从黑名单移除UP主?'; + act = 6; + } else { + return; + } + + showDialog( + context: Get.context!, builder: (BuildContext context) { + final Color outline = Theme.of(Get.context!).colorScheme.outline; return AlertDialog( title: const Text('提示'), - content: Text(currentStatus == 0 ? '关注UP主?' : '取消关注UP主?'), + content: Text(contentText), actions: [ TextButton( - onPressed: () => SmartDialog.dismiss(), - child: Text( - '点错了', - style: TextStyle(color: Theme.of(context).colorScheme.outline), - ), + onPressed: Navigator.of(context).pop, + child: Text('点错了', style: TextStyle(color: outline)), ), TextButton( - onPressed: () async { - var result = await VideoHttp.relationMod( - mid: videoDetail.value.owner!.mid!, - act: actionStatus, - reSrc: 14, - ); - if (result['status']) { - switch (currentStatus) { - case 0: - actionStatus = 2; - break; - case 2: - actionStatus = 0; - break; - default: - actionStatus = 0; - break; - } - followStatus['attribute'] = actionStatus; - followStatus.refresh(); - if (actionStatus == 2) { - if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: const Text('关注成功'), - duration: const Duration(seconds: 2), - action: SnackBarAction( - label: '设置分组', - onPressed: setFollowGroup, - ), - showCloseIcon: true, - ), - ); - } - } - } - SmartDialog.dismiss(); - }, - child: const Text('确认'), + onPressed: () => modifyRelationFetch( + context, + mid, + act, + currentStatus, + actionType, + ), + child: const Text('确定'), ) ], ); @@ -412,6 +388,52 @@ class VideoIntroController extends GetxController { ); } + // 操作用户关系Future + Future modifyRelationFetch( + BuildContext context, + mid, + act, + currentStatus, + actionType, + ) async { + var res = await VideoHttp.relationMod(mid: mid, act: act, reSrc: 11); + if (context.mounted) { + Navigator.of(context).pop(); + } + if (res['status']) { + if (actionType == 'follow') { + final Map statusMap = { + 0: 2, + 2: 0, + }; + late int actionStatus; + actionStatus = statusMap[currentStatus] ?? 0; + followStatus['attribute'] = actionStatus; + if (currentStatus == 0 && Get.context!.mounted) { + ScaffoldMessenger.of(Get.context!).showSnackBar( + SnackBar( + content: const Text('关注成功'), + duration: const Duration(seconds: 2), + action: SnackBarAction( + label: '设置分组', + onPressed: setFollowGroup, + ), + showCloseIcon: true, + ), + ); + } else { + SmartDialog.showToast('取消关注成功'); + } + } else if (actionType == 'block') { + followStatus['attribute'] = 0; + SmartDialog.showToast('取消拉黑成功'); + } + followStatus.refresh(); + } else { + SmartDialog.showToast(res['msg']); + } + } + // 修改分P或番剧分集 Future changeSeasonOrbangu( String bvid, diff --git a/lib/pages/video/detail/introduction/view.dart b/lib/pages/video/detail/introduction/view.dart index 81d0c3f0..80176a7b 100644 --- a/lib/pages/video/detail/introduction/view.dart +++ b/lib/pages/video/detail/introduction/view.dart @@ -470,8 +470,8 @@ class _VideoInfoState extends State with TickerProviderStateMixin { const Spacer(), Obx( () { - final bool isFollowed = - videoIntroController.followStatus['attribute'] != 0; + final int attr = + videoIntroController.followStatus['attribute'] ?? 0; return videoIntroController.followStatus.isEmpty ? const SizedBox() : SizedBox( @@ -484,15 +484,19 @@ class _VideoInfoState extends State with TickerProviderStateMixin { left: 8, right: 8, ), - foregroundColor: isFollowed + foregroundColor: attr != 0 ? outline : t.colorScheme.onPrimary, - backgroundColor: isFollowed + backgroundColor: attr != 0 ? t.colorScheme.onInverseSurface : t.colorScheme.primary, // 设置按钮背景色 ), child: Text( - isFollowed ? '已关注' : '关注', + attr == 128 + ? '已拉黑' + : attr != 0 + ? '已关注' + : '关注', style: TextStyle( fontSize: t.textTheme.labelMedium!.fontSize, diff --git a/lib/pages/video/detail/introduction/widgets/intro_detail.dart b/lib/pages/video/detail/introduction/widgets/intro_detail.dart index 78c621c0..c74558a0 100644 --- a/lib/pages/video/detail/introduction/widgets/intro_detail.dart +++ b/lib/pages/video/detail/introduction/widgets/intro_detail.dart @@ -3,6 +3,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; +import 'package:pilipala/http/constants.dart'; import 'package:pilipala/utils/feed_back.dart'; import 'package:pilipala/utils/utils.dart'; @@ -15,6 +16,10 @@ class IntroDetail extends StatelessWidget { @override Widget build(BuildContext context) { + TextStyle textStyle = TextStyle( + fontSize: 14, + color: Theme.of(context).colorScheme.primary, + ); return SizedBox( width: double.infinity, child: Column( @@ -29,12 +34,7 @@ class IntroDetail extends StatelessWidget { Clipboard.setData(ClipboardData(text: videoDetail!.bvid!)); SmartDialog.showToast('已复制'); }, - child: Text( - videoDetail!.bvid!, - style: TextStyle( - fontSize: 13, - color: Theme.of(context).colorScheme.primary), - ), + child: Text(videoDetail!.bvid!, style: textStyle), ), const SizedBox(width: 10), GestureDetector( @@ -44,12 +44,18 @@ class IntroDetail extends StatelessWidget { ClipboardData(text: videoDetail!.aid!.toString())); SmartDialog.showToast('已复制'); }, - child: Text( - videoDetail!.aid!.toString(), - style: TextStyle( - fontSize: 13, - color: Theme.of(context).colorScheme.primary), - ), + child: Text(videoDetail!.aid!.toString(), style: textStyle), + ), + const SizedBox(width: 10), + GestureDetector( + onTap: () { + feedBack(); + String videoUrl = + '${HttpString.baseUrl}/video/${videoDetail!.bvid!}'; + Clipboard.setData(ClipboardData(text: videoUrl)); + SmartDialog.showToast('已复制视频链接'); + }, + child: Text('复制链接', style: textStyle), ) ], ), diff --git a/lib/utils/app_scheme.dart b/lib/utils/app_scheme.dart index 3a69843c..5d4196bc 100644 --- a/lib/utils/app_scheme.dart +++ b/lib/utils/app_scheme.dart @@ -212,9 +212,9 @@ class PiliSchame { } } - static Future biliScheme(SchemeEntity value) async { - final String host = value.host!; - final String path = value.path!; + static Future biliScheme(Uri value) async { + final String host = value.host; + final String path = value.path; switch (host) { case 'root': Navigator.popUntil( @@ -301,7 +301,7 @@ class PiliSchame { break; default: SmartDialog.showToast('未匹配地址,请联系开发者'); - Clipboard.setData(ClipboardData(text: value.toJson().toString())); + Clipboard.setData(ClipboardData(text: value.toString())); break; } }