feat: 搜索直播间、用户

This commit is contained in:
guozhigq
2023-06-20 22:52:47 +08:00
parent 7e7892aab2
commit c2f8f143f8
15 changed files with 801 additions and 59 deletions

View File

@ -0,0 +1,48 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:pilipala/http/search.dart';
import 'package:pilipala/models/common/search_type.dart';
class SearchPanelController extends GetxController {
SearchPanelController({this.keyword, this.searchType});
ScrollController scrollController = ScrollController();
String? keyword;
SearchType? searchType;
RxInt page = 1.obs;
RxList resultList = [].obs;
@override
void onInit() {
super.onInit();
}
Future onSearch({type = 'init'}) async {
var result = await SearchHttp.searchByType(
searchType: searchType!, keyword: keyword!, page: page.value);
if (result['status']) {
if (type == 'init') {
page.value++;
resultList.addAll(result['data'].list);
} else {
resultList.value = result['data'].list;
}
}
return result;
}
Future onRefresh() async {
page.value = 1;
onSearch(type: 'refresh');
}
// 返回顶部并刷新
void animateToTop() async {
if (scrollController.offset >=
MediaQuery.of(Get.context!).size.height * 5) {
scrollController.jumpTo(0);
} else {
await scrollController.animateTo(0,
duration: const Duration(milliseconds: 500), curve: Curves.easeInOut);
}
}
}

View File

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

View File

@ -0,0 +1,94 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:pilipala/common/skeleton/video_card_h.dart';
import 'package:pilipala/common/widgets/http_error.dart';
import 'package:pilipala/common/widgets/live_card.dart';
import 'package:pilipala/common/widgets/video_card_h.dart';
import 'package:pilipala/models/common/search_type.dart';
import 'controller.dart';
import 'widgets/userPanel.dart';
class SearchPanel extends StatefulWidget {
String? keyword;
SearchType? searchType;
SearchPanel({required this.keyword, required this.searchType, Key? key})
: super(key: key);
@override
State<SearchPanel> createState() => _SearchPanelState();
}
class _SearchPanelState extends State<SearchPanel>
with AutomaticKeepAliveClientMixin {
late SearchPanelController? _searchPanelController;
@override
bool get wantKeepAlive => true;
@override
void initState() {
super.initState();
_searchPanelController = Get.put(
SearchPanelController(
keyword: widget.keyword,
searchType: widget.searchType,
),
tag: widget.searchType!.type);
}
@override
Widget build(BuildContext context) {
return RefreshIndicator(
onRefresh: () async {
await _searchPanelController!.onRefresh();
},
child: FutureBuilder(
future: _searchPanelController!.onSearch(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
Map data = snapshot.data;
if (data['status']) {
return Obx(
() => ListView.builder(
controller: _searchPanelController!.scrollController,
addAutomaticKeepAlives: false,
addRepaintBoundaries: false,
itemCount: _searchPanelController!.resultList.length,
itemBuilder: (context, index) {
var i = _searchPanelController!.resultList[index];
switch (widget.searchType) {
case SearchType.video:
return VideoCardH(videoItem: i);
case SearchType.bili_user:
return UserPanel(userItem: i);
case SearchType.live_room:
return LiveCard(liveItem: i);
default:
return const SizedBox();
}
},
),
);
} else {
return HttpError(
errMsg: data['msg'],
fn: () => setState(() {}),
);
}
} else {
// 骨架屏
return ListView.builder(
addAutomaticKeepAlives: false,
addRepaintBoundaries: false,
itemCount: 15,
itemBuilder: (context, index) {
return const VideoCardHSkeleton();
},
);
}
},
),
);
}
}

View File

@ -0,0 +1,63 @@
import 'package:flutter/material.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart';
class UserPanel extends StatelessWidget {
var userItem;
UserPanel({super.key, this.userItem});
@override
Widget build(BuildContext context) {
return InkWell(
onTap: () {},
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 15, vertical: 10),
child: Row(
children: [
NetworkImgLayer(
width: 42,
height: 42,
src: userItem.upic,
type: 'avatar',
),
const SizedBox(width: 10),
Column(
mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Row(
children: [
Text(
userItem!.uname,
style: TextStyle(
// color: replyItem!.isUp! ||
// replyItem!.member!.vip!['vipType'] > 0
// ? Theme.of(context).colorScheme.primary
// : Theme.of(context).colorScheme.outline,
fontSize:
Theme.of(context).textTheme.titleMedium!.fontSize,
),
),
const SizedBox(width: 6),
Image.asset(
'assets/images/lv/lv${userItem!.level}.png',
height: 11,
),
],
),
if (userItem.officialVerify['desc'] != '')
Text(
userItem.officialVerify['desc'],
style: TextStyle(
fontSize:
Theme.of(context).textTheme.labelSmall!.fontSize,
color: Theme.of(context).colorScheme.outline),
),
],
)
],
),
),
);
}
}

