完成Application-Main页面的一部分

This commit is contained in:
胡天 2023-07-21 18:15:53 +08:00
parent 6c2bc89f91
commit 69f3d9d64c
22 changed files with 767 additions and 11 deletions

View File

@ -2,8 +2,7 @@
const ProxyEnable = false;
/// IP
// const PROXY_IP = '192.168.1.105';
const ProxyIP = '172.16.43.74';
const ProxyIP = '172.31.163.87';
///
const ProxyPort = 8866;
const ProxyPort = 7890;

View File

@ -0,0 +1,31 @@
import 'dart:convert';
/// response
class CategoryResponse {
String code;
String title;
CategoryResponse({
required this.code,
required this.title,
});
factory CategoryResponse.fromRawJson(String str) => CategoryResponse.fromJson(json.decode(str));
String toRawJson() => json.encode(toJson());
factory CategoryResponse.fromJson(Map<String, dynamic> json) => CategoryResponse(
code: json["code"],
title: json["title"],
);
Map<String, dynamic> toJson() => {
"code": code,
"title": title,
};
@override
String toString() {
return "$title$code";
}
}

View File

@ -0,0 +1,27 @@
import 'dart:convert';
/// response
class ChannelResponse {
String code;
String title;
ChannelResponse({
required this.code,
required this.title,
});
factory ChannelResponse.fromRawJson(String str) => ChannelResponse.fromJson(json.decode(str));
String toRawJson() => json.encode(toJson());
factory ChannelResponse.fromJson(Map<String, dynamic> json) => ChannelResponse(
code: json["code"],
title: json["title"],
);
Map<String, dynamic> toJson() => {
"code": code,
"title": title,
};
}

131
lib/data/model/news.dart Normal file
View File

@ -0,0 +1,131 @@
/// request
class NewsPageListRequest {
String? categoryCode;
String? channelCode;
String? tag;
String? keyword;
int? pageNum;
int? pageSize;
NewsPageListRequest({
this.categoryCode,
this.channelCode,
this.tag,
this.keyword,
this.pageNum,
this.pageSize,
});
Map<String, dynamic> toJson() => {
"categoryCode": categoryCode,
"channelCode": channelCode,
"tag": tag,
"keyword": keyword,
"pageNum": pageNum,
"pageSize": pageSize,
};
}
/// response
class NewsPageListResponse {
int? counts;
int? pagesize;
int? pages;
int? page;
List<NewsItem>? items;
NewsPageListResponse({
this.counts,
this.pagesize,
this.pages,
this.page,
this.items,
});
factory NewsPageListResponse.fromJson(Map<String, dynamic> json) =>
NewsPageListResponse(
counts: json["counts"],
pagesize: json["pagesize"],
pages: json["pages"],
page: json["page"],
items: json["items"] == null
? []
: List<NewsItem>.from(
json["items"].map((x) => NewsItem.fromJson(x))),
);
Map<String, dynamic> toJson() => {
"counts": counts ?? 0,
"pagesize": pagesize ?? 0,
"pages": pages ?? 0,
"page": page ?? 0,
"items": items == null
? []
: List<dynamic>.from(items!.map((x) => x.toJson())),
};
}
class NewsItem {
String? id;
String? title;
String? category;
String? thumbnail;
String? author;
DateTime? addtime;
String? url;
NewsItem({
this.id,
this.title,
this.category,
this.thumbnail,
this.author,
this.addtime,
this.url,
});
factory NewsItem.fromJson(Map<String, dynamic> json) => NewsItem(
id: json["id"],
title: json["title"],
category: json["category"],
thumbnail: json["thumbnail"],
author: json["author"],
addtime: DateTime.parse(json["addtime"]),
url: json["url"],
);
Map<String, dynamic> toJson() => {
"id": id,
"title": title,
"category": category,
"thumbnail": thumbnail,
"author": author,
"addtime": addtime?.toIso8601String(),
"url": url,
};
}
/// request
class NewsRecommendRequest {
String? categoryCode;
String? channelCode;
String? tag;
String? keyword;
NewsRecommendRequest({
this.categoryCode,
this.channelCode,
this.tag,
this.keyword,
});
Map<String, dynamic> toJson() => {
"categoryCode": categoryCode,
"channelCode": channelCode,
"tag": tag,
"keyword": keyword,
};
}

