From acfe556c260b54a5e97f7b10a395c32cb984a4a7 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 6 Aug 2023 21:05:51 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=A1=B6=E6=A0=8F=E6=94=B6=E8=B5=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/http/api.dart | 4 + lib/pages/bangumi/controller.dart | 3 + lib/pages/bangumi/index.dart | 4 + lib/pages/bangumi/view.dart | 19 ++ lib/pages/home/controller.dart | 20 ++ lib/pages/home/view.dart | 312 +++++++++++++++++++++--------- lib/pages/main/controller.dart | 3 +- lib/pages/main/view.dart | 2 - lib/pages/search/controller.dart | 9 +- lib/pages/search/view.dart | 37 ++-- 10 files changed, 303 insertions(+), 110 deletions(-) create mode 100644 lib/pages/bangumi/controller.dart create mode 100644 lib/pages/bangumi/index.dart create mode 100644 lib/pages/bangumi/view.dart diff --git a/lib/http/api.dart b/lib/http/api.dart index f6168bf0..af77fe97 100644 --- a/lib/http/api.dart +++ b/lib/http/api.dart @@ -167,6 +167,10 @@ class Api { // 热搜 static const String hotSearchList = 'https://s.search.bilibili.com/main/hotword'; + + // 默认搜索词 + static const String searchDefault = '/x/web-interface/wbi/search/default'; + // 搜索关键词 static const String serachSuggest = 'https://s.search.bilibili.com/main/suggest'; diff --git a/lib/pages/bangumi/controller.dart b/lib/pages/bangumi/controller.dart new file mode 100644 index 00000000..cb02a3f7 --- /dev/null +++ b/lib/pages/bangumi/controller.dart @@ -0,0 +1,3 @@ +import 'package:get/get.dart'; + +class BangumiController extends GetxController {} diff --git a/lib/pages/bangumi/index.dart b/lib/pages/bangumi/index.dart new file mode 100644 index 00000000..0b514b74 --- /dev/null +++ b/lib/pages/bangumi/index.dart @@ -0,0 +1,4 @@ +library bangumi_panel; + +export './controller.dart'; +export './view.dart'; diff --git a/lib/pages/bangumi/view.dart b/lib/pages/bangumi/view.dart new file mode 100644 index 00000000..cb1b1ddb --- /dev/null +++ b/lib/pages/bangumi/view.dart @@ -0,0 +1,19 @@ +import 'package:flutter/material.dart'; + +class BangumiPage extends StatefulWidget { + const BangumiPage({super.key}); + + @override + State createState() => _BangumiPageState(); +} + +class _BangumiPageState extends State { + @override + Widget build(BuildContext context) { + return const Scaffold( + body: Center( + child: Text('还在开发中'), + ), + ); + } +} diff --git a/lib/pages/home/controller.dart b/lib/pages/home/controller.dart index e154fc0d..fe185738 100644 --- a/lib/pages/home/controller.dart +++ b/lib/pages/home/controller.dart @@ -1,5 +1,7 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:pilipala/http/index.dart'; +import 'package:pilipala/pages/bangumi/index.dart'; import 'package:pilipala/pages/hot/index.dart'; import 'package:pilipala/pages/live/index.dart'; import 'package:pilipala/pages/rcmd/index.dart'; @@ -31,6 +33,14 @@ class HomeController extends GetxController with GetTickerProviderStateMixin { 'label': '热门', 'type': 'hot' }, + { + 'icon': const Icon( + Icons.play_circle_outlined, + size: 15, + ), + 'label': '番剧', + 'type': 'bangumi' + }, ]; int initialIndex = 1; late TabController tabController; @@ -38,7 +48,9 @@ class HomeController extends GetxController with GetTickerProviderStateMixin { Get.find, Get.find, Get.find, + Get.find, ]; + RxString defaultSearch = '输入关键词搜索'.obs; @override void onInit() { @@ -48,6 +60,7 @@ class HomeController extends GetxController with GetTickerProviderStateMixin { length: tabs.length, vsync: this, ); + searchDefault(); } void onRefresh() { @@ -61,4 +74,11 @@ class HomeController extends GetxController with GetTickerProviderStateMixin { var ctr = ctrList[index]; ctr().animateToTop(); } + + void searchDefault() async { + var res = await Request().get(Api.searchDefault); + if (res.data['code'] == 0) { + defaultSearch.value = res.data['data']['name']; + } + } } diff --git a/lib/pages/home/view.dart b/lib/pages/home/view.dart index 8230246b..56c89abe 100644 --- a/lib/pages/home/view.dart +++ b/lib/pages/home/view.dart @@ -1,10 +1,16 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; +import 'package:hive/hive.dart'; +import 'package:pilipala/common/widgets/network_img_layer.dart'; +import 'package:pilipala/pages/bangumi/index.dart'; import 'package:pilipala/pages/hot/index.dart'; import 'package:pilipala/pages/live/index.dart'; +import 'package:pilipala/pages/main/index.dart'; +import 'package:pilipala/pages/mine/index.dart'; import 'package:pilipala/pages/rcmd/index.dart'; import 'package:pilipala/utils/feed_back.dart'; +import 'package:pilipala/utils/storage.dart'; import './controller.dart'; class HomePage extends StatefulWidget { @@ -18,7 +24,7 @@ class _HomePageState extends State with AutomaticKeepAliveClientMixin, TickerProviderStateMixin { final HomeController _homeController = Get.put(HomeController()); List videoList = []; - // late TabController? _tabController; + Stream stream = Get.find().bottomBarStream.stream; @override bool get wantKeepAlive => true; @@ -27,103 +33,231 @@ class _HomePageState extends State Widget build(BuildContext context) { super.build(context); return Scaffold( - appBar: AppBar( - titleSpacing: 0, - title: Padding( - padding: const EdgeInsets.only(left: 12, right: 12, bottom: 0), - child: Stack( - children: [ - const Align( - alignment: Alignment.centerLeft, - child: Padding( - padding: EdgeInsets.only(left: 8), - child: Text( - 'PLPL', - style: TextStyle( - height: 2.8, - fontSize: 17, - fontWeight: FontWeight.bold, - letterSpacing: 1, - fontFamily: 'Jura-Bold', + extendBody: true, + extendBodyBehindAppBar: true, + appBar: AppBar(toolbarHeight: 0, elevation: 0), + body: Column( + children: [ + CustomAppBar(stream: stream, ctr: _homeController), + Container( + padding: const EdgeInsets.only(left: 12, right: 12, bottom: 4), + child: Stack( + children: [ + Align( + alignment: Alignment.center, + child: Theme( + data: ThemeData( + splashColor: Colors.transparent, // 点击时的水波纹颜色设置为透明 + highlightColor: Colors.transparent, // 点击时的背景高亮颜色设置为透明 ), - ), - ), - ), - Align( - alignment: Alignment.center, - child: Theme( - data: ThemeData( - splashColor: Colors.transparent, // 点击时的水波纹颜色设置为透明 - highlightColor: Colors.transparent, // 点击时的背景高亮颜色设置为透明 - ), - child: Padding( - padding: const EdgeInsets.only(top: 4), - child: TabBar( - controller: _homeController.tabController, - tabs: [ - for (var i in _homeController.tabs) - // Tab(text: i['label']) - Padding( - padding: const EdgeInsets.symmetric( - horizontal: 0, vertical: 11), - child: Row( - children: [ - i['icon'], - const SizedBox(width: 4), - Text(i['label']) - ], + child: Padding( + padding: const EdgeInsets.only(top: 2), + child: TabBar( + controller: _homeController.tabController, + tabs: [ + for (var i in _homeController.tabs) + // Tab(text: i['label']) + Padding( + padding: const EdgeInsets.symmetric( + horizontal: 0, vertical: 11), + child: Row( + children: [ + i['icon'], + const SizedBox(width: 4), + Text(i['label']) + ], + ), ), - ), - ], - isScrollable: true, - indicatorWeight: 0, - indicatorPadding: const EdgeInsets.symmetric( - horizontal: 4, vertical: 5), - indicator: BoxDecoration( - color: Theme.of(context) - .colorScheme - .primaryContainer - .withOpacity(0.8), - borderRadius: - const BorderRadius.all(Radius.circular(20)), + ], + isScrollable: true, + indicatorWeight: 0, + indicatorPadding: const EdgeInsets.symmetric( + horizontal: 4, vertical: 5), + indicator: BoxDecoration( + color: Theme.of(context) + .colorScheme + .primaryContainer + .withOpacity(0.8), + borderRadius: + const BorderRadius.all(Radius.circular(20)), + ), + indicatorSize: TabBarIndicatorSize.tab, + labelColor: Theme.of(context).colorScheme.primary, + labelStyle: const TextStyle(fontSize: 13), + dividerColor: Colors.transparent, + unselectedLabelColor: + Theme.of(context).colorScheme.outline, + onTap: (value) => + {feedBack(), _homeController.initialIndex = value}, ), - indicatorSize: TabBarIndicatorSize.tab, - labelColor: Theme.of(context).colorScheme.primary, - labelStyle: const TextStyle(fontSize: 13), - dividerColor: Colors.transparent, - unselectedLabelColor: - Theme.of(context).colorScheme.outline, - onTap: (value) => - {feedBack(), _homeController.initialIndex = value}, ), ), ), - ), - Align( - alignment: Alignment.centerRight, - child: Hero( - tag: 'searchTag', - child: IconButton( - onPressed: () { - feedBack(); - Get.toNamed('/search'); - }, - icon: const Icon(CupertinoIcons.search, size: 21), - ), - ), - ), - ], + ], + ), + ), + Expanded( + child: TabBarView( + controller: _homeController.tabController, + children: const [ + LivePage(), + RcmdPage(), + HotPage(), + BangumiPage(), + ], + ), ), - ), - ), - body: TabBarView( - controller: _homeController.tabController, - children: const [ - LivePage(), - RcmdPage(), - HotPage(), ], ), ); } } + +class CustomAppBar extends StatelessWidget implements PreferredSizeWidget { + final double height; + final Stream? stream; + final ctr; + + const CustomAppBar({ + super.key, + this.height = kToolbarHeight, + this.stream, + this.ctr, + }); + + @override + Size get preferredSize => Size.fromHeight(height); + + @override + Widget build(BuildContext context) { + Box user = GStrorage.user; + + return StreamBuilder( + stream: stream, + initialData: true, + builder: (context, AsyncSnapshot snapshot) { + return ClipRect( + clipBehavior: Clip.hardEdge, + child: AnimatedOpacity( + opacity: snapshot.data ? 1 : 0, + duration: const Duration(milliseconds: 300), + child: AnimatedContainer( + curve: Curves.linear, + duration: const Duration(milliseconds: 300), + height: snapshot.data ? 94 : MediaQuery.of(context).padding.top, + child: Container( + padding: EdgeInsets.only( + left: 12, + right: 12, + bottom: 4, + top: MediaQuery.of(context).padding.top, + ), + child: Row( + children: [ + const Text( + 'PLPL', + style: TextStyle( + height: 2.8, + fontSize: 17, + fontWeight: FontWeight.bold, + fontFamily: 'Jura-Bold', + ), + ), + const SizedBox(width: 10), + Expanded( + child: Hero( + tag: 'searchWrap', + child: GestureDetector( + onTap: () { + Get.toNamed('/search', parameters: { + 'hintText': ctr.defaultSearch.value + }); + }, + child: Container( + width: 250, + height: 45, + clipBehavior: Clip.hardEdge, + padding: const EdgeInsets.only(left: 12, right: 22), + decoration: BoxDecoration( + borderRadius: + const BorderRadius.all(Radius.circular(25)), + color: Theme.of(context) + .colorScheme + .onInverseSurface, + ), + child: Row( + children: [ + Icon( + Icons.search_outlined, + size: 23, + color: Theme.of(context).colorScheme.outline, + ), + const SizedBox(width: 7), + Expanded( + child: Obx( + () => Text( + ctr.defaultSearch.value, + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: TextStyle( + color: Theme.of(context) + .colorScheme + .outline), + ), + ), + ), + ], + ), + ), + ), + ), + ), + const SizedBox(width: 12), + if (user.get(UserBoxKey.userLogin) ?? false) ...[ + GestureDetector( + onTap: () { + feedBack(); + showModalBottomSheet( + context: context, + builder: (_) => const SizedBox( + height: 450, + child: MinePage(), + ), + clipBehavior: Clip.hardEdge, + isScrollControlled: true, + ); + }, + child: NetworkImgLayer( + type: 'avatar', + width: 34, + height: 34, + src: user.get(UserBoxKey.userFace), + ), + ) + ] else ...[ + IconButton( + onPressed: () { + feedBack(); + showModalBottomSheet( + context: context, + builder: (_) => const SizedBox( + height: 450, + child: MinePage(), + ), + clipBehavior: Clip.hardEdge, + isScrollControlled: true, + ); + }, + icon: const Icon(CupertinoIcons.person, size: 22), + ) + ], + ], + ), + ), + ), + ), + ); + }, + ); + } +} diff --git a/lib/pages/main/controller.dart b/lib/pages/main/controller.dart index 2eea635b..122edb87 100644 --- a/lib/pages/main/controller.dart +++ b/lib/pages/main/controller.dart @@ -35,5 +35,6 @@ class MainController extends GetxController { 'label': "媒体库", } ].obs; - final StreamController bottomBarStream = StreamController(); + final StreamController bottomBarStream = + StreamController.broadcast(); } diff --git a/lib/pages/main/view.dart b/lib/pages/main/view.dart index bfdc3660..67d49c82 100644 --- a/lib/pages/main/view.dart +++ b/lib/pages/main/view.dart @@ -66,7 +66,6 @@ class _MainAppState extends State with SingleTickerProviderStateMixin { if (DateTime.now().millisecondsSinceEpoch - _lastSelectTime! < 500) { _homeController.onRefresh(); } else { - await Future.delayed(const Duration(microseconds: 300)); _homeController.animateToTop(); } _lastSelectTime = DateTime.now().millisecondsSinceEpoch; @@ -82,7 +81,6 @@ class _MainAppState extends State with SingleTickerProviderStateMixin { if (DateTime.now().millisecondsSinceEpoch - _lastSelectTime! < 500) { _dynamicController.onRefresh(); } else { - await Future.delayed(const Duration(microseconds: 300)); _dynamicController.animateToTop(); } _lastSelectTime = DateTime.now().millisecondsSinceEpoch; diff --git a/lib/pages/search/controller.dart b/lib/pages/search/controller.dart index 5be168c7..5183c635 100644 --- a/lib/pages/search/controller.dart +++ b/lib/pages/search/controller.dart @@ -19,6 +19,7 @@ class SSearchController extends GetxController { RxList searchSuggestList = [SearchSuggestItem()].obs; final _debouncer = Debouncer(delay: const Duration(milliseconds: 200)); // 设置延迟时间 + String hintText = '搜索'; @override void onInit() { @@ -33,7 +34,13 @@ class SSearchController extends GetxController { } // 其他页面跳转过来 if (Get.parameters.keys.isNotEmpty) { - onClickKeyword(Get.parameters['keyword']!); + if (Get.parameters['keyword'] != null) { + onClickKeyword(Get.parameters['keyword']!); + } + if (Get.parameters['hintText'] != null) { + hintText = Get.parameters['hintText']!; + searchKeyWord.value = hintText; + } } historyCacheList = histiryWord.get('cacheList') ?? []; historyList.value = historyCacheList; diff --git a/lib/pages/search/view.dart b/lib/pages/search/view.dart index e6c9735b..766ca080 100644 --- a/lib/pages/search/view.dart +++ b/lib/pages/search/view.dart @@ -60,26 +60,29 @@ class _SearchPageState extends State with RouteAware { ), const SizedBox(width: 10) ], - title: Obx( - () => TextField( - autofocus: true, - focusNode: _searchController.searchFocusNode, - controller: _searchController.controller.value, - textInputAction: TextInputAction.search, - onChanged: (value) => _searchController.onChange(value), - decoration: InputDecoration( - hintText: '搜索', - border: InputBorder.none, - suffixIcon: IconButton( - icon: Icon( - Icons.clear, - size: 22, - color: Theme.of(context).colorScheme.outline, + title: Hero( + tag: 'searchWrap', + child: Obx( + () => TextField( + autofocus: true, + focusNode: _searchController.searchFocusNode, + controller: _searchController.controller.value, + textInputAction: TextInputAction.search, + onChanged: (value) => _searchController.onChange(value), + decoration: InputDecoration( + hintText: _searchController.hintText, + border: InputBorder.none, + suffixIcon: IconButton( + icon: Icon( + Icons.clear, + size: 22, + color: Theme.of(context).colorScheme.outline, + ), + onPressed: () => _searchController.onClear(), ), - onPressed: () => _searchController.onClear(), ), + onSubmitted: (String value) => _searchController.submit(), ), - onSubmitted: (String value) => _searchController.submit(), ), ), ),