Reason Pun

增加了一言搜索页面

......@@ -95,18 +95,18 @@ class JsonConvert {
}
//list is returned by type
static M? _getListChildType<M>(List<dynamic> data) {
static M? _getListChildType<M>(List<Map<String, dynamic>> data) {
if(<UserEntity>[] is M){
return data.map<UserEntity>((e) => UserEntity.fromJson(e)).toList() as M;
return data.map<UserEntity>((Map<String, dynamic> e) => UserEntity.fromJson(e)).toList() as M;
}
if(<CategoryItemEntity>[] is M){
return data.map<CategoryItemEntity>((e) => CategoryItemEntity.fromJson(e)).toList() as M;
return data.map<CategoryItemEntity>((Map<String, dynamic> e) => CategoryItemEntity.fromJson(e)).toList() as M;
}
if(<FriendEntity>[] is M){
return data.map<FriendEntity>((e) => FriendEntity.fromJson(e)).toList() as M;
return data.map<FriendEntity>((Map<String, dynamic> e) => FriendEntity.fromJson(e)).toList() as M;
}
if(<FriendData>[] is M){
return data.map<FriendData>((e) => FriendData.fromJson(e)).toList() as M;
return data.map<FriendData>((Map<String, dynamic> e) => FriendData.fromJson(e)).toList() as M;
}
print("${M.toString()} not found");
......@@ -117,9 +117,8 @@ class JsonConvert {
static M? fromJsonAsT<M>(dynamic json) {
if(json == null){
return null;
}
if (json is List) {
return _getListChildType<M>(json);
} if (json is List) {
return _getListChildType<M>(json.map((e) => e as Map<String, dynamic>).toList());
} else {
return _fromJsonSingle<M>(json as Map<String, dynamic>);
}
......
This diff is collapsed. Click to expand it.
import 'base_page.dart';
import 'base_page_presenter.dart';
import 'base_presenter.dart';
/// 管理多个Presenter,实现复用。
class PowerPresenter<IMvpView> extends BasePresenter {
PowerPresenter(BasePageMixin state) {
_state = state;
}
......@@ -43,10 +40,10 @@ class PowerPresenter<IMvpView> extends BasePresenter {
@override
void didUpdateWidgets<W>(W oldWidget) {
void _didUpdateWidgets(BasePagePresenter presenter) {
presenter.didUpdateWidgets<W>(oldWidget);
}
_presenters.forEach(_didUpdateWidgets);
}
......@@ -67,5 +64,4 @@ class PowerPresenter<IMvpView> extends BasePresenter {
void _initState(BasePagePresenter presenter) {
presenter.initState();
}
}
......
import 'package:one_poem/mvp/mvps.dart';
import 'package:one_poem/poem/models/search_entity.dart';
import 'package:one_poem/poem/provider/base_list_provider.dart';
abstract class PoemSearchIMvpView implements IMvpView {
BaseListProvider<SearchItems> get provider;
}
import 'package:one_poem/generated/json/base/json_field.dart';
import 'package:one_poem/generated/json/search_entity.g.dart';
@JsonSerializable()
class SearchEntity {
SearchEntity();
factory SearchEntity.fromJson(Map<String, dynamic> json) => $SearchEntityFromJson(json);
Map<String, dynamic> toJson() => $SearchEntityToJson(this);
@JSONField(name: 'total_count')
int? totalCount;
@JSONField(name: 'incomplete_results')
bool? incompleteResults;
List<SearchItems>? items;
}
@JsonSerializable()
class SearchItems {
SearchItems();
factory SearchItems.fromJson(Map<String, dynamic> json) => $SearchItemsFromJson(json);
Map<String, dynamic> toJson() => $SearchItemsToJson(this);
int? id;
@JSONField(name: 'node_id')
String? nodeId;
String? name;
@JSONField(name: 'full_name')
String? fullName;
bool? private;
SearchItemsOwner? owner;
@JSONField(name: 'html_url')
String? htmlUrl;
String? description;
bool? fork;
String? url;
@JSONField(name: 'forks_url')
String? forksUrl;
@JSONField(name: 'keys_url')
String? keysUrl;
@JSONField(name: 'collaborators_url')
String? collaboratorsUrl;
@JSONField(name: 'teams_url')
String? teamsUrl;
@JSONField(name: 'hooks_url')
String? hooksUrl;
@JSONField(name: 'issue_events_url')
String? issueEventsUrl;
@JSONField(name: 'events_url')
String? eventsUrl;
@JSONField(name: 'assignees_url')
String? assigneesUrl;
@JSONField(name: 'branches_url')
String? branchesUrl;
@JSONField(name: 'tags_url')
String? tagsUrl;
@JSONField(name: 'blobs_url')
String? blobsUrl;
@JSONField(name: 'git_tags_url')
String? gitTagsUrl;
@JSONField(name: 'git_refs_url')
String? gitRefsUrl;
@JSONField(name: 'trees_url')
String? treesUrl;
@JSONField(name: 'statuses_url')
String? statusesUrl;
@JSONField(name: 'languages_url')
String? languagesUrl;
@JSONField(name: 'stargazers_url')
String? stargazersUrl;
@JSONField(name: 'contributors_url')
String? contributorsUrl;
@JSONField(name: 'subscribers_url')
String? subscribersUrl;
@JSONField(name: 'subscription_url')
String? subscriptionUrl;
@JSONField(name: 'commits_url')
String? commitsUrl;
@JSONField(name: 'git_commits_url')
String? gitCommitsUrl;
@JSONField(name: 'comments_url')
String? commentsUrl;
@JSONField(name: 'issue_comment_url')
String? issueCommentUrl;
@JSONField(name: 'contents_url')
String? contentsUrl;
@JSONField(name: 'compare_url')
String? compareUrl;
@JSONField(name: 'merges_url')
String? mergesUrl;
@JSONField(name: 'archive_url')
String? archiveUrl;
@JSONField(name: 'downloads_url')
String? downloadsUrl;
@JSONField(name: 'issues_url')
String? issuesUrl;
@JSONField(name: 'pulls_url')
String? pullsUrl;
@JSONField(name: 'milestones_url')
String? milestonesUrl;
@JSONField(name: 'notifications_url')
String? notificationsUrl;
@JSONField(name: 'labels_url')
String? labelsUrl;
@JSONField(name: 'releases_url')
String? releasesUrl;
@JSONField(name: 'deployments_url')
String? deploymentsUrl;
@JSONField(name: 'created_at')
String? createdAt;
@JSONField(name: 'updated_at')
String? updatedAt;
@JSONField(name: 'pushed_at')
String? pushedAt;
@JSONField(name: 'git_url')
String? gitUrl;
@JSONField(name: 'ssh_url')
String? sshUrl;
@JSONField(name: 'clone_url')
String? cloneUrl;
@JSONField(name: 'svn_url')
String? svnUrl;
String? homepage;
int? size;
@JSONField(name: 'stargazers_count')
int? stargazersCount;
@JSONField(name: 'watchers_count')
int? watchersCount;
String? language;
@JSONField(name: 'has_issues')
bool? hasIssues;
@JSONField(name: 'has_projects')
bool? hasProjects;
@JSONField(name: 'has_downloads')
bool? hasDownloads;
@JSONField(name: 'has_wiki')
bool? hasWiki;
@JSONField(name: 'has_pages')
bool? hasPages;
@JSONField(name: 'forks_count')
int? forksCount;
bool? archived;
bool? disabled;
@JSONField(name: 'open_issues_count')
int? openIssuesCount;
SearchItemsLicense? license;
int? forks;
@JSONField(name: 'open_issues')
int? openIssues;
int? watchers;
@JSONField(name: 'default_branch')
String? defaultBranch;
double? score;
}
@JsonSerializable()
class SearchItemsOwner {
SearchItemsOwner();
factory SearchItemsOwner.fromJson(Map<String, dynamic> json) => $SearchItemsOwnerFromJson(json);
Map<String, dynamic> toJson() => $SearchItemsOwnerToJson(this);
String? login;
int? id;
@JSONField(name: 'node_id')
String? nodeId;
@JSONField(name: 'avatar_url')
String? avatarUrl;
@JSONField(name: 'gravatar_id')
String? gravatarId;
String? url;
@JSONField(name: 'html_url')
String? htmlUrl;
@JSONField(name: 'followers_url')
String? followersUrl;
@JSONField(name: 'following_url')
String? followingUrl;
@JSONField(name: 'gists_url')
String? gistsUrl;
@JSONField(name: 'starred_url')
String? starredUrl;
@JSONField(name: 'subscriptions_url')
String? subscriptionsUrl;
@JSONField(name: 'organizations_url')
String? organizationsUrl;
@JSONField(name: 'repos_url')
String? reposUrl;
@JSONField(name: 'events_url')
String? eventsUrl;
@JSONField(name: 'received_events_url')
String? receivedEventsUrl;
String? type;
@JSONField(name: 'site_admin')
bool? siteAdmin;
}
@JsonSerializable()
class SearchItemsLicense {
SearchItemsLicense();
factory SearchItemsLicense.fromJson(Map<String, dynamic> json) => $SearchItemsLicenseFromJson(json);
Map<String, dynamic> toJson() => $SearchItemsLicenseToJson(this);
String? key;
String? name;
@JSONField(name: 'spdx_id')
String? spdxId;
String? url;
@JSONField(name: 'node_id')
String? nodeId;
}
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:one_poem/category/category_router.dart';
import 'package:one_poem/poem/poem_router.dart';
import 'package:one_poem/routers/fluro_navigator.dart';
import 'package:one_poem/tiktok/controller/tiktok_video_list_controller.dart';
import 'package:one_poem/tiktok/mock/video.dart';
......@@ -134,7 +134,10 @@ class _PoemPageState extends State<PoemPage> with WidgetsBindingObserver {
Icons.search,
color: Colors.white,
),
onPressed: () {},
onPressed: () {
NavigatorUtils.push(context, PoemRouter.poemSearchPage);
_videoListController.currentPlayer.pause();
},
),
),
leftPage: searchPage,
......
import 'package:flutter/material.dart';
import 'package:one_poem/mvp/base_page.dart';
import 'package:one_poem/mvp/power_presenter.dart';
import 'package:one_poem/poem/iview/poem_search_iview.dart';
import 'package:one_poem/poem/models/search_entity.dart';
import 'package:one_poem/poem/presenter/poem_search_presenter.dart';
import 'package:one_poem/poem/provider/base_list_provider.dart';
import 'package:one_poem/widgets/my_refresh_list.dart';
import 'package:one_poem/widgets/search_bar.dart';
import 'package:one_poem/widgets/state_layout.dart';
import 'package:provider/provider.dart';
import 'package:one_poem/extension/int_extension.dart';
class PoemSearchPage extends StatefulWidget {
const PoemSearchPage({Key? key}) : super(key: key);
@override
_PoemSearchPageState createState() => _PoemSearchPageState();
}
class _PoemSearchPageState extends State<PoemSearchPage>
with BasePageMixin<PoemSearchPage, PowerPresenter>
implements PoemSearchIMvpView {
@override
BaseListProvider<SearchItems> provider = BaseListProvider<SearchItems>();
late String _keyword;
int _page = 1;
@override
void initState() {
provider.stateType = StateType.empty;
super.initState();
}
@override
Widget build(BuildContext context) {
return ChangeNotifierProvider<BaseListProvider<SearchItems>>(
create: (_) => provider,
child: Scaffold(
appBar: SearchBar(
hintText: '请输入要查询的内容',
onPressed: (text) {
if (text.isEmpty) {
showToast('搜索关键字不能为空!');
return;
}
_keyword = text;
provider.setStateType(StateType.loading);
_page = 1;
_poemSearchPresenter.search(_keyword, _page, true);
},
),
body:
Consumer<BaseListProvider<SearchItems>>(builder: (_, provider, __) {
return DeerListView(
key: const Key('poem_search_list'),
itemCount: provider.list.length,
stateType: provider.stateType,
onRefresh: _onRefresh,
loadMore: _loadMore,
itemExtent: 50.0,
hasMore: provider.hasMore,
itemBuilder: (_, index) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 16.px),
alignment: Alignment.centerLeft,
child: Text(provider.list[index].name!),
);
},
);
}),
),
);
}
Future<void> _onRefresh() async {
_page = 1;
await _poemSearchPresenter.search(_keyword, _page, false);
}
Future<void> _loadMore() async {
_page++;
await _poemSearchPresenter.search(_keyword, _page, false);
}
late PoemSearchPresenter _poemSearchPresenter;
@override
PowerPresenter createPresenter() {
final PowerPresenter powerPresenter = PowerPresenter<dynamic>(this);
_poemSearchPresenter = PoemSearchPresenter();
powerPresenter.requestPresenter([_poemSearchPresenter]);
return powerPresenter;
}
}
import 'package:fluro/fluro.dart';
import 'package:one_poem/poem/page/poem_record_audio.dart';
import 'package:one_poem/poem/page/poem_search_page.dart';
import 'package:one_poem/routers/i_router.dart';
import 'page/poem_complete_page.dart';
import 'page/poem_detail.dart';
......@@ -16,6 +17,7 @@ class PoemRouter implements IRouterProvider {
static String poemVideoPlayer = '/poem/video/player';
static String poemPublish = '/poem/publish';
static String poemCompletePage = '/poem/complete';
static String poemSearchPage = '/poem/search';
@override
void initRouter(FluroRouter router) {
......@@ -25,6 +27,7 @@ class PoemRouter implements IRouterProvider {
handlerFunc: (_, __) => const PoemPage(),
),
);
router.define(
poemDetailPage,
handler: Handler(
......@@ -95,5 +98,8 @@ class PoemRouter implements IRouterProvider {
},
),
);
router.define(poemSearchPage,
handler: Handler(handlerFunc: (_, __) => const PoemSearchPage()));
}
}
......
import 'package:one_poem/mvp/base_page_presenter.dart';
import 'package:one_poem/net/dio_utils.dart';
import 'package:one_poem/net/http_api.dart';
import 'package:one_poem/poem/iview/poem_search_iview.dart';
import 'package:one_poem/poem/models/search_entity.dart';
import 'package:one_poem/widgets/state_layout.dart';
class PoemSearchPresenter extends BasePagePresenter<PoemSearchIMvpView> {
Future search(String text, int page, bool isShowDialog) {
final Map<String, String> params = <String, String>{};
params['q'] = text;
params['page'] = page.toString();
params['l'] = 'Dart';
return requestNetwork<SearchEntity>(Method.get,
url: HttpApi.search,
queryParameters: params,
isShow: isShowDialog,
onSuccess: (data) {
if (data != null && data.items != null) {
/// 一页30条数据,等于30条认为有下一页
/// 具体的处理逻辑根据具体的接口情况处理,这部分可以抽离出来
view.provider.hasMore = data.items!.length == 30;
if (page == 1) {
/// 刷新
view.provider.list.clear();
if (data.items!.isEmpty) {
view.provider.setStateType(StateType.order);
} else {
view.provider.addAll(data.items!);
}
} else {
view.provider.addAll(data.items!);
}
} else {
/// 加载失败
view.provider.hasMore = false;
view.provider.setStateType(StateType.network);
}
},
onError: (_, __) {
/// 加载失败
view.provider.hasMore = false;
view.provider.setStateType(StateType.network);
}
);
}
}
import 'package:flutter/material.dart';
import 'package:one_poem/widgets/state_layout.dart';
class BaseListProvider<T> extends ChangeNotifier {
final List<T> _list = <T>[];
List<T> get list => _list;
bool hasMore = true;
StateType stateType = StateType.loading;
void setStateType(StateType stateType) {
this.stateType = stateType;
notifyListeners();
}
void add(T data) {
_list.add(data);
notifyListeners();
}
void addAll(List<T> data) {
_list.addAll(data);
notifyListeners();
}
void insert(int i, T data) {
_list.insert(i, data);
notifyListeners();
}
void insertAll(int i, List<T> data) {
_list.insertAll(i, data);
notifyListeners();
}
void remove(T data) {
_list.remove(data);
notifyListeners();
}
void removeAt(int i) {
_list.removeAt(i);
notifyListeners();
}
void clear() {
_list.clear();
notifyListeners();
}
void refresh() {
notifyListeners();
}
}
......@@ -3,19 +3,18 @@ import 'package:flutter/scheduler.dart';
import 'package:flutter/services.dart';
import 'package:one_poem/res/resources.dart';
import 'package:one_poem/util/theme_utils.dart';
import 'package:one_poem/extension/int_extension.dart';
import 'load_image.dart';
import 'my_button.dart';
/// 搜索页的AppBar
class SearchBar extends StatefulWidget implements PreferredSizeWidget {
const SearchBar({
Key? key,
this.hintText = '',
this.backImg = 'assets/images/ic_back_black.png',
this.onPressed,
}): super(key: key);
}) : super(key: key);
final String backImg;
final String hintText;
......@@ -25,11 +24,10 @@ class SearchBar extends StatefulWidget implements PreferredSizeWidget {
_SearchBarState createState() => _SearchBarState();
@override
Size get preferredSize => const Size.fromHeight(48.0);
Size get preferredSize => Size.fromHeight(48.px);
}
class _SearchBarState extends State<SearchBar> {
final TextEditingController _controller = TextEditingController();
final FocusNode _focus = FocusNode();
......@@ -40,34 +38,26 @@ class _SearchBarState extends State<SearchBar> {
super.dispose();
}
// @override
// void initState() {
// WidgetsBinding.instance!.addPostFrameCallback((_) async {
// SystemChannels.textInput.invokeMethod<void>('TextInput.updateConfig', const TextInputConfiguration().toJson());
// SystemChannels.textInput.invokeMethod<void>('TextInput.hide');
// });
// super.initState();
// }
@override
Widget build(BuildContext context) {
final bool isDark = context.isDark;
final Color iconColor = isDark ? Colours.dark_text_gray : Colours.text_gray_c;
final Color iconColor =
isDark ? Colours.dark_text_gray : Colours.text_gray_c;
final Widget back = Semantics(
label: '返回',
child: SizedBox(
width: 48.0,
height: 48.0,
width: 48.px,
height: 48.px,
child: InkWell(
onTap: () {
_focus.unfocus();
Navigator.maybePop(context);
},
borderRadius: BorderRadius.circular(24.0),
borderRadius: BorderRadius.circular(24.px),
child: Padding(
key: const Key('search_back'),
padding: const EdgeInsets.all(12.0),
padding: EdgeInsets.all(12.px),
child: Image.asset(
widget.backImg,
color: isDark ? Colours.dark_text : Colours.text,
......@@ -77,43 +67,15 @@ class _SearchBarState extends State<SearchBar> {
),
);
/// 使用2.0.0新增CupertinoSearchTextField 实现, 需添加依赖 cupertino_icons: ^1.0.2
// final Widget textField1 = Expanded(child: Container(
// height: 32.0,
// child: CupertinoSearchTextField(
// key: const Key('search_text_field'),
// controller: _controller,
// focusNode: _focus,
// placeholder: widget.hintText,
// placeholderStyle: Theme.of(context).inputDecorationTheme.hintStyle,
// padding: const EdgeInsetsDirectional.fromSTEB(3.8, 0, 5, 0),
// prefixInsets: const EdgeInsetsDirectional.fromSTEB(8, 0, 0, 0),
// suffixInsets: const EdgeInsetsDirectional.fromSTEB(0, 0, 8, 0),
// style: Theme.of(context).textTheme.subtitle1,
// itemSize: 16.0,
// itemColor: iconColor,
// decoration: BoxDecoration(
// color: isDark ? Colours.dark_material_bg : Colours.bg_gray,
// borderRadius: BorderRadius.circular(4.0),
// ),
// onSubmitted: (String val) {
// _focus.unfocus();
// // 点击软键盘的动作按钮时的回调
// widget.onPressed(val);
// },
// )
// ));
final Widget textField = Expanded(
child: Container(
height: 32.0,
height: 32.px,
decoration: BoxDecoration(
color: isDark ? Colours.dark_material_bg : Colours.bg_gray,
borderRadius: BorderRadius.circular(4.0),
borderRadius: BorderRadius.circular(4.px),
),
child: TextField(
key: const Key('search_text_field'),
// autofocus: true,
controller: _controller,
focusNode: _focus,
textInputAction: TextInputAction.search,
......@@ -123,19 +85,24 @@ class _SearchBarState extends State<SearchBar> {
widget.onPressed?.call(val);
},
decoration: InputDecoration(
contentPadding: const EdgeInsets.only(left: -8.0, right: -16.0, bottom: 14.0),
contentPadding:
EdgeInsets.only(left: -8.px, right: -16.px, bottom: 14.px),
border: InputBorder.none,
icon: Padding(
padding: const EdgeInsets.only(top: 8.0, bottom: 8.0, left: 8.0),
child: LoadAssetImage('poem/order_search', color: iconColor,),
padding: EdgeInsets.only(top: 8.px, bottom: 8.px, left: 8.px),
child: LoadAssetImage(
'poem/poem_search',
color: iconColor,
),
),
hintText: widget.hintText,
suffixIcon: GestureDetector(
child: Semantics(
label: '清空',
child: Padding(
padding: const EdgeInsets.only(left: 16.0, top: 8.0, bottom: 8.0),
child: LoadAssetImage('poem/order_delete', color: iconColor),
padding:
EdgeInsets.only(left: 16.px, top: 8.px, bottom: 8.px),
child: LoadAssetImage('poem/poem_delete', color: iconColor),
),
),
onTap: () {
......@@ -151,13 +118,13 @@ class _SearchBarState extends State<SearchBar> {
);
final Widget search = MyButton(
minHeight: 32.0,
minWidth: 44.0,
minHeight: 32.px,
minWidth: 44.px,
fontSize: Dimens.font_sp14,
radius: 4.0,
padding: const EdgeInsets.symmetric(horizontal: 8.0),
radius: 4.px,
padding: EdgeInsets.symmetric(horizontal: 8.px),
text: '搜索',
onPressed:() {
onPressed: () {
_focus.unfocus();
widget.onPressed?.call(_controller.text);
},
......