61
lib/data/model/tag.dart Normal file
View File

@ -0,0 +1,61 @@
import 'dart:convert';
/// Request
class TagRequest {
String categoryCode;
String channelCode;
String tag;
String keyword;
String newsId;
TagRequest({
required this.categoryCode,
required this.channelCode,
required this.tag,
required this.keyword,
required this.newsId,
});
factory TagRequest.fromRawJson(String str) => TagRequest.fromJson(json.decode(str));
String toRawJson() => json.encode(toJson());
factory TagRequest.fromJson(Map<String, dynamic> json) => TagRequest(
categoryCode: json["categoryCode"],
channelCode: json["channelCode"],
tag: json["tag"],
keyword: json["keyword"],
newsId: json["newsID"],
);
Map<String, dynamic> toJson() => {
"categoryCode": categoryCode,
"channelCode": channelCode,
"tag": tag,
"keyword": keyword,
"newsID": newsId,
};
}
/// Response
class TagResponse {
String? tag;
TagResponse({
this.tag,
});
factory TagResponse.fromRawJson(String str) => TagResponse.fromJson(json.decode(str));
String toRawJson() => json.encode(toJson());
factory TagResponse.fromJson(Map<String, dynamic> json) => TagResponse(
tag: json["tag"],
);
Map<String, dynamic> toJson() => {
"tag": tag,
};
}

View File

@ -0,0 +1,83 @@
import 'package:news_getx/config/storage.dart';
import 'package:news_getx/data/model/categories.dart';
import 'package:news_getx/data/model/channels.dart';
import 'package:news_getx/data/model/news.dart';
import 'package:news_getx/data/model/tag.dart';
import 'package:news_getx/utils/http.dart';
/// API
class NewsAPI {
///
/// refresh
static Future<NewsPageListResponse> newsPageList({
NewsPageListRequest? params,
bool refresh = false,
bool cacheDisk = false,
}) async {
var response = await HttpUtil().get(
"/news",
queryParameters: params?.toJson(),
refresh: refresh,
cacheDisk: cacheDisk,
cacheKey: StorageIndexNewsCacheKey,
);
return NewsPageListResponse.fromJson(response);
}
///
static Future<NewsItem> newsRecommend({
NewsRecommendRequest? params,
bool refresh = false,
bool cacheDisk = false,
}) async {
var response = await HttpUtil().get(
'/news/recommend',
queryParameters: params?.toJson(),
refresh: refresh,
cacheDisk: cacheDisk,
);
return NewsItem.fromJson(response);
}
///
static Future<List<CategoryResponse>> categories({
bool cacheDisk = false,
}) async {
var response = await HttpUtil().get(
'/categories',
cacheDisk: cacheDisk,
);
return response
.map<CategoryResponse>((item) => CategoryResponse.fromJson(item))
.toList();
}
///
static Future<List<ChannelResponse>> channels({
bool cacheDisk = false,
}) async {
var response = await HttpUtil().get(
'/channels',
cacheDisk: cacheDisk,
);
return response
.map<ChannelResponse>((item) => ChannelResponse.fromJson(item))
.toList();
}
///
static Future<List<TagResponse>> tags({
TagRequest? params,
bool cacheDisk = false,
}) async {
var response = await HttpUtil().get(
'/tags',
queryParameters: params?.toJson(),
cacheDisk: cacheDisk,
);
return response
.map<TagResponse>((item) => TagResponse.fromJson(item))
.toList();
}
}

View File

