Merge branch 'design'

This commit is contained in:
guozhigq
2024-10-26 16:34:56 +08:00
19 changed files with 438 additions and 248 deletions

View File

@ -3,14 +3,9 @@ import 'package:pilipala/common/constants.dart';
import 'skeleton.dart'; import 'skeleton.dart';
class MediaBangumiSkeleton extends StatefulWidget { class MediaBangumiSkeleton extends StatelessWidget {
const MediaBangumiSkeleton({super.key}); const MediaBangumiSkeleton({super.key});
@override
State<MediaBangumiSkeleton> createState() => _MediaBangumiSkeletonState();
}
class _MediaBangumiSkeletonState extends State<MediaBangumiSkeleton> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Color bgColor = Theme.of(context).colorScheme.onInverseSurface; Color bgColor = Theme.of(context).colorScheme.onInverseSurface;
@ -35,25 +30,25 @@ class _MediaBangumiSkeletonState extends State<MediaBangumiSkeleton> {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Container( Container(
color: Theme.of(context).colorScheme.onInverseSurface, color: bgColor,
width: 200, width: 200,
height: 20, height: 20,
margin: const EdgeInsets.only(bottom: 15), margin: const EdgeInsets.only(bottom: 15),
), ),
Container( Container(
color: Theme.of(context).colorScheme.onInverseSurface, color: bgColor,
width: 150, width: 150,
height: 13, height: 13,
margin: const EdgeInsets.only(bottom: 5), margin: const EdgeInsets.only(bottom: 5),
), ),
Container( Container(
color: Theme.of(context).colorScheme.onInverseSurface, color: bgColor,
width: 150, width: 150,
height: 13, height: 13,
margin: const EdgeInsets.only(bottom: 5), margin: const EdgeInsets.only(bottom: 5),
), ),
Container( Container(
color: Theme.of(context).colorScheme.onInverseSurface, color: bgColor,
width: 150, width: 150,
height: 13, height: 13,
), ),
@ -64,7 +59,7 @@ class _MediaBangumiSkeletonState extends State<MediaBangumiSkeleton> {
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: borderRadius:
const BorderRadius.all(Radius.circular(20)), const BorderRadius.all(Radius.circular(20)),
color: Theme.of(context).colorScheme.onInverseSurface, color: bgColor,
), ),
), ),
], ],

View File

@ -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,
),
],
),
),
],
));
}
}

View File

