Compare commits

...

4 Commits

Author SHA1 Message Date
d0f036ec35 fix: 评论回复多张图片拉伸 2024-02-09 09:32:28 +08:00
94f3b7c1e4 fix: minePage 路由跳转 2024-02-08 21:33:02 +08:00
fb8b2de115 feat: up搜索 2024-02-08 21:27:22 +08:00
0d5d33a365 feat: up投稿排序 2024-02-08 10:29:26 +08:00
12 changed files with 317 additions and 48 deletions

View File

@ -214,6 +214,9 @@ class Api {
// https://api.bilibili.com/x/relation/tags // https://api.bilibili.com/x/relation/tags
static const String followingsClass = '/x/relation/tags'; static const String followingsClass = '/x/relation/tags';
// 搜索follow
static const followSearch = '/x/relation/followings/search';
// 粉丝 // 粉丝
// vmid 用户id pn 页码 ps 每页个数最大50 order: desc // vmid 用户id pn 页码 ps 每页个数最大50 order: desc
// order_type 排序规则 最近访问传空,最常访问传 attention // order_type 排序规则 最近访问传空,最常访问传 attention

View File

@ -461,4 +461,41 @@ class MemberHttp {
}; };
} }
} }
// 搜索follow
static Future getfollowSearch({
required int mid,
required int ps,
required int pn,
required String name,
}) async {
Map<String, dynamic> data = {
'vmid': mid,
'pn': pn,
'ps': ps,
'order': 'desc',
'order_type': 'attention',
'gaia_source': 'main_web',
'name': name,
'web_location': 333.999,
};
Map params = await WbiSign().makSign(data);
var res = await Request().get(Api.followSearch, data: {
...data,
'w_rid': params['w_rid'],
'wts': params['wts'],
});
if (res.data['code'] == 0) {
return {
'status': true,
'data': FollowDataModel.fromJson(res.data['data'])
};
} else {
return {
'status': false,
'data': [],
'msg': res.data['message'],
};
}
}
} }

View File

@ -37,6 +37,29 @@ class _FollowPageState extends State<FollowPage> {
: '${_followController.name}的关注', : '${_followController.name}的关注',
style: Theme.of(context).textTheme.titleMedium, style: Theme.of(context).textTheme.titleMedium,
), ),
actions: [
IconButton(
onPressed: () => Get.toNamed('/followSearch?mid=$mid'),
icon: const Icon(Icons.search_outlined),
),
PopupMenuButton(
icon: const Icon(Icons.more_vert),
itemBuilder: (BuildContext context) => <PopupMenuEntry>[
PopupMenuItem(
onTap: () => Get.toNamed('/blackListPage'),
child: const Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(Icons.block, size: 19),
SizedBox(width: 10),
Text('黑名单管理'),
],
),
)
],
),
const SizedBox(width: 6),
],
), ),
body: Obx( body: Obx(
() => !_followController.isOwner.value () => !_followController.isOwner.value
@ -87,3 +110,22 @@ class _FollowPageState extends State<FollowPage> {
); );
} }
} }
class _FakeAPI {
static const List<String> _kOptions = <String>[
'aardvark',
'bobcat',
'chameleon',
];
// Searches the options, but injects a fake "network" delay.
static Future<Iterable<String>> search(String query) async {
await Future<void>.delayed(
const Duration(seconds: 1)); // Fake 1 second delay.
if (query == '') {
return const Iterable<String>.empty();
}
return _kOptions.where((String option) {
return option.contains(query.toLowerCase());
});
}
}

View File