@ -0,0 +1,51 @@
import 'package:news_getx/data/model/news.dart';
import 'package:news_getx/data/provider/news.dart';
import '../model/categories.dart';
import '../model/channels.dart';
import '../model/tag.dart';
class NewsRepository {
Future<NewsPageListResponse> newsPageList({
NewsPageListRequest? params,
bool refresh = false,
bool cacheDisk = false,
}) {
return NewsAPI.newsPageList(
params: params,
refresh: refresh,
cacheDisk: cacheDisk,
);
}
Future<NewsItem> newsRecommend({
NewsRecommendRequest? params,
bool refresh = false,
bool cacheDisk = false,
}) {
return NewsAPI.newsRecommend(
params: params,
refresh: refresh,
cacheDisk: cacheDisk,
);
}
Future<List<CategoryResponse>> categories({
bool cacheDisk = false,
}) {
return NewsAPI.categories(cacheDisk: cacheDisk);
}
Future<List<ChannelResponse>> channels({
bool cacheDisk = false,
}) {
return NewsAPI.channels(cacheDisk: cacheDisk);
}
Future<List<TagResponse>> tags({
TagRequest? params,
bool cacheDisk = false,
}) {
return NewsAPI.tags(params: params, cacheDisk: cacheDisk);
}
}

View File

@ -34,7 +34,8 @@ class MyApp extends StatelessWidget {
title: 'News',
debugShowCheckedModeBanner: false,
theme: AppTheme.light,
initialRoute: AppRoutes.Initial,
// initialRoute: AppRoutes.Initial,
initialRoute: AppRoutes.Application,
getPages: AppPages.pages,
builder: EasyLoading.init(),
),

View File

@ -1,6 +1,8 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:news_getx/modules/category/category_page.dart';
import 'package:news_getx/modules/main/main_page.dart';
import 'package:news_getx/modules/widgets/app_bar.dart';
import 'package:news_getx/theme/app_colors.dart';
@ -41,8 +43,8 @@ class ApplicationPage extends GetView<ApplicationController> {
controller: controller.pageController,
onPageChanged: controller.handlePageChange,
children: <Widget>[
Text('Main'),
Text('Category'),
MainPage(),
Text('Categories'),
Text('BookmarksPage'),
Text('AccountPage'),
],

View File

@ -0,0 +1,10 @@
import 'package:get/get.dart';
import 'category_controller.dart';
class CategoryBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut(() => CategoryController());
}
}

View File

@ -0,0 +1,5 @@
import 'package:get/get.dart';
class CategoryController extends GetxController {
}

View File

@ -0,0 +1,15 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'category_controller.dart';
class CategoryPage extends StatelessWidget {
const CategoryPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final controller = Get.find<CategoryController>();
return Container();
}
}

View File

@ -1,10 +1,13 @@
import 'package:get/get.dart';
import 'package:news_getx/data/repository/news_repository.dart';
import 'main_controller.dart';
class MainBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut(() => MainController());
Get.lazyPut(() => MainController(
newsRepository: NewsRepository(),
));
}
}

View File

@ -1,5 +1,56 @@
import 'package:get/get.dart';
import 'package:news_getx/data/model/news.dart';
import 'package:news_getx/data/repository/news_repository.dart';
import 'package:news_getx/modules/main/main_state.dart';
class MainController extends GetxController {
NewsRepository newsRepository;
MainController({required this.newsRepository});
///
/// Getx中Observable
/// Getx会自动将其所有属性都转换为可观察属性Observable
/// GetxController中定义的属性
///
/// GetxController都会监听它们的变化
final state = MainState();
///
//
asyncLoadAllData() async {
state.categories = await newsRepository.categories(cacheDisk: true);
state.channels = await newsRepository.channels(cacheDisk: true);
//
state.newsRecommend = await newsRepository.newsRecommend(cacheDisk: true);
state.newsPageList = await newsRepository.newsPageList(cacheDisk: true);
//
state.selCategoryCode = state.categories?.first.code;
}
//
asyncLoadNewsData(
categoryCode, {
bool refresh = false,
}) async {
state.selCategoryCode = categoryCode;
state.newsRecommend = await newsRepository.newsRecommend(
params: NewsRecommendRequest(categoryCode: categoryCode),
refresh: refresh,
cacheDisk: true,
);
state.newsPageList = await newsRepository.newsPageList(
params: NewsPageListRequest(categoryCode: categoryCode),
refresh: refresh,
cacheDisk: true,
);
}
///
@override
void onReady() {
super.onReady();
//
asyncLoadAllData();
}
}