@ -8,6 +8,7 @@ class MemberInfoModel {
this.level, this.level,
this.isFollowed, this.isFollowed,
this.topPhoto, this.topPhoto,
this.silence,
this.official, this.official,
this.vip, this.vip,
this.liveRoom, this.liveRoom,
@ -21,6 +22,7 @@ class MemberInfoModel {
int? level; int? level;
bool? isFollowed; bool? isFollowed;
String? topPhoto; String? topPhoto;
int? silence;
Map? official; Map? official;
Vip? vip; Vip? vip;
LiveRoom? liveRoom; LiveRoom? liveRoom;
@ -34,6 +36,7 @@ class MemberInfoModel {
level = json['level']; level = json['level'];
isFollowed = json['is_followed']; isFollowed = json['is_followed'];
topPhoto = json['top_photo']; topPhoto = json['top_photo'];
silence = json['silence'] ?? 0;
official = json['official']; official = json['official'];
vip = Vip.fromJson(json['vip']); vip = Vip.fromJson(json['vip']);
liveRoom = liveRoom =

View File

@ -87,7 +87,9 @@ class _BlackListPageState extends State<BlackListPage> {
itemCount: list.length, itemCount: list.length,
itemBuilder: (BuildContext context, int index) { itemBuilder: (BuildContext context, int index) {
return ListTile( return ListTile(
onTap: () {}, onTap: () => Get.toNamed(
'/member?mid=${list[index].mid}',
arguments: {'face': list[index].face}),
leading: NetworkImgLayer( leading: NetworkImgLayer(
width: 45, width: 45,
height: 45, height: 45,

View File

@ -122,18 +122,13 @@ class MemberController extends GetxController {
// 合并关注/取关和拉黑逻辑 // 合并关注/取关和拉黑逻辑
Future modifyRelation(String actionType) async { Future modifyRelation(String actionType) async {
if (userInfo == null) {
SmartDialog.showToast('账号未登录');
return;
}
String contentText; String contentText;
int act; int act;
if (actionType == 'follow') { if (actionType == 'follow') {
contentText = memberInfo.value.isFollowed! ? '确定取消关注UP主?' : '确定关注UP主?'; contentText = memberInfo.value.isFollowed! ? '确定取消关注UP主?' : '确定关注UP主?';
act = memberInfo.value.isFollowed! ? 2 : 1; act = memberInfo.value.isFollowed! ? 2 : 1;
} else if (actionType == 'block') { } else if (actionType == 'block') {
contentText = attribute.value != 128 ? '确定拉黑UP主?' : '确定从黑名单移除UP主'; contentText = attribute.value != 128 ? '确定拉黑UP主?' : '确定从黑名单移除UP主?';
act = attribute.value != 128 ? 5 : 6; act = attribute.value != 128 ? 5 : 6;
} else { } else {
return; return;

View File

@ -1,5 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
@ -7,6 +8,7 @@ import 'package:pilipala/common/constants.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart';
import 'package:pilipala/models/member/info.dart'; import 'package:pilipala/models/member/info.dart';
import 'package:pilipala/pages/member/index.dart'; import 'package:pilipala/pages/member/index.dart';
import 'package:pilipala/utils/feed_back.dart';
import 'package:pilipala/utils/utils.dart'; import 'package:pilipala/utils/utils.dart';
import 'widgets/commen_widget.dart'; import 'widgets/commen_widget.dart';
import 'widgets/conis.dart'; import 'widgets/conis.dart';
@ -154,6 +156,25 @@ class _MemberPageState extends State<MemberPage>
bottom: MediaQuery.of(context).padding.bottom + 20, bottom: MediaQuery.of(context).padding.bottom + 20,
), ),
children: [ children: [
Obx(() {
Rx<MemberInfoModel> 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(), profileWidget(),
/// 动态链接 /// 动态链接
@ -318,6 +339,7 @@ class _MemberPageState extends State<MemberPage>
Rx<MemberInfoModel> memberInfo = _memberController.memberInfo; Rx<MemberInfoModel> memberInfo = _memberController.memberInfo;
return Obx( return Obx(
() => Column( () => Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
ProfilePanel(ctr: _memberController), ProfilePanel(ctr: _memberController),
@ -376,7 +398,7 @@ class _MemberPageState extends State<MemberPage>
.value.vip!.label!['img_label_uri_hans_static'], .value.vip!.label!['img_label_uri_hans_static'],
height: 20, height: 20,
), ),
] ],
], ],
), ),
if (memberInfo.value.official!['title'] != '') ...[ if (memberInfo.value.official!['title'] != '') ...[
@ -393,6 +415,39 @@ class _MemberPageState extends State<MemberPage>
), ),
], ],
const SizedBox(height: 6), 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 ?? ''), SelectableText(memberInfo.value.sign ?? ''),
], ],
), ),

View File

@ -26,7 +26,6 @@ class SSearchController extends GetxController {
Box setting = GStrorage.setting; Box setting = GStrorage.setting;
bool enableHotKey = true; bool enableHotKey = true;
bool enableSearchSuggest = true; bool enableSearchSuggest = true;
late StreamController<bool> clearStream = StreamController<bool>.broadcast();
@override @override
void onInit() { void onInit() {
@ -42,7 +41,6 @@ class SSearchController extends GetxController {
final hint = parameters['hintText']; final hint = parameters['hintText'];
if (hint != null) { if (hint != null) {
hintText = hint; hintText = hint;
searchKeyWord.value = hintText;
} }
} }
historyCacheList = GlobalDataCache().historyCacheList; historyCacheList = GlobalDataCache().historyCacheList;
@ -55,10 +53,8 @@ class SSearchController extends GetxController {
searchKeyWord.value = value; searchKeyWord.value = value;
if (value == '') { if (value == '') {
searchSuggestList.value = []; searchSuggestList.value = [];
clearStream.add(false);
return; return;
} }
clearStream.add(true);
if (enableSearchSuggest) { if (enableSearchSuggest) {
_debouncer.call(() => querySearchSuggest(value)); _debouncer.call(() => querySearchSuggest(value));
} }
@ -68,23 +64,20 @@ class SSearchController extends GetxController {
controller.value.clear(); controller.value.clear();
searchKeyWord.value = ''; searchKeyWord.value = '';
searchSuggestList.value = []; searchSuggestList.value = [];
clearStream.add(false);
} }
// 搜索 // 搜索
void submit() { void submit() {
if (searchKeyWord.value == '') { if (searchKeyWord.value == '' && hintText.isNotEmpty && hintText == '搜索') {
return; 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); hintText = '搜索';
historyCacheList = arr; cacheHistory();
historyList.value = historyCacheList;
// 手动刷新
historyList.refresh();
localCache.put('cacheList', historyCacheList);
searchFocusNode.unfocus();
Get.toNamed('/searchResult', parameters: {'keyword': searchKeyWord.value}); Get.toNamed('/searchResult', parameters: {'keyword': searchKeyWord.value});
} }
@ -135,6 +128,18 @@ class SSearchController extends GetxController {
historyCacheList = []; historyCacheList = [];
historyList.refresh(); historyList.refresh();
localCache.put('cacheList', []); localCache.put('cacheList', []);
GlobalDataCache().historyCacheList = [];
SmartDialog.showToast('搜索历史已清空'); 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();
}
} }

View File

@ -63,24 +63,35 @@ class _SearchPageState extends State<SearchPage> with RouteAware {
focusNode: _searchController.searchFocusNode, focusNode: _searchController.searchFocusNode,
controller: _searchController.controller.value, controller: _searchController.controller.value,
textInputAction: TextInputAction.search, textInputAction: TextInputAction.search,
onChanged: (value) => _searchController.onChange(value), onChanged: _searchController.onChange,
decoration: InputDecoration( decoration: InputDecoration(
hintText: _searchController.hintText, hintText: _searchController.hintText,
border: InputBorder.none, border: InputBorder.none,
suffixIcon: StreamBuilder( suffix: Obx(() {
initialData: false, RxString searchKeyWord = _searchController.searchKeyWord;
stream: _searchController.clearStream.stream, if (searchKeyWord.value.isEmpty) {
builder: (_, snapshot) {
if (snapshot.data == true) {
return IconButton(
icon: const Icon(Icons.clear, size: 22),
onPressed: () => _searchController.onClear(),
);
} else {
return const SizedBox(); 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(),
),
],
);
}),
), ),
onSubmitted: (String value) => _searchController.submit(), onSubmitted: (String value) => _searchController.submit(),
), ),

View File

@ -1,7 +1,9 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:pilipala/http/member.dart';
import 'package:pilipala/http/search.dart'; import 'package:pilipala/http/search.dart';
import 'package:pilipala/models/common/search_type.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/id_utils.dart';
import 'package:pilipala/utils/utils.dart'; import 'package:pilipala/utils/utils.dart';
@ -31,7 +33,7 @@ class SearchPanelController extends GetxController {
tids: searchType!.type != 'video' ? null : tids.value, tids: searchType!.type != 'video' ? null : tids.value,
); );
if (result['status']) { if (result['status']) {
if (type == 'onRefresh') { if (type == 'init') {
resultList.value = result['data'].list ?? []; resultList.value = result['data'].list ?? [];
} else { } else {
resultList.addAll(result['data'].list ?? []); resultList.addAll(result['data'].list ?? []);
@ -39,12 +41,36 @@ class SearchPanelController extends GetxController {
page.value++; page.value++;
onPushDetail(keyword, resultList); 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; return result;
} }
Future onRefresh() async { Future onRefresh() async {
page.value = 1; page.value = 1;
await onSearch(type: 'onRefresh'); await onSearch();
} }
// 返回顶部并刷新 // 返回顶部并刷新

View File

@ -4,6 +4,7 @@ import 'package:easy_debounce/easy_throttle.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:pilipala/common/skeleton/media_bangumi.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/skeleton/video_card_h.dart';
import 'package:pilipala/common/widgets/http_error.dart'; import 'package:pilipala/common/widgets/http_error.dart';
import 'package:pilipala/models/common/search_type.dart'; import 'package:pilipala/models/common/search_type.dart';
@ -81,11 +82,11 @@ class _SearchPanelState extends State<SearchPanel>
future: _futureBuilderFuture, future: _futureBuilderFuture,
builder: (context, snapshot) { builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) { 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; var ctr = _searchPanelController;
RxList list = ctr.resultList; RxList list = ctr.resultList;
if (data['status']) { if (list.isNotEmpty) {
return Obx(() { return Obx(() {
switch (widget.searchType) { switch (widget.searchType) {
case SearchType.video: case SearchType.video:
@ -110,21 +111,18 @@ class _SearchPanelState extends State<SearchPanel>
}); });
} else { } else {
return HttpError( return HttpError(
errMsg: data['msg'], errMsg: '没有数据',
fn: () { isShowBtn: false,
setState(() { fn: () => {},
_searchPanelController.onSearch();
});
},
isInSliver: false, isInSliver: false,
); );
} }
} else { } else {
return HttpError( return HttpError(
errMsg: '没有相关数据', errMsg: data?['msg'] ?? '请求异常',
fn: () { fn: () {
setState(() { setState(() {
_searchPanelController.onSearch(); _futureBuilderFuture = _searchPanelController.onRefresh();
}); });
}, },
isInSliver: false, isInSliver: false,
@ -143,7 +141,7 @@ class _SearchPanelState extends State<SearchPanel>
case SearchType.media_bangumi: case SearchType.media_bangumi:
return const MediaBangumiSkeleton(); return const MediaBangumiSkeleton();
case SearchType.bili_user: case SearchType.bili_user:
return const VideoCardHSkeleton(); return const UserListSkeleton();
case SearchType.live_room: case SearchType.live_room:
return const VideoCardHSkeleton(); return const VideoCardHSkeleton();
default: default:

View File

@ -1,12 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart';
import 'package:pilipala/common/constants.dart'; import 'package:pilipala/common/constants.dart';
import 'package:pilipala/common/widgets/badge.dart'; import 'package:pilipala/common/widgets/badge.dart';
import 'package:pilipala/common/widgets/network_img_layer.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/route_push.dart';
import 'package:pilipala/utils/utils.dart'; import 'package:pilipala/utils/utils.dart';
@ -30,8 +25,8 @@ Widget searchMbangumiPanel(BuildContext context, ctr, list) {
// }); // });
}, },
child: Padding( child: Padding(
padding: const EdgeInsets.fromLTRB( padding: const EdgeInsets.symmetric(
StyleString.safeSpace, 7, StyleString.safeSpace, 7), horizontal: StyleString.safeSpace, vertical: 7),
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [

View File

@ -1,5 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:pilipala/common/constants.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart';
import 'package:pilipala/utils/utils.dart'; import 'package:pilipala/utils/utils.dart';
@ -12,15 +13,16 @@ Widget searchUserPanel(BuildContext context, ctr, list) {
controller: ctr!.scrollController, controller: ctr!.scrollController,
addAutomaticKeepAlives: false, addAutomaticKeepAlives: false,
addRepaintBoundaries: false, addRepaintBoundaries: false,
itemCount: list!.length, itemCount: list.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
var i = list![index]; var i = list[index];
String heroTag = Utils.makeHeroTag(i!.mid); String heroTag = Utils.makeHeroTag(i.mid);
return InkWell( return InkWell(
onTap: () => Get.toNamed('/member?mid=${i.mid}', onTap: () => Get.toNamed('/member?mid=${i.mid}',
arguments: {'heroTag': heroTag, 'face': i.upic}), arguments: {'heroTag': heroTag, 'face': i.upic}),
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 10), padding: const EdgeInsets.symmetric(
horizontal: StyleString.safeSpace, vertical: 7),
child: Row( child: Row(
children: [ children: [
Hero( Hero(
@ -41,7 +43,7 @@ Widget searchUserPanel(BuildContext context, ctr, list) {
Row( Row(
children: [ children: [
Text( Text(
i!.uname, i.uname!,
style: const TextStyle( style: const TextStyle(
fontSize: 14, fontSize: 14,
), ),
@ -53,15 +55,16 @@ Widget searchUserPanel(BuildContext context, ctr, list) {
), ),
], ],
), ),
if (i.fans != null && i.videos != null)
Row( Row(
children: [ children: [
Text('粉丝:${i.fans} ', style: style), Text('粉丝:${i.fans} ', style: style),
Text(' 视频:${i.videos}', style: style) Text(' 视频:${i.videos}', style: style)
], ],
), ),
if (i.officialVerify['desc'] != '') if (i.officialVerify!['desc'] != '')
Text( Text(
i.officialVerify['desc'], i.officialVerify!['desc'],
style: style, style: style,
), ),
], ],

View File

@ -3,6 +3,7 @@ import 'package:get/get.dart';
import 'package:pilipala/models/common/search_type.dart'; import 'package:pilipala/models/common/search_type.dart';
import 'package:pilipala/pages/search_panel/index.dart'; import 'package:pilipala/pages/search_panel/index.dart';
import 'controller.dart'; import 'controller.dart';
import 'widget/tab_bar.dart';
class SearchResultPage extends StatefulWidget { class SearchResultPage extends StatefulWidget {
const SearchResultPage({super.key}); const SearchResultPage({super.key});
@ -29,6 +30,17 @@ class _SearchResultPageState extends State<SearchResultPage>
); );
} }
// tab点击事件
void _onTap(int index) {
if (index == _searchResultController.tabIndex) {
Get.find<SearchPanelController>(
tag: SearchType.values[index].type +
_searchResultController.keyword!)
.animateToTop();
}
_searchResultController.tabIndex = index;
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
@ -55,50 +67,10 @@ class _SearchResultPageState extends State<SearchResultPage>
body: Column( body: Column(
children: [ children: [
const SizedBox(height: 4), const SizedBox(height: 4),
Container( TabBarWidget(
width: double.infinity, onTap: _onTap,
padding: const EdgeInsets.only(left: 8), tabController: _tabController!,
color: Theme.of(context).colorScheme.surface, searchResultCtr: _searchResultController,
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<SearchPanelController>(
tag: SearchType.values[index].type +
_searchResultController.keyword!)
.animateToTop();
}
_searchResultController.tabIndex = index;
},
)),
),
),
), ),
Expanded( Expanded(
child: TabBarView( child: TabBarView(

View File

@ -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,
),
),
),
);
}
}

View File

@ -90,12 +90,15 @@ class VideoContent extends StatelessWidget {
return Expanded( return Expanded(
child: Padding( child: Padding(
padding: const EdgeInsets.fromLTRB(10, 2, 6, 0), padding: const EdgeInsets.fromLTRB(10, 2, 6, 0),
child: Column( child: Stack(
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
subFolderItem.title!, subFolderItem.title!,
textAlign: TextAlign.start, textAlign: TextAlign.start,
maxLines: 3,
style: const TextStyle( style: const TextStyle(
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
letterSpacing: 0.3, letterSpacing: 0.3,
@ -119,12 +122,13 @@ class VideoContent extends StatelessWidget {
color: Theme.of(context).colorScheme.outline, color: Theme.of(context).colorScheme.outline,
), ),
), ),
const Spacer(), ],
),
isOwner isOwner
? Row( ? Positioned(
mainAxisAlignment: MainAxisAlignment.end, right: 0,
children: [ bottom: -4,
IconButton( child: IconButton(
style: ButtonStyle( style: ButtonStyle(
padding: MaterialStateProperty.all(EdgeInsets.zero), padding: MaterialStateProperty.all(EdgeInsets.zero),
), ),
@ -134,8 +138,7 @@ class VideoContent extends StatelessWidget {
color: Theme.of(context).colorScheme.outline, color: Theme.of(context).colorScheme.outline,
size: 18, size: 18,
), ),
) ),
],
) )
: const SizedBox() : const SizedBox()
], ],

View File

@ -338,57 +338,79 @@ class VideoIntroController extends GetxController {
return; return;
} }
final int currentStatus = followStatus['attribute']; final int currentStatus = followStatus['attribute'];
int actionStatus = 0; if (currentStatus == 128) {
switch (currentStatus) { modifyRelation('block', currentStatus);
case 0: } else {
actionStatus = 1; modifyRelation('follow', currentStatus);
break;
case 2:
actionStatus = 2;
break;
default:
actionStatus = 0;
break;
} }
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) { builder: (BuildContext context) {
final Color outline = Theme.of(Get.context!).colorScheme.outline;
return AlertDialog( return AlertDialog(
title: const Text('提示'), title: const Text('提示'),
content: Text(currentStatus == 0 ? '关注UP主?' : '取消关注UP主?'), content: Text(contentText),
actions: [ actions: [
TextButton( TextButton(
onPressed: () => SmartDialog.dismiss(), onPressed: Navigator.of(context).pop,
child: Text( child: Text('点错了', style: TextStyle(color: outline)),
'点错了',
style: TextStyle(color: Theme.of(context).colorScheme.outline),
),
), ),
TextButton( TextButton(
onPressed: () async { onPressed: () => modifyRelationFetch(
var result = await VideoHttp.relationMod( context,
mid: videoDetail.value.owner!.mid!, mid,
act: actionStatus, act,
reSrc: 14, currentStatus,
actionType,
),
child: const Text('确定'),
)
],
);
},
); );
if (result['status']) {
switch (currentStatus) {
case 0:
actionStatus = 2;
break;
case 2:
actionStatus = 0;
break;
default:
actionStatus = 0;
break;
} }
followStatus['attribute'] = actionStatus;
followStatus.refresh(); // 操作用户关系Future
if (actionStatus == 2) { Future modifyRelationFetch(
BuildContext context,
mid,
act,
currentStatus,
actionType,
) async {
var res = await VideoHttp.relationMod(mid: mid, act: act, reSrc: 11);
if (context.mounted) { if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar( Navigator.of(context).pop();
}
if (res['status']) {
if (actionType == 'follow') {
final Map<int, int> 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( SnackBar(
content: const Text('关注成功'), content: const Text('关注成功'),
duration: const Duration(seconds: 2), duration: const Duration(seconds: 2),
@ -399,17 +421,17 @@ class VideoIntroController extends GetxController {
showCloseIcon: true, showCloseIcon: true,
), ),
); );
} else {
SmartDialog.showToast('取消关注成功');
} }
} else if (actionType == 'block') {
followStatus['attribute'] = 0;
SmartDialog.showToast('取消拉黑成功');
} }
followStatus.refresh();
} else {
SmartDialog.showToast(res['msg']);
} }
SmartDialog.dismiss();
},
child: const Text('确认'),
)
],
);
},
);
} }
// 修改分P或番剧分集 // 修改分P或番剧分集

View File

@ -470,8 +470,8 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
const Spacer(), const Spacer(),
Obx( Obx(
() { () {
final bool isFollowed = final int attr =
videoIntroController.followStatus['attribute'] != 0; videoIntroController.followStatus['attribute'] ?? 0;
return videoIntroController.followStatus.isEmpty return videoIntroController.followStatus.isEmpty
? const SizedBox() ? const SizedBox()
: SizedBox( : SizedBox(
@ -484,15 +484,19 @@ class _VideoInfoState extends State<VideoInfo> with TickerProviderStateMixin {
left: 8, left: 8,
right: 8, right: 8,
), ),
foregroundColor: isFollowed foregroundColor: attr != 0
? outline ? outline
: t.colorScheme.onPrimary, : t.colorScheme.onPrimary,
backgroundColor: isFollowed backgroundColor: attr != 0
? t.colorScheme.onInverseSurface ? t.colorScheme.onInverseSurface
: t.colorScheme.primary, // 设置按钮背景色 : t.colorScheme.primary, // 设置按钮背景色
), ),
child: Text( child: Text(
isFollowed ? '已关注' : '关注', attr == 128
? '已拉黑'
: attr != 0
? '已关注'
: '关注',
style: TextStyle( style: TextStyle(
fontSize: fontSize:
t.textTheme.labelMedium!.fontSize, t.textTheme.labelMedium!.fontSize,

View File

@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:pilipala/http/constants.dart';
import 'package:pilipala/utils/feed_back.dart'; import 'package:pilipala/utils/feed_back.dart';
import 'package:pilipala/utils/utils.dart'; import 'package:pilipala/utils/utils.dart';
@ -15,6 +16,10 @@ class IntroDetail extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
TextStyle textStyle = TextStyle(
fontSize: 14,
color: Theme.of(context).colorScheme.primary,
);
return SizedBox( return SizedBox(
width: double.infinity, width: double.infinity,
child: Column( child: Column(
@ -29,12 +34,7 @@ class IntroDetail extends StatelessWidget {
Clipboard.setData(ClipboardData(text: videoDetail!.bvid!)); Clipboard.setData(ClipboardData(text: videoDetail!.bvid!));
SmartDialog.showToast('已复制'); SmartDialog.showToast('已复制');
}, },
child: Text( child: Text(videoDetail!.bvid!, style: textStyle),
videoDetail!.bvid!,
style: TextStyle(
fontSize: 13,
color: Theme.of(context).colorScheme.primary),
),
), ),
const SizedBox(width: 10), const SizedBox(width: 10),
GestureDetector( GestureDetector(
@ -44,12 +44,18 @@ class IntroDetail extends StatelessWidget {
ClipboardData(text: videoDetail!.aid!.toString())); ClipboardData(text: videoDetail!.aid!.toString()));
SmartDialog.showToast('已复制'); SmartDialog.showToast('已复制');
}, },
child: Text( child: Text(videoDetail!.aid!.toString(), style: textStyle),
videoDetail!.aid!.toString(),
style: TextStyle(
fontSize: 13,
color: Theme.of(context).colorScheme.primary),
), ),
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),
) )
], ],
), ),

View File

@ -212,9 +212,9 @@ class PiliSchame {
} }
} }
static Future<void> biliScheme(SchemeEntity value) async { static Future<void> biliScheme(Uri value) async {
final String host = value.host!; final String host = value.host;
final String path = value.path!; final String path = value.path;
switch (host) { switch (host) {
case 'root': case 'root':
Navigator.popUntil( Navigator.popUntil(
@ -301,7 +301,7 @@ class PiliSchame {
break; break;
default: default:
SmartDialog.showToast('未匹配地址,请联系开发者'); SmartDialog.showToast('未匹配地址,请联系开发者');
Clipboard.setData(ClipboardData(text: value.toJson().toString())); Clipboard.setData(ClipboardData(text: value.toString()));
break; break;
} }
} }