@ -42,7 +42,7 @@ class FollowItem extends StatelessWidget {
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
dense: true, dense: true,
trailing: ctr!.isOwner.value trailing: ctr != null && ctr!.isOwner.value
? SizedBox( ? SizedBox(
height: 34, height: 34,
child: TextButton( child: TextButton(

View File

@ -0,0 +1,73 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:pilipala/http/member.dart';
import '../../models/follow/result.dart';
class FollowSearchController extends GetxController {
Rx<TextEditingController> controller = TextEditingController().obs;
final FocusNode searchFocusNode = FocusNode();
RxString searchKeyWord = ''.obs;
String hintText = '搜索';
RxString loadingStatus = 'init'.obs;
late int mid = 1;
RxString uname = ''.obs;
int ps = 20;
int pn = 1;
RxList<FollowItemModel> followList = <FollowItemModel>[].obs;
RxInt total = 0.obs;
@override
void onInit() {
super.onInit();
mid = int.parse(Get.parameters['mid']!);
}
// 清空搜索
void onClear() {
if (searchKeyWord.value.isNotEmpty && controller.value.text != '') {
controller.value.clear();
searchKeyWord.value = '';
} else {
Get.back();
}
}
void onChange(value) {
searchKeyWord.value = value;
}
// 提交搜索内容
void submit() {
loadingStatus.value = 'loading';
searchFollow();
}
Future searchFollow({type = 'init'}) async {
if (controller.value.text == '') {
return {'status': true, 'data': <FollowItemModel>[].obs};
}
if (type == 'init') {
ps = 1;
}
var res = await MemberHttp.getfollowSearch(
mid: mid,
ps: ps,
pn: pn,
name: controller.value.text,
);
if (res['status']) {
if (type == 'init') {
followList.value = res['data'].list;
} else {
followList.addAll(res['data'].list);
}
total.value = res['data'].total;
}
return res;
}
void onLoad() {
searchFollow(type: 'onLoad');
}
}

View File

@ -0,0 +1,4 @@
library follow_search;
export './controller.dart';
export './view.dart';

View File

@ -0,0 +1,121 @@
import 'package:easy_debounce/easy_throttle.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:pilipala/common/widgets/http_error.dart';
import 'package:pilipala/pages/follow_search/index.dart';
import '../follow/widgets/follow_item.dart';
class FollowSearchPage extends StatefulWidget {
const FollowSearchPage({super.key});
@override
State<FollowSearchPage> createState() => _FollowSearchPageState();
}
class _FollowSearchPageState extends State<FollowSearchPage> {
final FollowSearchController _followSearchController =
Get.put(FollowSearchController());
late Future? _futureBuilder;
final ScrollController scrollController = ScrollController();
@override
void initState() {
super.initState();
_futureBuilder = _followSearchController.searchFollow();
scrollController.addListener(
() {
if (scrollController.position.pixels >=
scrollController.position.maxScrollExtent - 200) {
EasyThrottle.throttle(
'my-throttler', const Duration(milliseconds: 500), () {
_followSearchController.onLoad();
});
}
},
);
}
void reRequest() {
setState(() {
_futureBuilder = _followSearchController.searchFollow();
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
titleSpacing: 0,
actions: [
IconButton(
onPressed: reRequest,
icon: const Icon(CupertinoIcons.search, size: 22),
),
const SizedBox(width: 6),
],
title: TextField(
autofocus: true,
focusNode: _followSearchController.searchFocusNode,
controller: _followSearchController.controller.value,
textInputAction: TextInputAction.search,
onChanged: (value) => _followSearchController.onChange(value),
decoration: InputDecoration(
hintText: _followSearchController.hintText,
border: InputBorder.none,
suffixIcon: IconButton(
icon: Icon(
Icons.clear,
size: 22,
color: Theme.of(context).colorScheme.outline,
),
onPressed: () => _followSearchController.onClear(),
),
),
onSubmitted: (String value) => reRequest(),
),
),
body: FutureBuilder(
future: _futureBuilder,
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
var data = snapshot.data;
if (data == null) {
return CustomScrollView(
slivers: [
HttpError(errMsg: snapshot.data['msg'], fn: reRequest)
],
);
}
if (data['status']) {
RxList followList = _followSearchController.followList;
return Obx(
() => followList.isNotEmpty
? ListView.builder(
controller: scrollController,
itemCount: followList.length,
itemBuilder: ((context, index) {
return FollowItem(
item: followList[index],
);
}),
)
: CustomScrollView(
slivers: [HttpError(errMsg: '未搜索到结果', fn: reRequest)],
),
);
} else {
return CustomScrollView(
slivers: [
HttpError(errMsg: snapshot.data['msg'], fn: reRequest)
],
);
}
} else {
return const SizedBox();
}
}),
);
}
}

View File

@ -25,7 +25,7 @@ class MemberArchiveController extends GetxController {
// 获取用户投稿 // 获取用户投稿
Future getMemberArchive(type) async { Future getMemberArchive(type) async {
if (type == 'onRefresh') { if (type == 'init') {
pn = 1; pn = 1;
} }
var res = await MemberHttp.memberArchive( var res = await MemberHttp.memberArchive(
@ -34,7 +34,12 @@ class MemberArchiveController extends GetxController {
order: currentOrder['type']!, order: currentOrder['type']!,
); );
if (res['status']) { if (res['status']) {
if (type == 'init') {
archivesList.value = res['data'].list.vlist;
}
if (type == 'onLoad') {
archivesList.addAll(res['data'].list.vlist); archivesList.addAll(res['data'].list.vlist);
}
count = res['data'].page['count']; count = res['data'].page['count'];
pn += 1; pn += 1;
} }
@ -42,13 +47,14 @@ class MemberArchiveController extends GetxController {
} }
toggleSort() async { toggleSort() async {
pn = 1; List<String> typeList = orderList.map((e) => e['type']!).toList();
int index = orderList.indexOf(currentOrder); int index = typeList.indexOf(currentOrder['type']!);
if (index == orderList.length - 1) { if (index == orderList.length - 1) {
currentOrder.value = orderList.first; currentOrder.value = orderList.first;
} else { } else {
currentOrder.value = orderList[index + 1]; currentOrder.value = orderList[index + 1];
} }
getMemberArchive('init');
} }
// 上拉加载 // 上拉加载

View File

@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:pilipala/common/widgets/video_card_h.dart'; import 'package:pilipala/common/widgets/video_card_h.dart';
import 'package:pilipala/utils/utils.dart'; import 'package:pilipala/utils/utils.dart';
import '../../common/constants.dart';
import 'controller.dart'; import 'controller.dart';
class MemberArchivePage extends StatefulWidget { class MemberArchivePage extends StatefulWidget {
@ -48,39 +49,16 @@ class _MemberArchivePageState extends State<MemberArchivePage> {
titleSpacing: 0, titleSpacing: 0,
centerTitle: false, centerTitle: false,
title: Text('他的投稿', style: Theme.of(context).textTheme.titleMedium), title: Text('他的投稿', style: Theme.of(context).textTheme.titleMedium),
// actions: [ actions: [
// Obx( Obx(
// () => PopupMenuButton<String>( () => TextButton.icon(
// padding: EdgeInsets.zero, icon: const Icon(Icons.sort, size: 20),
// tooltip: '投稿排序', onPressed: _memberArchivesController.toggleSort,
// icon: Icon( label: Text(_memberArchivesController.currentOrder['label']!),
// Icons.more_vert_outlined, ),
// color: Theme.of(context).colorScheme.outline, ),
// ), const SizedBox(width: 6),
// position: PopupMenuPosition.under, ],
// onSelected: (String type) {},
// itemBuilder: (BuildContext context) => <PopupMenuEntry<String>>[
// for (var i in _memberArchivesController.orderList) ...[
// PopupMenuItem<String>(
// onTap: () {},
// value: _memberArchivesController.currentOrder['label'],
// child: Row(
// mainAxisSize: MainAxisSize.min,
// children: [
// Text(i['label']!),
// if (_memberArchivesController.currentOrder['label'] ==
// i['label']) ...[
// const SizedBox(width: 10),
// const Icon(Icons.done, size: 20),
// ],
// ],
// ),
// ),
// ]
// ],
// ),
// ),
// ],
), ),
body: CustomScrollView( body: CustomScrollView(
controller: _memberArchivesController.scrollController, controller: _memberArchivesController.scrollController,

View File

@ -119,7 +119,7 @@ class MineController extends GetxController {
SmartDialog.showToast('账号未登录'); SmartDialog.showToast('账号未登录');
return; return;
} }
Get.toNamed('/follow?mid=${userInfo.value.mid}'); Get.toNamed('/follow?mid=${userInfo.value.mid}', preventDuplicates: false);
} }
pushFans() { pushFans() {
@ -127,7 +127,7 @@ class MineController extends GetxController {
SmartDialog.showToast('账号未登录'); SmartDialog.showToast('账号未登录');
return; return;
} }
Get.toNamed('/fan?mid=${userInfo.value.mid}'); Get.toNamed('/fan?mid=${userInfo.value.mid}', preventDuplicates: false);
} }
pushDynamic() { pushDynamic() {
@ -135,6 +135,7 @@ class MineController extends GetxController {
SmartDialog.showToast('账号未登录'); SmartDialog.showToast('账号未登录');
return; return;
} }
Get.toNamed('/memberDynamics?mid=${userInfo.value.mid}'); Get.toNamed('/memberDynamics?mid=${userInfo.value.mid}',
preventDuplicates: false);
} }
} }

View File

@ -797,8 +797,7 @@ InlineSpan buildContent(
), ),
), ),
); );
} } else if (len > 1) {
if (len > 1) {
List<Widget> list = []; List<Widget> list = [];
for (var i = 0; i < len; i++) { for (var i = 0; i < len; i++) {
picList.add(content.pictures[i]['img_src']); picList.add(content.pictures[i]['img_src']);
@ -819,7 +818,8 @@ InlineSpan buildContent(
src: content.pictures[i]['img_src'], src: content.pictures[i]['img_src'],
width: box.maxWidth, width: box.maxWidth,
height: box.maxWidth, height: box.maxWidth,
), origAspectRatio: content.pictures[i]['img_width'] /
content.pictures[i]['img_height']),
); );
}, },
), ),

