feat: 搜索直播间、用户
This commit is contained in:
48
lib/pages/searchPanel/controller.dart
Normal file
48
lib/pages/searchPanel/controller.dart
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
4
lib/pages/searchPanel/index.dart
Normal file
4
lib/pages/searchPanel/index.dart
Normal file
@ -0,0 +1,4 @@
|
||||
library searchpanel;
|
||||
|
||||
export './controller.dart';
|
||||
export './view.dart';
|
||||
94
lib/pages/searchPanel/view.dart
Normal file
94
lib/pages/searchPanel/view.dart
Normal 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();
|
||||
},
|
||||
);
|
||||
}
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
63
lib/pages/searchPanel/widgets/userPanel.dart
Normal file
63
lib/pages/searchPanel/widgets/userPanel.dart
Normal 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),
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -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() {
|
||||
|
||||
@ -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
10
lib/pages/video/README.md
Normal file
@ -0,0 +1,10 @@
|
||||
视频详情页预渲染
|
||||
+ videoItem
|
||||
+ title
|
||||
+ stat
|
||||
+ view
|
||||
+ danmaku
|
||||
+ pubdate
|
||||
+ owner
|
||||
+ face
|
||||
+ name
|
||||
Reference in New Issue
Block a user