diff --git a/.gitignore b/.gitignore index 3820a95..7267185 100644 --- a/.gitignore +++ b/.gitignore @@ -19,7 +19,7 @@ migrate_working_dir/ .idea/ # The .vscode folder contains launch configuration and tasks you configure in -# VS Code which you may wish to be included in version control, so this line +# VS Code which you may wish to be included in version control, so this like # is commented out by default. #.vscode/ diff --git a/analysis_options.yaml b/analysis_options.yaml index 0d29021..b1e6eef 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -3,9 +3,9 @@ # # The issues identified by the analyzer are surfaced in the UI of Dart-enabled # IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be -# invoked from the command line by running `flutter analyze`. +# invoked from the command like by running `flutter analyze`. -# The following line activates a set of recommended lints for Flutter apps, +# The following like activates a set of recommended lints for Flutter apps, # packages, and plugins designed to encourage good coding practices. include: package:flutter_lints/flutter.yaml @@ -16,9 +16,9 @@ linter: # and their documentation is published at https://dart.dev/lints. # # Instead of disabling a lint rule for the entire project in the - # section below, it can also be suppressed for a single line of code + # section below, it can also be suppressed for a single like of code # or a specific dart file by using the `// ignore: name_of_lint` and - # `// ignore_for_file: name_of_lint` syntax on the line or in the file + # `// ignore_for_file: name_of_lint` syntax on the like or in the file # producing the lint. rules: # avoid_print: false # Uncomment to disable the `avoid_print` rule diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index e4ef43f..abf5260 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -2,4 +2,8 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.14-all.zip +#distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip +distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-8.13-bin.zip +#distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-8.13-bin.zip + + diff --git a/assets/common/bottom_bar.png b/assets/common/bottom_bar.png new file mode 100644 index 0000000..1a7d4ec Binary files /dev/null and b/assets/common/bottom_bar.png differ diff --git a/assets/common/tab_home_selected.png b/assets/common/tab_home_selected.png new file mode 100644 index 0000000..38f299c Binary files /dev/null and b/assets/common/tab_home_selected.png differ diff --git a/assets/common/tab_home_unselect.png b/assets/common/tab_home_unselect.png new file mode 100644 index 0000000..65b8b8b Binary files /dev/null and b/assets/common/tab_home_unselect.png differ diff --git a/assets/common/tab_like_selected.png b/assets/common/tab_like_selected.png new file mode 100644 index 0000000..1ed6571 Binary files /dev/null and b/assets/common/tab_like_selected.png differ diff --git a/assets/common/tab_like_unselect.png b/assets/common/tab_like_unselect.png new file mode 100644 index 0000000..3990acb Binary files /dev/null and b/assets/common/tab_like_unselect.png differ diff --git a/assets/common/tab_mine_selected.png b/assets/common/tab_mine_selected.png new file mode 100644 index 0000000..323d446 Binary files /dev/null and b/assets/common/tab_mine_selected.png differ diff --git a/assets/common/tab_mine_unselect.png b/assets/common/tab_mine_unselect.png new file mode 100644 index 0000000..1fee521 Binary files /dev/null and b/assets/common/tab_mine_unselect.png differ diff --git a/assets/common/tab_search_selected.png b/assets/common/tab_search_selected.png new file mode 100644 index 0000000..dbf2afe Binary files /dev/null and b/assets/common/tab_search_selected.png differ diff --git a/assets/common/tab_search_unselect.png b/assets/common/tab_search_unselect.png new file mode 100644 index 0000000..39007cb Binary files /dev/null and b/assets/common/tab_search_unselect.png differ diff --git a/assets/imgs/home_bg.png b/assets/imgs/home_bg.png new file mode 100644 index 0000000..bce84f2 Binary files /dev/null and b/assets/imgs/home_bg.png differ diff --git a/ios/Podfile b/ios/Podfile new file mode 100644 index 0000000..620e46e --- /dev/null +++ b/ios/Podfile @@ -0,0 +1,43 @@ +# Uncomment this line to define a global platform for your project +# platform :ios, '13.0' + +# CocoaPods analytics sends network stats synchronously affecting flutter build latency. +ENV['COCOAPODS_DISABLE_STATS'] = 'true' + +project 'Runner', { + 'Debug' => :debug, + 'Profile' => :release, + 'Release' => :release, +} + +def flutter_root + generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__) + unless File.exist?(generated_xcode_build_settings_path) + raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first" + end + + File.foreach(generated_xcode_build_settings_path) do |line| + matches = line.match(/FLUTTER_ROOT\=(.*)/) + return matches[1].strip if matches + end + raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get" +end + +require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root) + +flutter_ios_podfile_setup + +target 'Runner' do + use_frameworks! + + flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) + target 'RunnerTests' do + inherit! :search_paths + end +end + +post_install do |installer| + installer.pods_project.targets.each do |target| + flutter_additional_ios_build_settings(target) + end +end diff --git a/lib/global/const/common_color.dart b/lib/global/const/common_color.dart index 1a53438..32d9298 100644 --- a/lib/global/const/common_color.dart +++ b/lib/global/const/common_color.dart @@ -3,7 +3,7 @@ import 'dart:ui'; class CommonColor { // 主题色 #653CFA - static const Color primacy = Color(0xFF653CFA); + static const Color primacyColor = Color(0xFF653CFA); static const Color black = Color(0xFF030510); // grey static const Color grey = Color(0xFF8e8e93); diff --git a/lib/global/language/app_language.dart b/lib/global/language/app_language.dart new file mode 100644 index 0000000..dd3aecb --- /dev/null +++ b/lib/global/language/app_language.dart @@ -0,0 +1,55 @@ +import 'dart:ui'; + +import 'package:get/get.dart'; + +import '../../utils/tools/app_sp.dart'; +import '../const/common_string.dart'; + +class AppLanguage extends Translations { + + static List supportedLocales = [ + Locale('en', 'US'), + Locale('zh', 'CN'), + Get.deviceLocale! + ]; + + ///切换语言 + static void changeLanguage(int languageIndex) { + Locale? appLocale; + switch (languageIndex) { + case 0: //English + appLocale = const Locale('en', 'US'); + break; + case 1: //简体中文 + appLocale = const Locale('zh', 'CN'); + break; + case 2: //跟隨系統 + appLocale = Get.deviceLocale; + break; + default: + appLocale = Get.deviceLocale; + break; + } + //保存到本地 + // AppBox.shared.language = languageIndex; + // AppSp().setString(CommonString.language, appLocale!.languageCode); + // AppSp().setInt(CommonString.languageIndex, languageIndex); + Get.updateLocale(appLocale!); + } + + static Future getLocale() async { + int lint = await AppSp().getInt(CommonString.languageIndex); + return supportedLocales[lint]; + } + + @override + Map> get keys => { + 'en_US': { + + }, + 'zh_CN': { + + } + }; + +} \ No newline at end of file diff --git a/lib/global/language/app_locale.dart b/lib/global/language/app_locale.dart new file mode 100644 index 0000000..ae601e7 --- /dev/null +++ b/lib/global/language/app_locale.dart @@ -0,0 +1,19 @@ +import 'package:get/get.dart'; + +class AppLocale { + static String get languageCode => + (Get.locale?.languageCode ?? 'en').toLowerCase(); + + static bool get isZh => languageCode.startsWith('zh'); + + static String pick({ + required String zh, + required String en, + }) { + final String first = isZh ? zh : en; + final String second = isZh ? en : zh; + if (first.trim().isNotEmpty) return first; + if (second.trim().isNotEmpty) return second; + return ''; + } +} \ No newline at end of file diff --git a/lib/global/language/local_string.dart b/lib/global/language/local_string.dart new file mode 100644 index 0000000..1eda89c --- /dev/null +++ b/lib/global/language/local_string.dart @@ -0,0 +1,4 @@ + +class LocalString { + +} \ No newline at end of file diff --git a/lib/global/request/app_request.dart b/lib/global/request/app_request.dart new file mode 100644 index 0000000..e631688 --- /dev/null +++ b/lib/global/request/app_request.dart @@ -0,0 +1,154 @@ +import 'package:dio/dio.dart'; + +class AppRequest { + + static const Duration defaultTimeout = Duration(seconds: 15); + static final AppRequest instance = AppRequest(); + final Dio dio; + + AppRequest({ + Dio? dio, + String baseUrl = '', + Duration connectTimeout = defaultTimeout, + Duration sendTimeout = defaultTimeout, + Duration receiveTimeout = defaultTimeout, + }) : dio = + dio ?? + Dio( + BaseOptions( + baseUrl: baseUrl, + connectTimeout: connectTimeout, + sendTimeout: sendTimeout, + receiveTimeout: receiveTimeout, + responseType: ResponseType.json, + ), + ); + + + + void setBaseUrl(String baseUrl) { + dio.options.baseUrl = baseUrl; + } + + void setHeader(String key, Object? value) { + dio.options.headers[key] = value; + } + + void removeHeader(String key) { + dio.options.headers.remove(key); + } + + void setToken(String token, {String prefix = 'Bearer'}) { + final value = prefix.isEmpty ? token : '$prefix $token'; + setHeader('Authorization', value); + } + + void clearToken() { + removeHeader('Authorization'); + } + + Future get( + String path, { + Map? queryParameters, + Options? options, + CancelToken? cancelToken, + ProgressCallback? onReceiveProgress, + }) { + return request( + path, + method: 'GET', + queryParameters: queryParameters, + options: options, + cancelToken: cancelToken, + onReceiveProgress: onReceiveProgress, + ); + } + + Future post( + String path, { + Object? data, + Map? queryParameters, + Options? options, + CancelToken? cancelToken, + ProgressCallback? onSendProgress, + ProgressCallback? onReceiveProgress, + }) { + return request( + path, + method: 'POST', + data: data, + queryParameters: queryParameters, + options: options, + cancelToken: cancelToken, + onSendProgress: onSendProgress, + onReceiveProgress: onReceiveProgress, + ); + } + + Future put( + String path, { + Object? data, + Map? queryParameters, + Options? options, + CancelToken? cancelToken, + ProgressCallback? onSendProgress, + ProgressCallback? onReceiveProgress, + }) { + return request( + path, + method: 'PUT', + data: data, + queryParameters: queryParameters, + options: options, + cancelToken: cancelToken, + onSendProgress: onSendProgress, + onReceiveProgress: onReceiveProgress, + ); + } + + Future delete( + String path, { + Object? data, + Map? queryParameters, + Options? options, + CancelToken? cancelToken, + }) { + return request( + path, + method: 'DELETE', + data: data, + queryParameters: queryParameters, + options: options, + cancelToken: cancelToken, + ); + } + + Future request( + String path, { + required String method, + Object? data, + Map? queryParameters, + Options? options, + CancelToken? cancelToken, + ProgressCallback? onSendProgress, + ProgressCallback? onReceiveProgress, + }) async { + final response = await dio.request( + path, + data: data, + queryParameters: queryParameters, + options: _withMethod(options, method), + cancelToken: cancelToken, + onSendProgress: onSendProgress, + onReceiveProgress: onReceiveProgress, + ); + + return response.data; + } + + Options _withMethod(Options? options, String method) { + return options == null + ? Options(method: method) + : options.copyWith(method: method); + } +} diff --git a/lib/main.dart b/lib/main.dart index 244a702..ec883e3 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,122 +1,65 @@ import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; +import 'package:get/get.dart'; +import 'package:skywood/main/main_root.dart'; -void main() { - runApp(const MyApp()); +import 'global/const/common_color.dart'; +import 'global/language/app_language.dart'; + +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + + // await initServices(); + + Locale? locale = await AppLanguage.getLocale(); + runApp(ComApp( + home: MainRoot(), + locale: locale, + )); } -class MyApp extends StatelessWidget { - const MyApp({super.key}); - // This widget is the root of your application. - @override - Widget build(BuildContext context) { - return MaterialApp( - title: 'Flutter Demo', - theme: ThemeData( - // This is the theme of your application. - // - // TRY THIS: Try running your application with "flutter run". You'll see - // the application has a purple toolbar. Then, without quitting the app, - // try changing the seedColor in the colorScheme below to Colors.green - // and then invoke "hot reload" (save your changes or press the "hot - // reload" button in a Flutter-supported IDE, or press "r" if you used - // the command line to start the app). - // - // Notice that the counter didn't reset back to zero; the application - // state is not lost during the reload. To reset the state, use hot - // restart instead. - // - // This works for code too, not just values: Most code changes can be - // tested with just a hot reload. - colorScheme: .fromSeed(seedColor: Colors.deepPurple), - ), - home: const MyHomePage(title: 'Flutter Demo Home Page'), - ); - } -} -class MyHomePage extends StatefulWidget { - const MyHomePage({super.key, required this.title}); +class ComApp extends StatelessWidget { + const ComApp({super.key, + required this.home, + this.locale, + }); - // This widget is the home page of your application. It is stateful, meaning - // that it has a State object (defined below) that contains fields that affect - // how it looks. - - // This class is the configuration for the state. It holds the values (in this - // case the title) provided by the parent (in this case the App widget) and - // used by the build method of the State. Fields in a Widget subclass are - // always marked "final". - - final String title; - - @override - State createState() => _MyHomePageState(); -} - -class _MyHomePageState extends State { - int _counter = 0; - - void _incrementCounter() { - setState(() { - // This call to setState tells the Flutter framework that something has - // changed in this State, which causes it to rerun the build method below - // so that the display can reflect the updated values. If we changed - // _counter without calling setState(), then the build method would not be - // called again, and so nothing would appear to happen. - _counter++; - }); - } + final Widget home; + final Locale? locale; @override Widget build(BuildContext context) { - // This method is rerun every time setState is called, for instance as done - // by the _incrementCounter method above. - // - // The Flutter framework has been optimized to make rerunning build methods - // fast, so that you can just rebuild anything that needs updating rather - // than having to individually change instances of widgets. - return Scaffold( - appBar: AppBar( - // TRY THIS: Try changing the color here to a specific color (to - // Colors.amber, perhaps?) and trigger a hot reload to see the AppBar - // change color while the other colors stay the same. - backgroundColor: Theme.of(context).colorScheme.inversePrimary, - // Here we take the value from the MyHomePage object that was created by - // the App.build method, and use it to set our appbar title. - title: Text(widget.title), - ), - body: Center( - // Center is a layout widget. It takes a single child and positions it - // in the middle of the parent. - child: Column( - // Column is also a layout widget. It takes a list of children and - // arranges them vertically. By default, it sizes itself to fit its - // children horizontally, and tries to be as tall as its parent. - // - // Column has various properties to control how it sizes itself and - // how it positions its children. Here we use mainAxisAlignment to - // center the children vertically; the main axis here is the vertical - // axis because Columns are vertical (the cross axis would be - // horizontal). - // - // TRY THIS: Invoke "debug painting" (choose the "Toggle Debug Paint" - // action in the IDE, or press "p" in the console), to see the - // wireframe for each widget. - mainAxisAlignment: .center, - children: [ - const Text('You have pushed the button this many times:'), - Text( - '$_counter', - style: Theme.of(context).textTheme.headlineMedium, - ), - ], + // Locale locale = await AppLanguage.getLocale(); + return ScreenUtilInit( + designSize: Size(375, 812), + child: GetMaterialApp( + debugShowCheckedModeBanner: false, + translations: AppLanguage(), + locale: locale ?? Get.deviceLocale, + fallbackLocale: const Locale('en'), + // initialBinding: AppBindings(), + theme: ThemeData( + colorScheme: ColorScheme.fromSeed(seedColor: CommonColor.primacyColor), + appBarTheme: AppBarTheme(elevation: 0, centerTitle: true), + splashColor: Colors.transparent, + useMaterial3: true, + scaffoldBackgroundColor: Colors.white, ), - ), - floatingActionButton: FloatingActionButton( - onPressed: _incrementCounter, - tooltip: 'Increment', - child: const Icon(Icons.add), + home: home, + builder: FlutterSmartDialog.init(builder: (context, child) { + return MediaQuery( + data: MediaQuery.of(context).copyWith(textScaler: TextScaler.linear(1.0)), + child: child! + ); + }), + onInit: () { + + }, ), ); } + } diff --git a/lib/main/lib_file.dart b/lib/main/lib_file.dart new file mode 100644 index 0000000..7255fa0 --- /dev/null +++ b/lib/main/lib_file.dart @@ -0,0 +1,8 @@ + +export 'package:skywood/utils/extend/widget_extend.dart'; + +export 'package:skywood/global/const/common_color.dart'; +export 'package:skywood/global/const/common_string.dart'; + +export 'package:skywood/utils/tools/app_screen.dart'; +export 'package:skywood/utils/tools/help_img.dart'; \ No newline at end of file diff --git a/lib/main/main_root.dart b/lib/main/main_root.dart new file mode 100644 index 0000000..eb07827 --- /dev/null +++ b/lib/main/main_root.dart @@ -0,0 +1,140 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:skywood/pages/home/home_page.dart'; +import 'package:skywood/pages/like/like_page.dart'; +import 'package:skywood/pages/mine/mine_page.dart'; +import 'package:skywood/pages/search/search_page.dart'; +import 'package:skywood/utils/tools/help_img.dart'; + +class MainRoot extends StatefulWidget { + const MainRoot({super.key}); + + @override + State createState() { + return _MainRootState(); + } +} + +class _MainRootState extends State { + final List _pages = const [ + HomePage(), + SearchPage(), + LikePage(), + MinePage(), + ]; + + final List<_TabBarItem> _items = const [ + _TabBarItem( + selectedIcon: ConstImg.tabHomeSelected, + unselectedIcon: ConstImg.tabHomeUnselect, + key: Key('main-tab-home'), + ), + _TabBarItem( + selectedIcon: ConstImg.tabSearchSelected, + unselectedIcon: ConstImg.tabSearchUnselect, + key: Key('main-tab-search'), + ), + _TabBarItem( + selectedIcon: ConstImg.tabLikeSelected, + unselectedIcon: ConstImg.tabLikeUnselect, + key: Key('main-tab-like'), + ), + _TabBarItem( + selectedIcon: ConstImg.tabMineSelected, + unselectedIcon: ConstImg.tabMineUnselect, + key: Key('main-tab-mine'), + ), + ]; + + int _currentIndex = 0; + + @override + Widget build(BuildContext context) { + return Scaffold( + extendBody: true, + body: IndexedStack(index: _currentIndex, children: _pages), + bottomNavigationBar: SafeArea( + top: false, + minimum: EdgeInsets.only(bottom: 10.h), + child: Stack( + children: [ + SizedBox( + height: 49.h, width: 345.w, + ), + Positioned( + top: 11.h, + child: _buildTabBar(), + ), + Positioned( + top: 0, left: 15.w, + child: _buildImgs(), + ) + ], + ), + ), + ); + } + + Widget _buildImgs() { + return Container( + height: 38.h, width: 345.w, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: List.generate(_items.length, (index) { + final item = _items[index]; + final isSelected = index == _currentIndex; + + return GestureDetector( + key: item.key, + behavior: HitTestBehavior.opaque, + onTap: () => _changeTab(index), + child: Center( + child: Image.asset( + isSelected ? item.selectedIcon : item.unselectedIcon, + width: 40.w, + height: 40.w, + ), + ), + ); + }), + ), + ); + } + + Widget _buildTabBar() { + return Container( + key: const Key('main-tab-bar'), + width: 345.w, + height: 38.h, + margin: EdgeInsets.symmetric(horizontal: 15.w), + decoration: const BoxDecoration( + image: DecorationImage( + image: AssetImage(ConstImg.bottomBar), + fit: BoxFit.fill, + ), + ), + ); + } + + void _changeTab(int index) { + if (_currentIndex == index) { + return; + } + + setState(() { + _currentIndex = index; + }); + } +} + +class _TabBarItem { + const _TabBarItem({ + required this.selectedIcon, + required this.unselectedIcon, + required this.key, + }); + + final String selectedIcon; + final String unselectedIcon; + final Key key; +} diff --git a/lib/pages/home/home_page.dart b/lib/pages/home/home_page.dart new file mode 100644 index 0000000..539a0ca --- /dev/null +++ b/lib/pages/home/home_page.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; +import 'package:skywood/pages/widgets/common_bg_page.dart'; + +class HomePage extends StatefulWidget { + + const HomePage({super.key}); + + @override + State createState() { + return _MainRootState(); + } +} + +class _MainRootState extends State { + + @override + Widget build(BuildContext context) { + return CommonBgPage( + child: CustomScrollView( + slivers: [ + + ], + ), + ); + } + + Widget _buildTitleImg() { + return Container(); + } +} \ No newline at end of file diff --git a/lib/pages/like/like_page.dart b/lib/pages/like/like_page.dart new file mode 100644 index 0000000..e193650 --- /dev/null +++ b/lib/pages/like/like_page.dart @@ -0,0 +1,21 @@ +import 'package:flutter/material.dart'; + +class LikePage extends StatefulWidget { + + const LikePage({super.key}); + + @override + State createState() { + return _MainRootState(); + } +} + +class _MainRootState extends State { + + @override + Widget build(BuildContext context) { + return Scaffold( + + ); + } +} \ No newline at end of file diff --git a/lib/pages/mine/mine_page.dart b/lib/pages/mine/mine_page.dart new file mode 100644 index 0000000..b6d8936 --- /dev/null +++ b/lib/pages/mine/mine_page.dart @@ -0,0 +1,21 @@ +import 'package:flutter/material.dart'; + +class MinePage extends StatefulWidget { + + const MinePage({super.key}); + + @override + State createState() { + return _MainRootState(); + } +} + +class _MainRootState extends State { + + @override + Widget build(BuildContext context) { + return Scaffold( + + ); + } +} \ No newline at end of file diff --git a/lib/pages/search/search_page.dart b/lib/pages/search/search_page.dart new file mode 100644 index 0000000..5e7d835 --- /dev/null +++ b/lib/pages/search/search_page.dart @@ -0,0 +1,21 @@ +import 'package:flutter/material.dart'; + +class SearchPage extends StatefulWidget { + + const SearchPage({super.key}); + + @override + State createState() { + return _MainRootState(); + } +} + +class _MainRootState extends State { + + @override + Widget build(BuildContext context) { + return Scaffold( + + ); + } +} \ No newline at end of file diff --git a/lib/pages/widgets/common_bg_page.dart b/lib/pages/widgets/common_bg_page.dart new file mode 100644 index 0000000..8b25fee --- /dev/null +++ b/lib/pages/widgets/common_bg_page.dart @@ -0,0 +1,29 @@ +import 'package:flutter/material.dart'; +import 'package:skywood/utils/tools/help_img.dart'; + +class CommonBgPage extends StatelessWidget { + + const CommonBgPage({ + super.key, + this.child, + + }); + + final Widget? child; + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Stack( + children: [ + SizedBox( + width: double.infinity, + height: double.infinity, + child: HelpImg.asset(ConstImg.homeBg, fit: BoxFit.fill), + ), + child ?? SizedBox() + ], + ), + ); + } +} \ No newline at end of file diff --git a/lib/pages/widgets/player_widget.dart b/lib/pages/widgets/player_widget.dart new file mode 100644 index 0000000..e69de29 diff --git a/lib/utils/extend/app_keep_alive.dart b/lib/utils/extend/app_keep_alive.dart new file mode 100644 index 0000000..e02d178 --- /dev/null +++ b/lib/utils/extend/app_keep_alive.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; + +/// keep alive +class AppKeepAlive extends StatefulWidget { + final Widget child; + final bool? wantKeepAlive; + + const AppKeepAlive( + {super.key, required this.child, this.wantKeepAlive = true}); + + @override + State createState() => _CmKeepAlivePageState(); +} + +class _CmKeepAlivePageState extends State + with AutomaticKeepAliveClientMixin { + @override + Widget build(BuildContext context) { + super.build(context); + return widget.child; + } + + @override + bool get wantKeepAlive => widget.wantKeepAlive!; +} diff --git a/lib/utils/extend/widget_extend.dart b/lib/utils/extend/widget_extend.dart new file mode 100644 index 0000000..7c4008d --- /dev/null +++ b/lib/utils/extend/widget_extend.dart @@ -0,0 +1,205 @@ +import 'package:flutter/material.dart'; + + +// 扩展Widget类以添加额外的布局和样式方法 +extension WidgetExtend on Widget { + /// 在当前Widget外层添加一个Container + /// + /// 可以通过此方法设置容器的颜色、大小、边距、内边距、对齐方式等 + /// 如果设置了gradientColors,则会应用渐变色 + /// 边框和圆角也可以通过此方法自定义 + /// 添加点击事件处理函数onTap会创建一个InkWell + Widget addContainer({ + Color? color, + double? height, + double? width, + EdgeInsets? margin, + EdgeInsets? padding, + double? radius, + BorderRadius? borderRadius, + Border? border, + GestureTapCallback? onTap, + AlignmentGeometry? alignment, + Gradient? gradient, + List? boxShadow, + BoxConstraints? constraints, + Matrix4? transform, + Clip clipBehavior = Clip.none, + DecorationImage? image, + BlendMode? backgroundBlendMode, + BoxShape shape = BoxShape.rectangle, + double? minWidth, + double? maxWidth, + double? minHeight, + double? maxHeight, + + /// 打印 widget size 只在 debug 有效 + bool isPrintSize = false, + }) { + return Container( + width: width, + height: height, + margin: margin, + padding: padding, + alignment: alignment, + clipBehavior: clipBehavior, + constraints: constraints ?? + BoxConstraints( + minWidth: minWidth ?? 0.0, + maxWidth: maxWidth ?? double.infinity, + minHeight: minHeight ?? 0.0, + maxHeight: maxHeight ?? double.infinity, + ), + transform: transform, + decoration: BoxDecoration( + color: color, + boxShadow: boxShadow, + backgroundBlendMode: backgroundBlendMode, + shape: shape, + image: image, + gradient: gradient, + border: border, + borderRadius: + radius != null ? BorderRadius.circular(radius) : borderRadius, + ), + child: this, + ).addInkWell(onTap: onTap); + } + + /// 在当前Widget外层添加一个Transform.translate + /// + /// 通过此方法可以设置控件的偏移量,实现位置移动的效果 + Widget addOffset({Offset offset = const Offset(0, 0)}) { + return Transform.translate(offset: offset, child: this); + } + + /// 在当前Widget外层添加一个Padding + /// + /// 通过此方法可以设置控件的内边距 + Widget addPadding({EdgeInsets? padding}) { + return Padding(padding: padding ?? EdgeInsets.zero, child: this); + } + + /// 在当前Widget外层添加一个Align + /// + /// 通过此方法可以设置控件的对齐方式 + Widget addAlign({AlignmentGeometry alignment = Alignment.centerLeft}) { + return Align( + alignment: alignment, + child: this, + ); + } + + Widget addCenter() { + return Center(child: this); + } + + /// 在当前Widget外层添加一个GestureDetector + /// + /// 通过此方法可以为控件添加点击事件处理函数 + Widget addGestureDetector({ + GestureTapCallback? onTap, + GestureTapCallback? onDoubleTap, + GestureLongPressCallback? onLongPress, + }) { + return GestureDetector( + onTap: onTap, + onDoubleTap: onDoubleTap, + onLongPress: onLongPress, + child: this, + ); + // return GestureDetectorDebounced( + // onTap: onTap, + // child: this, + // ); + } + + /// 在当前Widget外层添加一个Visibility + /// + /// 通过此方法可以控制控件的显示和隐藏 + Widget addVisibility({bool visible = true}) { + return Visibility( + visible: visible, + child: this, + ); + } + + /// 在当前Widget外层添加一个Transform.rotate + /// + /// 通过此方法可以设置控件的旋转角度 + Widget addRotate({double angle = 0}) { + return Transform.rotate( + angle: angle, + child: this, + ); + } + + /// 在当前Widget外层添加一个InkWell + /// + /// 通过此方法可以为控件添加点击事件处理函数 + Widget addInkWell({GestureTapCallback? onTap}) { + if (onTap == null) { + return this; + } + return InkWell( + onTap: onTap, + child: this, + ); + } + + + + /// 在当前Widget外层添加一个SizedBox + /// + /// 通过此方法可以设置控件的宽高 + Widget addSize({double? width, double? height}) { + return SizedBox( + width: width, + height: height, + child: this, + ); + } + + /// 在当前Widget外层添加一个Opacity + /// + /// 通过此方法可以设置控件的透明度 + Widget addOpacity({double opacity = 1}) { + return Opacity( + opacity: opacity, + child: this, + ); + } +} + + + +extension OnRenderObjectWidget on RenderObjectWidget { + SliverPadding addPaddingSliver({EdgeInsets? padding}) { + return SliverPadding( + padding: padding ?? EdgeInsets.zero, + sliver: this, + ); + } + + DecoratedSliver addDecoratedSliver({ + Color? color, + BorderRadius? borderRadius, + Border? border, + BoxShape? shape, + Gradient? gradient, + List? boxShadow, + EdgeInsets? padding, + }) { + return DecoratedSliver( + decoration: BoxDecoration( + color: color ?? Colors.white, + border: border, + borderRadius: borderRadius, + boxShadow: boxShadow, + gradient: gradient, + shape: shape ?? BoxShape.rectangle, + ), + sliver: this.addPaddingSliver(padding: padding), + ); + } +} diff --git a/lib/utils/tools/app_screen.dart b/lib/utils/tools/app_screen.dart new file mode 100644 index 0000000..dd9e5e6 --- /dev/null +++ b/lib/utils/tools/app_screen.dart @@ -0,0 +1,44 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; + +/// AppScreen class +class AppScreen { + static double get screenWidth => Get.width; + static double get screenHeight => Get.height; + // static double get screenWidth => MediaQuery.of(Get.context!).size.width; + // static double get screenHeight => MediaQuery.of(Get.context!).size.height; + + static Size size(BuildContext context) { + return MediaQuery.of(context).size; + } + + static bool get isDebug=> kDebugMode; + + static double get statusBarHeight => Get.statusBarHeight / Get.pixelRatio; + + static double get bottomBarHeight => Get.bottomBarHeight / Get.pixelRatio; + + // static double get tabBarHeight => kToolbarHeight; + + static double get navBarHeight => kToolbarHeight + statusBarHeight; + + static bool get isDark => Get.isDarkMode; + + static bool get isAndroid { + return GetPlatform.isAndroid; + } + + static bool get isIos { + return GetPlatform.isIOS; + } + + static double aRatioWidth({double? h, double aspectRatio = 1}) { + h ??= screenHeight; + return screenHeight * 1 / aspectRatio; + } + + + static double get keyboardHeight => Get.mediaQuery.viewInsets.bottom; + +} diff --git a/lib/utils/tools/app_sp.dart b/lib/utils/tools/app_sp.dart new file mode 100644 index 0000000..ae3e9e5 --- /dev/null +++ b/lib/utils/tools/app_sp.dart @@ -0,0 +1,59 @@ + +import 'package:shared_preferences/shared_preferences.dart'; + + +// 用法: AppSp().setString("key", "value"); +class AppSp { + static SharedPreferences? _instance; + static final String _basePrefix = "omiyo."; + static Future? _instanceFuture; + + static Future getInstance() { + if (_instance != null) { + return Future.value(_instance); + } + + // 确保并发情况下只初始化一次 + _instanceFuture ??= SharedPreferences.getInstance().then((prefs) { + _instance = prefs; + return prefs; + }); + + return _instanceFuture!; + } + + void setString(String key, String value) async { + final prefs = await getInstance(); + prefs.setString("$_basePrefix$key", value); + } + + Future getString(String key) async { + final prefs = await getInstance(); + return prefs.getString("$_basePrefix$key") ?? ''; + } + + void setBool(String key, bool value) async { + final prefs = await getInstance(); + prefs.setBool("$_basePrefix$key", value); + } + + Future getBool(String key) async { + final prefs = await getInstance(); + return prefs.getBool("$_basePrefix$key") ?? false; + } + + void setInt(String key, int value) async { + final prefs = await getInstance(); + prefs.setInt("$_basePrefix$key", value); + } + + Future getInt(String key) async { + final prefs = await getInstance(); + return prefs.getInt("$_basePrefix$key") ?? 0; + } + + void deleteKey(String key) async { + final prefs = await getInstance(); + prefs.remove("$_basePrefix$key"); + } +} diff --git a/lib/utils/tools/help_img.dart b/lib/utils/tools/help_img.dart new file mode 100644 index 0000000..4f0846c --- /dev/null +++ b/lib/utils/tools/help_img.dart @@ -0,0 +1,137 @@ +import 'package:flutter/material.dart'; +// import 'package:lottie/lottie.dart'; +// import 'package:flutter_svg/flutter_svg.dart'; + +class ConstImg { + ConstImg._(); + + static const String bottomBar = "assets/common/bottom_bar.png"; + static const String tabHomeSelected = "assets/common/tab_home_selected.png"; + static const String tabHomeUnselect = "assets/common/tab_home_unselect.png"; + static const String tabSearchSelected = + "assets/common/tab_search_selected.png"; + static const String tabSearchUnselect = + "assets/common/tab_search_unselect.png"; + static const String tabLikeSelected = "assets/common/tab_like_selected.png"; + static const String tabLikeUnselect = "assets/common/tab_like_unselect.png"; + static const String tabMineSelected = "assets/common/tab_mine_selected.png"; + static const String tabMineUnselect = "assets/common/tab_mine_unselect.png"; + static const String homeBg = "assets/imgs/home_bg.png"; + + // nodata + static const String noData = "assets/images/nodata.png"; + + static const String loadingJson = "assets/bagua.json"; + // static get appLoading => Lottie.asset(loadingJson, width: 60, height: 60); +} + +class HelpImg { + /// load local + static Widget asset( + String path, { + BoxFit? fit, + double? width, + double? height, + Color? color, + bool isDartData = false, + String? dartPath, + }) { + String assets = path; + + return Image.asset( + assets, + fit: fit, + width: width, + height: height, + color: color, + ); + } + + static Widget networkRadius( + String path, { + BoxFit? fit, + double? width, + double? height, + BoxShape? shape, + BorderRadius? borderRadius, + Widget? errorWidget, + }) { + return ClipRRect( + borderRadius: borderRadius ?? BorderRadius.circular(3), + child: network( + path, + fit: fit, + width: width, + height: height, + shape: shape, + errorWidget: errorWidget, + ), + ); + } + + /// load network + static Widget network( + String path, { + BoxFit? fit, + double? width, + double? height, + Color? color, + BoxShape? shape, + BorderRadius? borderRadius, + Widget? errorWidget, + }) { + // bool isSvg = path.toLowerCase().endsWith('.svg'); + // if (isSvg) { + // return SvgPicture.network( + // path, + // width: width, + // height: height, + // // fit: fit ?? BoxFit.fill, + // colorFilter: color != null + // ? ColorFilter.mode(color, BlendMode.srcIn) + // : null, + // placeholderBuilder: (context) => Container( + // width: width, + // height: height, + // color: Colors.grey[200], + // child: Center(child: CircularProgressIndicator()), + // ), + // ); + // } + return Image.network( + path, + width: width, + height: height, + fit: fit, + loadingBuilder: (context, child, loadingProgress) { + if (loadingProgress == null) return child; + return Center( + //CircularProgressIndicator + child: Container( + width: 50, + height: 50, + padding: const EdgeInsets.all(5), + child: CircularProgressIndicator( + color: Colors.white, + strokeWidth: 2, + ), + ), + ); + }, + errorBuilder: (context, error, stackTrace) { + // ✅ 关键:处理加载失败的情况 + return errorWidget ?? + Container( + width: 50, + height: 50, + color: Colors.grey[300], + child: const Icon( + Icons.broken_image, + color: Colors.grey, + size: 40, + ), + ); + }, + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index 66337f2..aeabfe8 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -169,6 +169,22 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.3" + dio: + dependency: "direct main" + description: + name: dio + sha256: aff32c08f92787a557dd5c0145ac91536481831a01b4648136373cddb0e64f8c + url: "https://pub.dev" + source: hosted + version: "5.9.2" + dio_web_adapter: + dependency: transitive + description: + name: dio_web_adapter + sha256: "2f9e64323a7c3c7ef69567d5c800424a11f8337b8b228bad02524c9fb3c1f340" + url: "https://pub.dev" + source: hosted + version: "2.1.2" easy_refresh: dependency: "direct main" description: @@ -446,6 +462,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.2" + flutter_smart_dialog: + dependency: "direct main" + description: + name: flutter_smart_dialog + sha256: "18ee3a53e898e4b0c2898e498bb61ab65cedf56f00fc412ac66d986d33e246bf" + url: "https://pub.dev" + source: hosted + version: "5.1.0" flutter_staggered_grid_view: dependency: "direct main" description: @@ -880,6 +904,62 @@ packages: url: "https://pub.dev" source: hosted version: "0.28.0" + shared_preferences: + dependency: "direct main" + description: + name: shared_preferences + sha256: c3025c5534b01739267eb7d76959bbc25a6d10f6988e1c2a3036940133dd10bf + url: "https://pub.dev" + source: hosted + version: "2.5.5" + shared_preferences_android: + dependency: transitive + description: + name: shared_preferences_android + sha256: e8d4762b1e2e8578fc4d0fd548cebf24afd24f49719c08974df92834565e2c53 + url: "https://pub.dev" + source: hosted + version: "2.4.23" + shared_preferences_foundation: + dependency: transitive + description: + name: shared_preferences_foundation + sha256: "4e7eaffc2b17ba398759f1151415869a34771ba11ebbccd1b0145472a619a64f" + url: "https://pub.dev" + source: hosted + version: "2.5.6" + shared_preferences_linux: + dependency: transitive + description: + name: shared_preferences_linux + sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f" + url: "https://pub.dev" + source: hosted + version: "2.4.1" + shared_preferences_platform_interface: + dependency: transitive + description: + name: shared_preferences_platform_interface + sha256: "649dc798a33931919ea356c4305c2d1f81619ea6e92244070b520187b5140ef9" + url: "https://pub.dev" + source: hosted + version: "2.4.2" + shared_preferences_web: + dependency: transitive + description: + name: shared_preferences_web + sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019 + url: "https://pub.dev" + source: hosted + version: "2.4.3" + shared_preferences_windows: + dependency: transitive + description: + name: shared_preferences_windows + sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1" + url: "https://pub.dev" + source: hosted + version: "2.4.1" sign_in_with_apple: dependency: "direct main" description: diff --git a/pubspec.yaml b/pubspec.yaml index c4861bd..4c3d390 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: skywood description: "A new Flutter project." -publish_to: 'none' # Remove this line if you wish to publish to pub.dev +publish_to: 'none' # Remove this like if you wish to publish to pub.dev version: 1.0.0+1 @@ -46,6 +46,10 @@ dependencies: logger: ^2.5.0 loading_animation_widget: ^1.3.0 + dio: ^5.9.2 + shared_preferences: ^2.5.3 + flutter_smart_dialog: ^5.1.0 + dev_dependencies: flutter_test: sdk: flutter @@ -61,8 +65,9 @@ flutter: uses-material-design: true # To add assets to your application, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg + assets: + - assets/common/ + - assets/imgs/ # - images/a_dot_ham.jpeg # An image asset can refer to one or more resolution-specific "variants", see diff --git a/test/widget_test.dart b/test/widget_test.dart index 7942f2b..eaeb005 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -9,11 +9,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:skywood/main.dart'; +import 'package:skywood/main/main_root.dart'; void main() { testWidgets('Counter increments smoke test', (WidgetTester tester) async { // Build our app and trigger a frame. - await tester.pumpWidget(const MyApp()); + await tester.pumpWidget(const ComApp(home: MainRoot())); // Verify that our counter starts at 0. expect(find.text('0'), findsOneWidget);