Merge branch 'design'
This commit is contained in:
@ -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<MediaBangumiSkeleton> createState() => _MediaBangumiSkeletonState();
|
||||
}
|
||||
|
||||
class _MediaBangumiSkeletonState extends State<MediaBangumiSkeleton> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
Color bgColor = Theme.of(context).colorScheme.onInverseSurface;
|
||||
@ -35,25 +30,25 @@ class _MediaBangumiSkeletonState extends State<MediaBangumiSkeleton> {
|
||||
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<MediaBangumiSkeleton> {
|
||||
decoration: BoxDecoration(
|
||||
borderRadius:
|
||||
const BorderRadius.all(Radius.circular(20)),
|
||||
color: Theme.of(context).colorScheme.onInverseSurface,
|
||||
color: bgColor,
|
||||
),
|
||||
),
|
||||
],
|
||||
|
||||
42
lib/common/skeleton/user_list.dart
Normal file
42
lib/common/skeleton/user_list.dart
Normal 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,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
));
|
||||
}
|
||||
}
|
||||
@ -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 =
|
||||
|
||||
@ -87,7 +87,9 @@ class _BlackListPageState extends State<BlackListPage> {
|
||||
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,
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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<MemberPage>
|
||||
bottom: MediaQuery.of(context).padding.bottom + 20,
|
||||
),
|
||||
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(),
|
||||
|
||||
/// 动态链接
|
||||
@ -318,6 +339,7 @@ class _MemberPageState extends State<MemberPage>
|
||||
Rx<MemberInfoModel> memberInfo = _memberController.memberInfo;
|
||||
return Obx(
|
||||
() => Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
ProfilePanel(ctr: _memberController),
|
||||
@ -376,7 +398,7 @@ class _MemberPageState extends State<MemberPage>
|
||||
.value.vip!.label!['img_label_uri_hans_static'],
|
||||
height: 20,
|
||||
),
|
||||
]
|
||||
],
|
||||
],
|
||||
),
|
||||
if (memberInfo.value.official!['title'] != '') ...[
|
||||
@ -393,6 +415,39 @@ class _MemberPageState extends State<MemberPage>
|
||||
),
|
||||
],
|
||||
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 ?? ''),
|
||||
],
|
||||
),
|
||||
|
||||
@ -26,7 +26,6 @@ class SSearchController extends GetxController {
|
||||
Box setting = GStrorage.setting;
|
||||
bool enableHotKey = true;
|
||||
bool enableSearchSuggest = true;
|
||||
late StreamController<bool> clearStream = StreamController<bool>.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();
|
||||
}
|
||||
}
|
||||
|
||||
@ -63,24 +63,35 @@ class _SearchPageState extends State<SearchPage> 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(),
|
||||
),
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
// 返回顶部并刷新
|
||||
|
||||
@ -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<SearchPanel>
|
||||
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<SearchPanel>
|
||||
});
|
||||
} 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<SearchPanel>
|
||||
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:
|
||||
|
||||
@ -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: [
|
||||
|
||||
@ -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,
|
||||
),
|
||||
],
|
||||
|
||||
@ -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<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
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
@ -55,50 +67,10 @@ class _SearchResultPageState extends State<SearchResultPage>
|
||||
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<SearchPanelController>(
|
||||
tag: SearchType.values[index].type +
|
||||
_searchResultController.keyword!)
|
||||
.animateToTop();
|
||||
}
|
||||
|
||||
_searchResultController.tabIndex = index;
|
||||
},
|
||||
)),
|
||||
),
|
||||
),
|
||||
TabBarWidget(
|
||||
onTap: _onTap,
|
||||
tabController: _tabController!,
|
||||
searchResultCtr: _searchResultController,
|
||||
),
|
||||
Expanded(
|
||||
child: TabBarView(
|
||||
|
||||
53
lib/pages/search_result/widget/tab_bar.dart
Normal file
53
lib/pages/search_result/widget/tab_bar.dart
Normal 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,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -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()
|
||||
],
|
||||
|
||||
@ -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<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(
|
||||
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,
|
||||
|
||||
@ -470,8 +470,8 @@ class _VideoInfoState extends State<VideoInfo> 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<VideoInfo> 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,
|
||||
|
||||
@ -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),
|
||||
)
|
||||
],
|
||||
),
|
||||
|
||||
@ -212,9 +212,9 @@ class PiliSchame {
|
||||
}
|
||||
}
|
||||
|
||||
static Future<void> biliScheme(SchemeEntity value) async {
|
||||
final String host = value.host!;
|
||||
final String path = value.path!;
|
||||
static Future<void> 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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user