View File

@ -1,15 +1,10 @@
import 'package:get/get.dart';
import 'package:pilipala/models/common/search_type.dart';
import 'package:pilipala/pages/searchPanel/index.dart';
class SearchResultController extends GetxController {
String? keyword;
List tabs = [
{'label': '综合', 'id': ''},
{'label': '视频', 'id': ''},
{'label': '番剧', 'id': ''},
{'label': '直播', 'id': ''},
{'label': '专栏', 'id': ''},
{'label': '用户', 'id': ''}
];
int tabIndex = 0;
@override
void onInit() {

View File

@ -1,5 +1,7 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:pilipala/models/common/search_type.dart';
import 'package:pilipala/pages/searchPanel/index.dart';
import 'controller.dart';
class SearchResultPage extends StatefulWidget {
@ -9,14 +11,32 @@ class SearchResultPage extends StatefulWidget {
State<SearchResultPage> createState() => _SearchResultPageState();
}
class _SearchResultPageState extends State<SearchResultPage> {
class _SearchResultPageState extends State<SearchResultPage>
with TickerProviderStateMixin {
final SearchResultController _searchResultController =
Get.put(SearchResultController());
late TabController? _tabController;
@override
void initState() {
super.initState();
_tabController = TabController(
vsync: this,
length: SearchType.values.length,
initialIndex: _searchResultController.tabIndex,
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
shape: Border(
bottom: BorderSide(
color: Theme.of(context).dividerColor.withOpacity(0.08),
width: 1,
),
),
titleSpacing: 0,
centerTitle: false,
title: GestureDetector(
@ -30,57 +50,58 @@ class _SearchResultPageState extends State<SearchResultPage> {
),
),
),
body: DefaultTabController(
length: _searchResultController.tabs.length,
child: Column(
children: [
Theme(
data: ThemeData(
splashColor: Colors.transparent, // 点击时的水波纹颜色设置为透明
highlightColor: Colors.transparent, // 点击时的背景高亮颜色设置为透明
),
child: TabBar(
tabs: _searchResultController.tabs
.map((e) => Tab(text: e['label']))
.toList(),
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(16),
),
body: Column(
children: [
Theme(
data: ThemeData(
splashColor: Colors.transparent, // 点击时的水波纹颜色设置为透明
highlightColor: Colors.transparent, // 点击时的背景高亮颜色设置为透明
),
child: TabBar(
controller: _tabController,
tabs: [
for (var i in SearchType.values) Tab(text: i.label),
],
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(16),
),
indicatorSize: TabBarIndicatorSize.tab,
labelColor: Theme.of(context).colorScheme.onSecondaryContainer,
labelStyle: const TextStyle(fontSize: 13),
dividerColor: Colors.transparent,
unselectedLabelColor: Theme.of(context).colorScheme.outline,
onTap: (index) {
print(index);
},
),
indicatorSize: TabBarIndicatorSize.tab,
labelColor: Theme.of(context).colorScheme.onSecondaryContainer,
labelStyle: const TextStyle(fontSize: 13),
dividerColor: Colors.transparent,
unselectedLabelColor: Theme.of(context).colorScheme.outline,
onTap: (index) {
if (index == _searchResultController.tabIndex) {
Get.find<SearchPanelController>(
tag: SearchType.values[index].type)
.animateToTop();
}
_searchResultController.tabIndex = index;
},
),
Expanded(
child: TabBarView(
children: [
Container(
width: 200,
height: 200,
color: Colors.amber,
),
Text('1'),
Text('1'),
Text('1'),
Text('1'),
Text('1'),
],
),
),
const SizedBox(height: 4),
Expanded(
child: TabBarView(
controller: _tabController,
children: [
for (var i in SearchType.values) ...{
SearchPanel(
keyword: _searchResultController.keyword,
searchType: i,
)
}
],
),
],
),
),
],
),
);
}

10
lib/pages/video/README.md Normal file
View File

@ -0,0 +1,10 @@
视频详情页预渲染
+ videoItem
+ title
+ stat
+ view
+ danmaku
+ pubdate
+ owner
+ face
+ name