View File

@ -3,6 +3,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:pilipala/pages/follow_search/view.dart';
import 'package:pilipala/pages/setting/pages/logs.dart'; import 'package:pilipala/pages/setting/pages/logs.dart';
import '../pages/about/index.dart'; import '../pages/about/index.dart';
@ -104,7 +105,8 @@ class Routes {
CustomGetPage( CustomGetPage(
name: '/replyReply', page: () => const VideoReplyReplyPanel()), name: '/replyReply', page: () => const VideoReplyReplyPanel()),
// 推荐设置 // 推荐设置
CustomGetPage(name: '/recommendSetting', page: () => const RecommendSetting()), CustomGetPage(
name: '/recommendSetting', page: () => const RecommendSetting()),
// 播放设置 // 播放设置
CustomGetPage(name: '/playSetting', page: () => const PlaySetting()), CustomGetPage(name: '/playSetting', page: () => const PlaySetting()),
// 外观设置 // 外观设置
@ -156,6 +158,8 @@ class Routes {
name: '/memberSeasons', page: () => const MemberSeasonsPage()), name: '/memberSeasons', page: () => const MemberSeasonsPage()),
// 日志 // 日志
CustomGetPage(name: '/logs', page: () => const LogsPage()), CustomGetPage(name: '/logs', page: () => const LogsPage()),
// 搜索关注
CustomGetPage(name: '/followSearch', page: () => const FollowSearchPage()),
]; ];
} }