View File

@ -1,15 +1,29 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:news_getx/modules/main/widgets/categories.dart';
import 'package:news_getx/modules/main/widgets/recommend.dart';
import 'main_controller.dart';
class MainPage extends StatelessWidget {
class MainPage extends GetView<MainController> {
const MainPage({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
final controller = Get.find<MainController>();
return Container();
return SingleChildScrollView(
child: Column(
children: <Widget>[
NewsCategoriesWidget(),
Divider(height: 1),
NewsRecommendWidget(),
Divider(height: 1),
Text('NewsChannelsWidget'),
Divider(height: 1),
Text('NewsListWidget'),
Divider(height: 1),
Text('NewsletterWidget'),
],
),
);
}
}

View File

@ -0,0 +1,47 @@
import 'package:get/get.dart';
import 'package:news_getx/data/model/categories.dart';
import 'package:news_getx/data/model/channels.dart';
import 'package:news_getx/data/model/news.dart';
class MainState {
//
var _categories = Rx<List<CategoryResponse>?>(null);
set categories(value) => _categories.value = value;
List<CategoryResponse>? get categories => _categories.value;
//
var _newsPageList = Rx<NewsPageListResponse?>(null);
set newsPageList(value) => _newsPageList.value = value;
NewsPageListResponse? get newsPageList => _newsPageList.value;
void appendNewsPageList(NewsPageListResponse value) {
if (_newsPageList.value != null) {
_newsPageList.value!.items?.addAll(value.items!.toList());
}
}
//
var _newsRecommend = Rx<NewsItem?>(null);
set newsRecommend(value) => _newsRecommend.value = value;
NewsItem? get newsRecommend => _newsRecommend.value;
//
var _channels = Rx<List<ChannelResponse>?>(null);
set channels(value) => _channels.value = value;
List<ChannelResponse>? get channels => _channels.value;
// Code
var _selCategoryCode = "".obs;
set selCategoryCode(value) => _selCategoryCode.value = value;
String get selCategoryCode => _selCategoryCode.value;
}

View File

@ -0,0 +1,47 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:news_getx/modules/main/main_controller.dart';
import 'package:news_getx/theme/app_colors.dart';
class NewsCategoriesWidget extends GetView<MainController> {
@override
Widget build(BuildContext context) {
// Row Tab
return Obx(
() => controller.state.categories == null
? Container()
: SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: controller.state.categories!.map<Widget>(
(item) {
return Container(
alignment: Alignment.center,
height: 52.h,
padding: EdgeInsets.symmetric(horizontal: 8),
child: GestureDetector(
onTap: () {
//
controller.asyncLoadNewsData(item.code);
},
child: Text(
item.title,
style: TextStyle(
color: controller.state.selCategoryCode == item.code
? AppColors.secondaryElementText
: AppColors.primaryText,
fontSize: 18.h,
fontFamily: 'Montserrat',
fontWeight: FontWeight.w600,
),
),
),
);
},
).toList(),
),
),
);
}
}

View File

@ -0,0 +1,106 @@
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:news_getx/modules/main/main_controller.dart';
import 'package:news_getx/modules/widgets/image.dart';
import 'package:news_getx/theme/app_colors.dart';
import 'package:news_getx/utils/date.dart';
class NewsRecommendWidget extends GetView<MainController> {
@override
Widget build(BuildContext context) {
print(controller.state.newsRecommend?.thumbnail);
return Obx(
() => controller.state.newsRecommend == null
? Container()
: Container(
margin: EdgeInsets.all(20.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
//
InkWell(
onTap: () {
print("进入详情页");
},
child: netImageCached(
controller.state.newsRecommend?.thumbnail ?? "",
width: 335.w,
height: 290.h,
),
),
//
Container(
margin: EdgeInsets.only(top: 14.h),
child: Text(
controller.state.newsRecommend!.author ?? "",
style: TextStyle(
fontFamily: 'Avenir',
fontWeight: FontWeight.normal,
color: AppColors.thirdElementText,
fontSize: 14.sp,
),
),
),
//
// 3
Container(
margin: EdgeInsets.only(top: 10.h),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 120),
child: Text(
controller.state.newsRecommend!.category ?? "",
style: TextStyle(
fontFamily: 'Avenir',
fontWeight: FontWeight.normal,
color: AppColors.secondaryElementText,
fontSize: 14.sp,
height: 1,
),
overflow: TextOverflow.clip,
maxLines: 1,
),
),
//
Container(
width: 15.w,
),
ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 120),
child: Text(
'${timeLineFormat(controller.state.newsRecommend!.addtime!)}',
style: TextStyle(
fontFamily: 'Avenir',
fontWeight: FontWeight.normal,
color: AppColors.thirdElementText,
fontSize: 14.sp,
height: 1,
),
overflow: TextOverflow.clip,
maxLines: 1,
),
),
//
Spacer(),
//
InkWell(
child: Icon(
Icons.more_horiz,
color: AppColors.primaryText,
size: 24,
),
onTap: () {},
),
],
),
),
],
),
),
);
}
}

View File

@ -0,0 +1,34 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:news_getx/theme/app_radii.dart';
///
Widget netImageCached(
String url, {
double width = 48,
double height = 48,
EdgeInsetsGeometry? margin,
}) {
return CachedNetworkImage(
imageUrl: url,
imageBuilder: (context, imageProvider) => Container(
height: height.h,
width: width.w,
margin: margin,
decoration: BoxDecoration(
borderRadius: Radii.k6pxRadius,
image: DecorationImage(
image: imageProvider,
fit: BoxFit.cover,
)),
),
placeholder: (context, url) {
return Container(
alignment: Alignment.center,
child: CircularProgressIndicator(),
);
},
errorWidget: (context, url, error) => Icon(Icons.error),
);
}

View File

@ -1,6 +1,9 @@
import 'package:get/get.dart';
import 'package:news_getx/modules/application/application_binding.dart';
import 'package:news_getx/modules/application/application_page.dart';
import 'package:news_getx/modules/category/category_binding.dart';
import 'package:news_getx/modules/category/category_page.dart';
import 'package:news_getx/modules/main/main_binding.dart';
import 'package:news_getx/modules/not_found/not_found_binding.dart';
import 'package:news_getx/modules/not_found/not_found_page.dart';
import 'package:news_getx/modules/sign_in/sign_in_binding.dart';
@ -40,6 +43,13 @@ abstract class AppPages {
name: AppRoutes.Application,
page: () => ApplicationPage(),
binding: ApplicationBinding(),
bindings: [MainBinding(), CategoryBinding()]
),
//
GetPage(
name: AppRoutes.Category,
page: () => CategoryPage(),
binding: CategoryBinding(),
),
];
}

27
lib/utils/date.dart Normal file
View File

@ -0,0 +1,27 @@
import 'package:intl/intl.dart';
///
String timeLineFormat(DateTime dt) {
var now = DateTime.now();
var difference = now.difference(dt);
// 1
if (difference.inHours < 24) {
return "${difference.inHours} hours ago";
}
// 30
else if (difference.inDays < 30) {
return "${difference.inDays} days ago";
}
// MM-dd
else if (difference.inDays < 365) {
final dtFormat = DateFormat('MM-dd');
return dtFormat.format(dt);
}
// yyyy-MM-dd
else {
final dtFormat = DateFormat('yyyy-MM-dd');
var str = dtFormat.format(dt);
return str;
}
}

View File

@ -21,6 +21,7 @@ class CacheObject {
class NetCache extends Interceptor {
// 使LinkedHashMap, {}
//
var cache = <String, CacheObject>{};
@override