完成Iconfont、应用页、网络缓存以及代理
This commit is contained in:
parent
756108fb8d
commit
c47b5207de
|
@ -0,0 +1,9 @@
|
|||
// 是否启用代理
|
||||
const ProxyEnable = false;
|
||||
|
||||
/// 代理服务IP
|
||||
// const PROXY_IP = '192.168.1.105';
|
||||
const ProxyIP = '172.16.43.74';
|
||||
|
||||
/// 代理服务端口
|
||||
const ProxyPort = 8866;
|
|
@ -13,7 +13,10 @@ class Global {
|
|||
WidgetsFlutterBinding.ensureInitialized();
|
||||
await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
|
||||
|
||||
// 调整系统UI
|
||||
setSystemUi();
|
||||
|
||||
// 初始换easyLoading
|
||||
Loading();
|
||||
|
||||
await Get.putAsync<StorageService>(() => StorageService().init());
|
||||
|
@ -24,6 +27,7 @@ class Global {
|
|||
|
||||
static void setSystemUi() {
|
||||
if (GetPlatform.isAndroid) {
|
||||
// android 状态栏为透明的沉浸
|
||||
SystemUiOverlayStyle systemUiOverlayStyle = SystemUiOverlayStyle(
|
||||
statusBarColor: Colors.transparent,
|
||||
statusBarBrightness: Brightness.light,
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
import 'package:get/get.dart';
|
||||
|
||||
import 'application_controller.dart';
|
||||
|
||||
class ApplicationBinding extends Bindings {
|
||||
@override
|
||||
void dependencies() {
|
||||
Get.lazyPut(() => ApplicationController());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,115 @@
|
|||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:news_getx/theme/app_colors.dart';
|
||||
import 'package:news_getx/utils/iconfont.dart';
|
||||
|
||||
class ApplicationController extends GetxController {
|
||||
/// 响应式成员变量
|
||||
// 当前 tab 页码
|
||||
final _page = 0.obs;
|
||||
|
||||
set page(value) => _page.value = value;
|
||||
|
||||
get page => _page.value;
|
||||
|
||||
/// 成员变量
|
||||
///
|
||||
// tab 页标题
|
||||
late final List<String> tabTitles;
|
||||
|
||||
// 页控制器
|
||||
late final PageController pageController;
|
||||
|
||||
// 底部导航项目
|
||||
late final List<BottomNavigationBarItem> bottomTabs;
|
||||
|
||||
/// 事件
|
||||
// tab栏动画
|
||||
void handleNavBarTap(int page) {
|
||||
pageController.animateToPage(
|
||||
page,
|
||||
duration: const Duration(seconds: 1),
|
||||
curve: Curves.ease,
|
||||
);
|
||||
}
|
||||
|
||||
// tab栏页码切换
|
||||
void handlePageChange(int newPage) {
|
||||
page = newPage;
|
||||
}
|
||||
|
||||
/// scheme 内部打开 TODO uriLink 通过地址打开程序指定页面
|
||||
bool isInitialUriIsHandled = false;
|
||||
StreamSubscription? uriSub;
|
||||
|
||||
/// 生命周期
|
||||
|
||||
@override
|
||||
void onInit() {
|
||||
super.onInit();
|
||||
|
||||
// 准备静态数据 作为tab
|
||||
tabTitles = ['Welcome', 'Cagegory', 'Bookmarks', 'Account'];
|
||||
bottomTabs = <BottomNavigationBarItem>[
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(
|
||||
Iconfont.home,
|
||||
color: AppColors.tabBarElement,
|
||||
),
|
||||
activeIcon: Icon(
|
||||
Iconfont.home,
|
||||
color: AppColors.secondaryElementText,
|
||||
),
|
||||
label: "main",
|
||||
backgroundColor: AppColors.primaryBackground,
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(
|
||||
Iconfont.grid,
|
||||
color: AppColors.tabBarElement,
|
||||
),
|
||||
activeIcon: Icon(
|
||||
Iconfont.grid,
|
||||
color: AppColors.secondaryElementText,
|
||||
),
|
||||
label: 'category',
|
||||
backgroundColor: AppColors.primaryBackground,
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(
|
||||
Iconfont.tag,
|
||||
color: AppColors.tabBarElement,
|
||||
),
|
||||
activeIcon: Icon(
|
||||
Iconfont.tag,
|
||||
color: AppColors.secondaryElementText,
|
||||
),
|
||||
label: 'tag',
|
||||
backgroundColor: AppColors.primaryBackground,
|
||||
),
|
||||
BottomNavigationBarItem(
|
||||
icon: Icon(
|
||||
Iconfont.me,
|
||||
color: AppColors.tabBarElement,
|
||||
),
|
||||
activeIcon: Icon(
|
||||
Iconfont.me,
|
||||
color: AppColors.secondaryElementText,
|
||||
),
|
||||
label: 'my',
|
||||
backgroundColor: AppColors.primaryBackground,
|
||||
),
|
||||
];
|
||||
|
||||
pageController = PageController(initialPage: page);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
uriSub?.cancel();
|
||||
pageController.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_screenutil/flutter_screenutil.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:news_getx/modules/widgets/app_bar.dart';
|
||||
import 'package:news_getx/theme/app_colors.dart';
|
||||
|
||||
import 'application_controller.dart';
|
||||
|
||||
class ApplicationPage extends GetView<ApplicationController> {
|
||||
const ApplicationPage({Key? key}) : super(key: key);
|
||||
|
||||
AppBar _buildAppBar() {
|
||||
return transparentAppBar(
|
||||
title: Obx(
|
||||
() => Text(
|
||||
controller.tabTitles[controller.page],
|
||||
style: TextStyle(
|
||||
color: AppColors.primaryText,
|
||||
fontFamily: "Montserrat",
|
||||
fontSize: 18.sp,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
actions: <Widget>[
|
||||
IconButton(
|
||||
onPressed: () {},
|
||||
icon: Icon(
|
||||
Icons.search,
|
||||
color: AppColors.primaryText,
|
||||
),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildPageView() {
|
||||
return PageView(
|
||||
// 禁止滚动
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
controller: controller.pageController,
|
||||
onPageChanged: controller.handlePageChange,
|
||||
children: <Widget>[
|
||||
Text('Main'),
|
||||
Text('Category'),
|
||||
Text('BookmarksPage'),
|
||||
Text('AccountPage'),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildBottomNavigationBar() {
|
||||
return Obx(
|
||||
() => BottomNavigationBar(
|
||||
items: controller.bottomTabs,
|
||||
currentIndex: controller.page,
|
||||
// 固定类型 导航栏中的所有项都会显示在屏幕上,无论有多少项,它们的宽度都会平均分配
|
||||
type: BottomNavigationBarType.fixed,
|
||||
onTap: controller.handleNavBarTap,
|
||||
showSelectedLabels: false,
|
||||
showUnselectedLabels: false,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: _buildAppBar(),
|
||||
body: _buildPageView(),
|
||||
bottomNavigationBar: _buildBottomNavigationBar(),
|
||||
);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
import 'package:get/get.dart';
|
||||
|
||||
import 'main_controller.dart';
|
||||
|
||||
class MainBinding extends Bindings {
|
||||
@override
|
||||
void dependencies() {
|
||||
Get.lazyPut(() => MainController());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
import 'package:get/get.dart';
|
||||
|
||||
class MainController extends GetxController {
|
||||
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:get/get.dart';
|
||||
|
||||
import 'main_controller.dart';
|
||||
|
||||
class MainPage extends StatelessWidget {
|
||||
const MainPage({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final controller = Get.find<MainController>();
|
||||
|
||||
return Container();
|
||||
}
|
||||
}
|
|
@ -10,11 +10,8 @@ AppBar transparentAppBar({
|
|||
return AppBar(
|
||||
backgroundColor: Colors.transparent,
|
||||
elevation: 0,
|
||||
title: title != null
|
||||
? Center(
|
||||
child: title,
|
||||
)
|
||||
: null,
|
||||
centerTitle: true,
|
||||
title: title,
|
||||
leading: leading,
|
||||
actions: actions,
|
||||
);
|
||||
|
|
|
@ -1,4 +1,6 @@
|
|||
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/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';
|
||||
|
@ -33,5 +35,11 @@ abstract class AppPages {
|
|||
page: () => NotFoundPage(),
|
||||
binding: NotFoundBinding(),
|
||||
),
|
||||
// 应用页
|
||||
GetPage(
|
||||
name: AppRoutes.Application,
|
||||
page: () => ApplicationPage(),
|
||||
binding: ApplicationBinding(),
|
||||
),
|
||||
];
|
||||
}
|
||||
|
|
|
@ -1,12 +1,18 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:dio/io.dart';
|
||||
import 'package:get/get.dart' hide FormData;
|
||||
import 'package:news_getx/config/cache.dart';
|
||||
import 'package:news_getx/config/proxy.dart';
|
||||
import 'package:news_getx/config/server.dart';
|
||||
import 'package:cookie_jar/cookie_jar.dart';
|
||||
import 'package:dio_cookie_manager/dio_cookie_manager.dart';
|
||||
import 'package:flutter_easyloading/flutter_easyloading.dart';
|
||||
import 'package:news_getx/data/services/config.dart';
|
||||
import 'package:news_getx/data/services/user.dart';
|
||||
import 'package:news_getx/utils/loading.dart';
|
||||
import 'package:news_getx/utils/net_cache.dart';
|
||||
|
||||
/*
|
||||
* http 操作类
|
||||
|
@ -56,10 +62,29 @@ class HttpUtil {
|
|||
|
||||
dio = Dio(options);
|
||||
|
||||
// 代理设置
|
||||
if (!ConfigService.to.isRelease && ProxyEnable) {
|
||||
dio.httpClientAdapter = IOHttpClientAdapter(
|
||||
createHttpClient: () {
|
||||
final client = HttpClient();
|
||||
client.findProxy = (uri) {
|
||||
return "PROXY $ProxyIP:$ProxyPort";
|
||||
};
|
||||
// 代理工具会提供一个抓包的自签名证书,会通不过证书校验,所以我们禁用证书校验
|
||||
client.badCertificateCallback = (X509Certificate cert, String host, int port) => true;
|
||||
return client;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// Cookie管理
|
||||
CookieJar cookieJar = CookieJar();
|
||||
// 增加拦截器CookieManager
|
||||
dio.interceptors.add(CookieManager(cookieJar));
|
||||
// 添加内存缓存
|
||||
if (CacheEnable) {
|
||||
dio.interceptors.add(NetCache());
|
||||
}
|
||||
|
||||
// 添加拦截器
|
||||
dio.interceptors.add(InterceptorsWrapper(
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
import 'package:flutter/material.dart';
|
||||
|
||||
class Iconfont {
|
||||
// iconName: share
|
||||
static const share = IconData(
|
||||
0xe60d,
|
||||
fontFamily: 'Iconfont',
|
||||
matchTextDirection: true,
|
||||
);
|
||||
|
||||
// iconName: fav
|
||||
static const fav = IconData(
|
||||
0xe60c,
|
||||
fontFamily: 'Iconfont',
|
||||
matchTextDirection: true,
|
||||
);
|
||||
|
||||
// iconName: social-linkedin
|
||||
static const sociallinkedin = IconData(
|
||||
0xe605,
|
||||
fontFamily: 'Iconfont',
|
||||
matchTextDirection: true,
|
||||
);
|
||||
|
||||
// iconName: social-apple
|
||||
static const socialapple = IconData(
|
||||
0xe606,
|
||||
fontFamily: 'Iconfont',
|
||||
matchTextDirection: true,
|
||||
);
|
||||
|
||||
// iconName: social-octocat
|
||||
static const socialoctocat = IconData(
|
||||
0xe607,
|
||||
fontFamily: 'Iconfont',
|
||||
matchTextDirection: true,
|
||||
);
|
||||
|
||||
// iconName: social-reddit
|
||||
static const socialreddit = IconData(
|
||||
0xe608,
|
||||
fontFamily: 'Iconfont',
|
||||
matchTextDirection: true,
|
||||
);
|
||||
|
||||
// iconName: social-snapchat
|
||||
static const socialsnapchat = IconData(
|
||||
0xe609,
|
||||
fontFamily: 'Iconfont',
|
||||
matchTextDirection: true,
|
||||
);
|
||||
|
||||
// iconName: social-skype
|
||||
static const socialskype = IconData(
|
||||
0xe60a,
|
||||
fontFamily: 'Iconfont',
|
||||
matchTextDirection: true,
|
||||
);
|
||||
|
||||
// iconName: social-twitter
|
||||
static const socialtwitter = IconData(
|
||||
0xe60b,
|
||||
fontFamily: 'Iconfont',
|
||||
matchTextDirection: true,
|
||||
);
|
||||
|
||||
// iconName: me
|
||||
static const me = IconData(
|
||||
0xe604,
|
||||
fontFamily: 'Iconfont',
|
||||
matchTextDirection: true,
|
||||
);
|
||||
|
||||
// iconName: tag
|
||||
static const tag = IconData(
|
||||
0xe603,
|
||||
fontFamily: 'Iconfont',
|
||||
matchTextDirection: true,
|
||||
);
|
||||
|
||||
// iconName: grid
|
||||
static const grid = IconData(
|
||||
0xe602,
|
||||
fontFamily: 'Iconfont',
|
||||
matchTextDirection: true,
|
||||
);
|
||||
|
||||
// iconName: home
|
||||
static const home = IconData(
|
||||
0xe601,
|
||||
fontFamily: 'Iconfont',
|
||||
matchTextDirection: true,
|
||||
);
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
import 'dart:collection';
|
||||
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:news_getx/config/cache.dart';
|
||||
|
||||
class CacheObject {
|
||||
Response response;
|
||||
int timestamp;
|
||||
|
||||
CacheObject(this.response)
|
||||
: timestamp = DateTime.now().millisecondsSinceEpoch;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return response.hashCode == other.hashCode;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => response.realUri.hashCode;
|
||||
}
|
||||
|
||||
class NetCache extends Interceptor {
|
||||
// 为确保迭代器顺序和对象插入时间一致顺序一致,我们使用LinkedHashMap, 默认的字面量{}就是有序的
|
||||
var cache = <String, CacheObject>{};
|
||||
|
||||
@override
|
||||
void onRequest(RequestOptions options, RequestInterceptorHandler handler) {
|
||||
// refresh标记是否是"下拉刷新"
|
||||
bool refresh = options.extra['refresh'] == true;
|
||||
if (refresh) {
|
||||
// 如果是下拉刷新,先删除相关缓存
|
||||
if (options.extra['list']) {
|
||||
//若是列表,则只要url中包含当前path的缓存全部删除(简单实现,并不精准)
|
||||
cache.removeWhere((key, value) => key.contains(options.path));
|
||||
} else {
|
||||
cacheDelete(options.uri.toString());
|
||||
}
|
||||
}
|
||||
|
||||
// get 请求,开启缓存
|
||||
if (options.extra['noCache'] != true &&
|
||||
options.method.toLowerCase() == "get") {
|
||||
String key = options.extra['cacheKey'] ?? options.uri.toString();
|
||||
var ob = cache[key];
|
||||
|
||||
if (ob != null) {
|
||||
//若缓存未过期,则返回缓存内容
|
||||
if ((DateTime.now().millisecondsSinceEpoch - ob.timestamp) / 1000 <
|
||||
CacheMaxAge) {
|
||||
CacheObject? cacheRes = cache[key];
|
||||
if (cacheRes != null) {
|
||||
handler.resolve(cacheRes.response);
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
//若已过期则删除缓存,继续向服务器请求
|
||||
cacheDelete(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
super.onRequest(options, handler);
|
||||
}
|
||||
|
||||
@override
|
||||
void onResponse(Response response, ResponseInterceptorHandler handler) {
|
||||
// 将返回结果保存到缓存
|
||||
_setCache(response);
|
||||
super.onResponse(response, handler);
|
||||
}
|
||||
|
||||
_setCache(Response response) {
|
||||
RequestOptions options = response.requestOptions;
|
||||
// 只缓存 get 的请求
|
||||
if (options.extra["noCache"] != true &&
|
||||
options.method.toLowerCase() == "get") {
|
||||
// 如果缓存数量超过最大数量限制,则先移除最早的一条记录
|
||||
if (cache.length >= CacheMaxCount) {
|
||||
cacheDelete(cache.keys.first);
|
||||
}
|
||||
|
||||
String key = options.extra['cacheKey'] ?? options.uri.toString();
|
||||
cache[key] = CacheObject(response);
|
||||
}
|
||||
}
|
||||
|
||||
void cacheDelete(String key) {
|
||||
cache.remove(key);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue