diff --git a/lib/http/api.dart b/lib/http/api.dart index 2333393c5..b1f22982c 100644 --- a/lib/http/api.dart +++ b/lib/http/api.dart @@ -888,4 +888,6 @@ class Api { static const String vipExpAdd = '/x/vip/experience/add'; static const String coinLog = '/x/member/web/coin/log'; + + static const String dynTopicRcmd = '/x/topic/web/dynamic/rcmd'; } diff --git a/lib/http/dynamics.dart b/lib/http/dynamics.dart index f0ac902e9..2cf4e2928 100644 --- a/lib/http/dynamics.dart +++ b/lib/http/dynamics.dart @@ -14,6 +14,7 @@ import 'package:PiliPlus/models_new/article/article_view/data.dart'; import 'package:PiliPlus/models_new/dynamic/dyn_reserve/data.dart'; import 'package:PiliPlus/models_new/dynamic/dyn_topic_feed/topic_card_list.dart'; import 'package:PiliPlus/models_new/dynamic/dyn_topic_top/top_details.dart'; +import 'package:PiliPlus/models_new/dynamic/dyn_topic_top/topic_item.dart'; import 'package:PiliPlus/utils/accounts/account.dart'; import 'package:PiliPlus/utils/storage.dart'; import 'package:PiliPlus/utils/utils.dart'; @@ -473,4 +474,23 @@ class DynamicsHttp { return {'status': false, 'msg': res.data['message']}; } } + + static Future?>> dynTopicRcmd( + {int ps = 25}) async { + final res = await Request().get( + Api.dynTopicRcmd, + queryParameters: { + 'source': 'Web', + 'page_size': ps, + 'web_location': 333.1365, + }, + ); + if (res.data['code'] == 0) { + return Success((res.data['data']?['topic_items'] as List?) + ?.map((e) => TopicItem.fromJson(e)) + .toList()); + } else { + return Error(res.data['message']); + } + } } diff --git a/lib/models_new/dynamic/dyn_topic_pub_search/data.dart b/lib/models_new/dynamic/dyn_topic_pub_search/data.dart index c69c83d2c..ca05cb7e3 100644 --- a/lib/models_new/dynamic/dyn_topic_pub_search/data.dart +++ b/lib/models_new/dynamic/dyn_topic_pub_search/data.dart @@ -1,11 +1,11 @@ import 'package:PiliPlus/models_new/dynamic/dyn_topic_pub_search/new_topic.dart'; import 'package:PiliPlus/models_new/dynamic/dyn_topic_pub_search/page_info.dart'; -import 'package:PiliPlus/models_new/dynamic/dyn_topic_pub_search/topic_item.dart'; +import 'package:PiliPlus/models_new/dynamic/dyn_topic_top/topic_item.dart'; class TopicPubSearchData { NewTopic? newTopic; bool? hasCreateJurisdiction; - List? topicItems; + List? topicItems; String? requestId; PageInfo? pageInfo; @@ -24,7 +24,7 @@ class TopicPubSearchData { : NewTopic.fromJson(json['new_topic'] as Map), hasCreateJurisdiction: json['has_create_jurisdiction'] as bool?, topicItems: (json['topic_items'] as List?) - ?.map((e) => TopicPubSearchItem.fromJson(e as Map)) + ?.map((e) => TopicItem.fromJson(e as Map)) .toList(), requestId: json['request_id'] as String?, pageInfo: json['page_info'] == null diff --git a/lib/models_new/dynamic/dyn_topic_pub_search/topic_item.dart b/lib/models_new/dynamic/dyn_topic_pub_search/topic_item.dart deleted file mode 100644 index 0b1043e85..000000000 --- a/lib/models_new/dynamic/dyn_topic_pub_search/topic_item.dart +++ /dev/null @@ -1,30 +0,0 @@ -class TopicPubSearchItem { - int? id; - String? name; - int? view; - int? discuss; - String? statDesc; - String? description; - bool? showInteractData; - - TopicPubSearchItem({ - this.id, - this.name, - this.view, - this.discuss, - this.statDesc, - this.description, - this.showInteractData, - }); - - factory TopicPubSearchItem.fromJson(Map json) => - TopicPubSearchItem( - id: json['id'] as int?, - name: json['name'] as String?, - view: json['view'] as int?, - discuss: json['discuss'] as int?, - statDesc: json['stat_desc'] as String?, - description: json['description'] as String?, - showInteractData: json['show_interact_data'] as bool?, - ); -} diff --git a/lib/models_new/dynamic/dyn_topic_top/topic_item.dart b/lib/models_new/dynamic/dyn_topic_top/topic_item.dart index 190b2deef..e1d7bd4da 100644 --- a/lib/models_new/dynamic/dyn_topic_top/topic_item.dart +++ b/lib/models_new/dynamic/dyn_topic_top/topic_item.dart @@ -1,10 +1,10 @@ class TopicItem { - int? id; - String? name; - int? view; - int? discuss; - late int fav; - late int like; + int id; + String name; + int view; + int discuss; + int fav; + int like; int? dynamics; String? jumpUrl; String? backColor; @@ -17,10 +17,10 @@ class TopicItem { bool? isLike; TopicItem({ - this.id, - this.name, - this.view, - this.discuss, + required this.id, + required this.name, + required this.view, + required this.discuss, required this.fav, required this.like, this.dynamics, @@ -36,12 +36,12 @@ class TopicItem { }); factory TopicItem.fromJson(Map json) => TopicItem( - id: json['id'] as int?, - name: json['name'] as String?, - view: json['view'] as int? ?? 0, - discuss: json['discuss'] as int? ?? 0, - fav: json['fav'] as int? ?? 0, - like: json['like'] as int? ?? 0, + id: json['id'], + name: json['name'], + view: json['view'] ?? 0, + discuss: json['discuss'] ?? 0, + fav: json['fav'] ?? 0, + like: json['like'] ?? 0, dynamics: json['dynamics'] as int?, jumpUrl: json['jump_url'] as String?, backColor: json['back_color'] as String?, diff --git a/lib/pages/dynamics_create/view.dart b/lib/pages/dynamics_create/view.dart index b0176def8..72f389ade 100644 --- a/lib/pages/dynamics_create/view.dart +++ b/lib/pages/dynamics_create/view.dart @@ -11,7 +11,7 @@ import 'package:PiliPlus/common/widgets/pair.dart'; import 'package:PiliPlus/http/dynamics.dart'; import 'package:PiliPlus/models/common/publish_panel_type.dart'; import 'package:PiliPlus/models/common/reply/reply_option_type.dart'; -import 'package:PiliPlus/models_new/dynamic/dyn_topic_pub_search/topic_item.dart'; +import 'package:PiliPlus/models_new/dynamic/dyn_topic_top/topic_item.dart'; import 'package:PiliPlus/pages/common/common_publish_page.dart'; import 'package:PiliPlus/pages/dynamics_select_topic/controller.dart'; import 'package:PiliPlus/pages/dynamics_select_topic/view.dart'; @@ -621,7 +621,7 @@ class _CreateDynPanelState extends CommonPublishPageState { double _offset = 0; Future _onSelectTopic() async { - TopicPubSearchItem? res = await showModalBottomSheet( + TopicItem? res = await showModalBottomSheet( context: context, useSafeArea: true, isScrollControlled: true, @@ -643,7 +643,7 @@ class _CreateDynPanelState extends CommonPublishPageState { ), ); if (res != null) { - topic.value = Pair(first: res.id!, second: res.name!); + topic.value = Pair(first: res.id, second: res.name); } } } diff --git a/lib/pages/dynamics_select_topic/controller.dart b/lib/pages/dynamics_select_topic/controller.dart index 7d5dbba41..784813a6c 100644 --- a/lib/pages/dynamics_select_topic/controller.dart +++ b/lib/pages/dynamics_select_topic/controller.dart @@ -1,13 +1,13 @@ import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/search.dart'; import 'package:PiliPlus/models_new/dynamic/dyn_topic_pub_search/data.dart'; -import 'package:PiliPlus/models_new/dynamic/dyn_topic_pub_search/topic_item.dart'; +import 'package:PiliPlus/models_new/dynamic/dyn_topic_top/topic_item.dart'; import 'package:PiliPlus/pages/common/common_list_controller.dart'; import 'package:flutter/widgets.dart'; import 'package:get/get.dart'; class SelectTopicController - extends CommonListController { + extends CommonListController { final focusNode = FocusNode(); final controller = TextEditingController(); @@ -20,7 +20,7 @@ class SelectTopicController } @override - List? getDataList(TopicPubSearchData response) { + List? getDataList(TopicPubSearchData response) { return response.topicItems; } diff --git a/lib/pages/dynamics_select_topic/view.dart b/lib/pages/dynamics_select_topic/view.dart index 8e3fd08fd..b0a3d3053 100644 --- a/lib/pages/dynamics_select_topic/view.dart +++ b/lib/pages/dynamics_select_topic/view.dart @@ -1,12 +1,11 @@ import 'dart:async'; -import 'package:PiliPlus/common/widgets/custom_icon.dart'; import 'package:PiliPlus/common/widgets/loading_widget/loading_widget.dart'; import 'package:PiliPlus/http/loading_state.dart'; -import 'package:PiliPlus/models_new/dynamic/dyn_topic_pub_search/topic_item.dart'; +import 'package:PiliPlus/models_new/dynamic/dyn_topic_top/topic_item.dart'; import 'package:PiliPlus/pages/dynamics_select_topic/controller.dart'; +import 'package:PiliPlus/pages/dynamics_select_topic/widgets/item.dart'; import 'package:PiliPlus/utils/extension.dart'; -import 'package:PiliPlus/utils/utils.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:stream_transform/stream_transform.dart'; @@ -140,70 +139,41 @@ class _SelectTopicPanelState extends State { } Widget _buildBody( - ThemeData theme, LoadingState?> loadingState) { + ThemeData theme, LoadingState?> loadingState) { return switch (loadingState) { Loading() => loadingWidget, - Success?>(:var response) => - response?.isNotEmpty == true - ? NotificationListener( - onNotification: (notification) { - if (notification is UserScrollNotification) { - if (_controller.focusNode.hasFocus) { - _controller.focusNode.unfocus(); - } - } else if (notification is ScrollEndNotification) { - widget.callback?.call(notification.metrics.pixels); + Success?>(:var response) => response?.isNotEmpty == true + ? NotificationListener( + onNotification: (notification) { + if (notification is UserScrollNotification) { + if (_controller.focusNode.hasFocus) { + _controller.focusNode.unfocus(); } - return false; - }, - child: ListView.builder( - padding: EdgeInsets.only( - bottom: MediaQuery.paddingOf(context).bottom + - MediaQuery.viewInsetsOf(context).bottom + - 80, - ), - controller: widget.scrollController, - itemBuilder: (context, index) { - if (index == response.length - 1) { - _controller.onLoadMore(); - } - final item = response[index]; - return ListTile( - dense: true, - onTap: () => Get.back(result: item), - title: Text.rich( - TextSpan( - children: [ - const WidgetSpan( - alignment: PlaceholderAlignment.middle, - child: Padding( - padding: EdgeInsets.only(right: 5), - child: Icon( - CustomIcon.topic_tag, - size: 18, - ), - ), - ), - TextSpan( - text: item.name, - style: const TextStyle(fontSize: 14), - ), - ], - ), - ), - subtitle: Padding( - padding: const EdgeInsets.only(left: 23), - child: Text( - '${Utils.numFormat(item.view)}浏览 · ${Utils.numFormat(item.discuss)}讨论', - style: TextStyle(color: theme.colorScheme.outline), - ), - ), - ); - }, - itemCount: response!.length, + } else if (notification is ScrollEndNotification) { + widget.callback?.call(notification.metrics.pixels); + } + return false; + }, + child: ListView.builder( + padding: EdgeInsets.only( + bottom: MediaQuery.paddingOf(context).bottom + + MediaQuery.viewInsetsOf(context).bottom + + 80, ), - ) - : _errWidget(), + controller: widget.scrollController, + itemBuilder: (context, index) { + if (index == response.length - 1) { + _controller.onLoadMore(); + } + return DynTopicItem( + item: response[index], + onTap: (item) => Get.back(result: item), + ); + }, + itemCount: response!.length, + ), + ) + : _errWidget(), Error(:var errMsg) => _errWidget(errMsg), }; } diff --git a/lib/pages/dynamics_select_topic/widgets/item.dart b/lib/pages/dynamics_select_topic/widgets/item.dart new file mode 100644 index 000000000..1ca8e8bee --- /dev/null +++ b/lib/pages/dynamics_select_topic/widgets/item.dart @@ -0,0 +1,50 @@ +import 'package:PiliPlus/common/widgets/custom_icon.dart'; +import 'package:PiliPlus/models_new/dynamic/dyn_topic_top/topic_item.dart'; +import 'package:PiliPlus/utils/utils.dart'; +import 'package:flutter/material.dart'; + +class DynTopicItem extends StatelessWidget { + const DynTopicItem({ + super.key, + required this.item, + required this.onTap, + }); + + final TopicItem item; + final ValueChanged onTap; + + @override + Widget build(BuildContext context) { + return ListTile( + dense: true, + onTap: () => onTap(item), + title: Text.rich( + TextSpan( + children: [ + const WidgetSpan( + alignment: PlaceholderAlignment.middle, + child: Padding( + padding: EdgeInsets.only(right: 5), + child: Icon( + CustomIcon.topic_tag, + size: 18, + ), + ), + ), + TextSpan( + text: item.name, + style: const TextStyle(fontSize: 14), + ), + ], + ), + ), + subtitle: Padding( + padding: const EdgeInsets.only(left: 23), + child: Text( + '${Utils.numFormat(item.view)}浏览 · ${Utils.numFormat(item.discuss)}讨论', + style: TextStyle(color: Theme.of(context).colorScheme.outline), + ), + ), + ); + } +} diff --git a/lib/pages/dynamics_topic/controller.dart b/lib/pages/dynamics_topic/controller.dart index 5e0779f4f..40010c255 100644 --- a/lib/pages/dynamics_topic/controller.dart +++ b/lib/pages/dynamics_topic/controller.dart @@ -40,7 +40,7 @@ class DynTopicController topState.value = await DynamicsHttp.topicTop(topicId: topicId); if (topState.value.isSuccess) { var topicItem = topState.value.data!.topicItem!; - topicName = topicItem.name!; + topicName = topicItem.name; isFav.value = topicItem.isFav; isLike.value = topicItem.isLike; } diff --git a/lib/pages/dynamics_topic/view.dart b/lib/pages/dynamics_topic/view.dart index dd97a1b4a..ea538c52d 100644 --- a/lib/pages/dynamics_topic/view.dart +++ b/lib/pages/dynamics_topic/view.dart @@ -153,7 +153,7 @@ class _DynTopicPageState extends State { pinned: true, callback: (value) => _controller.appbarOffset = value - kToolbarHeight - paddingTop - 7, - title: IgnorePointer(child: Text(response!.topicItem!.name!)), + title: IgnorePointer(child: Text(response!.topicItem!.name)), flexibleSpace: Container( decoration: BoxDecoration( image: DecorationImage( @@ -206,7 +206,7 @@ class _DynTopicPageState extends State { ), ), Text( - response.topicItem!.name!, + response.topicItem!.name, style: const TextStyle( fontSize: 16, fontWeight: FontWeight.bold, diff --git a/lib/pages/dynamics_topic_rcmd/controller.dart b/lib/pages/dynamics_topic_rcmd/controller.dart new file mode 100644 index 000000000..2f3b53230 --- /dev/null +++ b/lib/pages/dynamics_topic_rcmd/controller.dart @@ -0,0 +1,17 @@ +import 'package:PiliPlus/http/dynamics.dart'; +import 'package:PiliPlus/http/loading_state.dart'; +import 'package:PiliPlus/models_new/dynamic/dyn_topic_top/topic_item.dart'; +import 'package:PiliPlus/pages/common/common_list_controller.dart'; + +class DynTopicRcmdController + extends CommonListController?, TopicItem> { + @override + void onInit() { + super.onInit(); + queryData(); + } + + @override + Future?>> customGetData() => + DynamicsHttp.dynTopicRcmd(); +} diff --git a/lib/pages/dynamics_topic_rcmd/view.dart b/lib/pages/dynamics_topic_rcmd/view.dart new file mode 100644 index 000000000..fadd31cb9 --- /dev/null +++ b/lib/pages/dynamics_topic_rcmd/view.dart @@ -0,0 +1,70 @@ +import 'package:PiliPlus/common/widgets/loading_widget/http_error.dart'; +import 'package:PiliPlus/common/widgets/refresh_indicator.dart'; +import 'package:PiliPlus/http/loading_state.dart'; +import 'package:PiliPlus/models_new/dynamic/dyn_topic_top/topic_item.dart'; +import 'package:PiliPlus/pages/dynamics_select_topic/widgets/item.dart'; +import 'package:PiliPlus/pages/dynamics_topic_rcmd/controller.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +class DynTopicRcmdPage extends StatefulWidget { + const DynTopicRcmdPage({super.key}); + + @override + State createState() => _DynTopicRcmdPageState(); +} + +class _DynTopicRcmdPageState extends State { + final DynTopicRcmdController _controller = Get.put(DynTopicRcmdController()); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(title: const Text('话题')), + body: SafeArea( + top: false, + bottom: false, + child: refreshIndicator( + onRefresh: _controller.onRefresh, + child: CustomScrollView( + slivers: [ + SliverPadding( + padding: EdgeInsets.only( + bottom: MediaQuery.paddingOf(context).bottom + 80, + ), + sliver: Obx(() => _buildBody(_controller.loadingState.value)), + ), + ], + ), + ), + ), + ); + } + + Widget _buildBody(LoadingState?> loadingState) { + return switch (loadingState) { + Loading() => const SliverToBoxAdapter(child: LinearProgressIndicator()), + Success(:var response) => response?.isNotEmpty == true + ? SliverList.builder( + itemCount: response!.length, + itemBuilder: (context, index) { + return DynTopicItem( + item: response[index], + onTap: (item) => Get.toNamed( + '/dynTopic', + parameters: { + 'id': item.id.toString(), + 'name': item.name, + }, + ), + ); + }, + ) + : HttpError(onReload: _controller.onReload), + Error(:var errMsg) => HttpError( + errMsg: errMsg, + onReload: _controller.onReload, + ), + }; + } +} diff --git a/lib/pages/search/view.dart b/lib/pages/search/view.dart index 9ba8f6f09..d1313bfba 100644 --- a/lib/pages/search/view.dart +++ b/lib/pages/search/view.dart @@ -174,31 +174,48 @@ class _SearchPageState extends State { mainAxisSize: MainAxisSize.min, children: [ text, - Padding( - padding: const EdgeInsets.only(left: 14), - child: SizedBox( - height: 34, - child: TextButton.icon( - onPressed: () => Get.toNamed( - '/searchTrending', - parameters: {'tag': _tag}, - ), - label: Text( - '完整榜单', - style: TextStyle( - fontSize: 13, - color: theme.colorScheme.outline, - ), - ), - icon: Icon( - size: 16, - Icons.keyboard_arrow_right, + const SizedBox(width: 14), + SizedBox( + height: 34, + child: TextButton.icon( + onPressed: () => Get.toNamed( + '/searchTrending', + parameters: {'tag': _tag}, + ), + label: Text( + '完整榜单', + style: TextStyle( + fontSize: 13, color: theme.colorScheme.outline, ), - iconAlignment: IconAlignment.end, ), + icon: Icon( + size: 16, + Icons.keyboard_arrow_right, + color: theme.colorScheme.outline, + ), + iconAlignment: IconAlignment.end, ), - ) + ), + SizedBox( + height: 34, + child: TextButton.icon( + onPressed: () => Get.toNamed('/dynTopicRcmd'), + label: Text( + '话题', + style: TextStyle( + fontSize: 13, + color: theme.colorScheme.outline, + ), + ), + icon: Icon( + size: 16, + Icons.keyboard_arrow_right, + color: theme.colorScheme.outline, + ), + iconAlignment: IconAlignment.end, + ), + ), ], ) : text, diff --git a/lib/router/app_pages.dart b/lib/router/app_pages.dart index d0997858f..d49bb536e 100644 --- a/lib/router/app_pages.dart +++ b/lib/router/app_pages.dart @@ -6,6 +6,7 @@ import 'package:PiliPlus/pages/danmaku_block/view.dart'; import 'package:PiliPlus/pages/dynamics/view.dart'; import 'package:PiliPlus/pages/dynamics_detail/view.dart'; import 'package:PiliPlus/pages/dynamics_topic/view.dart'; +import 'package:PiliPlus/pages/dynamics_topic_rcmd/view.dart'; import 'package:PiliPlus/pages/fan/view.dart'; import 'package:PiliPlus/pages/fav/view.dart'; import 'package:PiliPlus/pages/fav_create/view.dart'; @@ -177,6 +178,7 @@ class Routes { CustomGetPage(name: '/barSetting', page: () => const BarSetPage()), CustomGetPage(name: '/upowerRank', page: () => const UpowerRankPage()), CustomGetPage(name: '/spaceSetting', page: () => const SpaceSettingPage()), + CustomGetPage(name: '/dynTopicRcmd', page: () => const DynTopicRcmdPage()), ]; }