From c47b5207dee6dfb7160579739a2ccf9f811c4bad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=83=A1=E5=A4=A9?= Date: Thu, 20 Jul 2023 18:24:10 +0800 Subject: [PATCH] =?UTF-8?q?=E5=AE=8C=E6=88=90Iconfont=E3=80=81=E5=BA=94?= =?UTF-8?q?=E7=94=A8=E9=A1=B5=E3=80=81=E7=BD=91=E7=BB=9C=E7=BC=93=E5=AD=98?= =?UTF-8?q?=E4=BB=A5=E5=8F=8A=E4=BB=A3=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/config/proxy.dart | 9 ++ lib/global.dart | 4 + .../application/application_binding.dart | 10 ++ .../application/application_controller.dart | 115 ++++++++++++++++++ lib/modules/application/application_page.dart | 74 +++++++++++ lib/modules/main/main_binding.dart | 10 ++ lib/modules/main/main_controller.dart | 5 + lib/modules/main/main_page.dart | 15 +++ lib/modules/widgets/app_bar.dart | 7 +- lib/routes/app_pages.dart | 8 ++ lib/utils/http.dart | 25 ++++ lib/utils/iconfont.dart | 94 ++++++++++++++ lib/utils/net_cache.dart | 90 ++++++++++++++ 13 files changed, 461 insertions(+), 5 deletions(-) create mode 100644 lib/config/proxy.dart create mode 100644 lib/modules/application/application_binding.dart create mode 100644 lib/modules/application/application_controller.dart create mode 100644 lib/modules/application/application_page.dart create mode 100644 lib/modules/main/main_binding.dart create mode 100644 lib/modules/main/main_controller.dart create mode 100644 lib/modules/main/main_page.dart create mode 100644 lib/utils/iconfont.dart create mode 100644 lib/utils/net_cache.dart diff --git a/lib/config/proxy.dart b/lib/config/proxy.dart new file mode 100644 index 0000000..ef9a530 --- /dev/null +++ b/lib/config/proxy.dart @@ -0,0 +1,9 @@ +// 是否启用代理 +const ProxyEnable = false; + +/// 代理服务IP +// const PROXY_IP = '192.168.1.105'; +const ProxyIP = '172.16.43.74'; + +/// 代理服务端口 +const ProxyPort = 8866; \ No newline at end of file diff --git a/lib/global.dart b/lib/global.dart index 30dac56..628cb38 100644 --- a/lib/global.dart +++ b/lib/global.dart @@ -13,7 +13,10 @@ class Global { WidgetsFlutterBinding.ensureInitialized(); await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); + // 调整系统UI setSystemUi(); + + // 初始换easyLoading Loading(); await Get.putAsync(() => StorageService().init()); @@ -24,6 +27,7 @@ class Global { static void setSystemUi() { if (GetPlatform.isAndroid) { + // android 状态栏为透明的沉浸 SystemUiOverlayStyle systemUiOverlayStyle = SystemUiOverlayStyle( statusBarColor: Colors.transparent, statusBarBrightness: Brightness.light, diff --git a/lib/modules/application/application_binding.dart b/lib/modules/application/application_binding.dart new file mode 100644 index 0000000..ab3e93f --- /dev/null +++ b/lib/modules/application/application_binding.dart @@ -0,0 +1,10 @@ +import 'package:get/get.dart'; + +import 'application_controller.dart'; + +class ApplicationBinding extends Bindings { + @override + void dependencies() { + Get.lazyPut(() => ApplicationController()); + } +} diff --git a/lib/modules/application/application_controller.dart b/lib/modules/application/application_controller.dart new file mode 100644 index 0000000..f983819 --- /dev/null +++ b/lib/modules/application/application_controller.dart @@ -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 tabTitles; + + // 页控制器 + late final PageController pageController; + + // 底部导航项目 + late final List 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( + 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(); + } +} diff --git a/lib/modules/application/application_page.dart b/lib/modules/application/application_page.dart new file mode 100644 index 0000000..df73b72 --- /dev/null +++ b/lib/modules/application/application_page.dart @@ -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 { + 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: [ + IconButton( + onPressed: () {}, + icon: Icon( + Icons.search, + color: AppColors.primaryText, + ), + ) + ], + ); + } + + Widget _buildPageView() { + return PageView( + // 禁止滚动 + physics: NeverScrollableScrollPhysics(), + controller: controller.pageController, + onPageChanged: controller.handlePageChange, + children: [ + 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(), + ); + } +} diff --git a/lib/modules/main/main_binding.dart b/lib/modules/main/main_binding.dart new file mode 100644 index 0000000..b34edf1 --- /dev/null +++ b/lib/modules/main/main_binding.dart @@ -0,0 +1,10 @@ +import 'package:get/get.dart'; + +import 'main_controller.dart'; + +class MainBinding extends Bindings { + @override + void dependencies() { + Get.lazyPut(() => MainController()); + } +} diff --git a/lib/modules/main/main_controller.dart b/lib/modules/main/main_controller.dart new file mode 100644 index 0000000..027fc9e --- /dev/null +++ b/lib/modules/main/main_controller.dart @@ -0,0 +1,5 @@ +import 'package:get/get.dart'; + +class MainController extends GetxController { + +} diff --git a/lib/modules/main/main_page.dart b/lib/modules/main/main_page.dart new file mode 100644 index 0000000..95ef0ba --- /dev/null +++ b/lib/modules/main/main_page.dart @@ -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(); + + return Container(); + } +} diff --git a/lib/modules/widgets/app_bar.dart b/lib/modules/widgets/app_bar.dart index 2c300ce..0225ce1 100644 --- a/lib/modules/widgets/app_bar.dart +++ b/lib/modules/widgets/app_bar.dart @@ -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, ); diff --git a/lib/routes/app_pages.dart b/lib/routes/app_pages.dart index 2fcfb82..6dcfdd7 100644 --- a/lib/routes/app_pages.dart +++ b/lib/routes/app_pages.dart @@ -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(), + ), ]; } diff --git a/lib/utils/http.dart b/lib/utils/http.dart index e8d41cd..f0cc2db 100644 --- a/lib/utils/http.dart +++ b/lib/utils/http.dart @@ -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( diff --git a/lib/utils/iconfont.dart b/lib/utils/iconfont.dart new file mode 100644 index 0000000..f2f90c1 --- /dev/null +++ b/lib/utils/iconfont.dart @@ -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, + ); +} diff --git a/lib/utils/net_cache.dart b/lib/utils/net_cache.dart new file mode 100644 index 0000000..fc0029c --- /dev/null +++ b/lib/utils/net_cache.dart @@ -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 = {}; + + @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); + } +}