提交 5345b482 authored 作者: 王鹏飞's avatar 王鹏飞

chore: update

上级 e70c4011
......@@ -48,3 +48,7 @@ app.*.map.json
# fvm
.fvm/
# App Store Connect / iOS signing secrets
.asc/
signing/
# Repository Guidelines
## Project Structure & Module Organization
This is a Flutter mobile app for Android and iOS. Application code lives in `lib/`:
- `lib/main.dart` and `lib/global.dart`: startup and global service initialization.
- `lib/pages/`: feature pages and controllers.
- `lib/routes/`: GoRouter configuration and route observers.
- `lib/services/`, `lib/apis/`, `lib/models/`: HTTP, API wrappers, and typed data models.
- `lib/store/`: GetX stores for user/session and configuration state.
- `lib/widgets/`, `lib/utils/`: shared UI components and helpers.
Assets are under `assets/`, including the embedded reader HTML in `assets/html/`. Native projects are in `android/` and `ios/`. Tests are in `test/`.
## Build, Test, and Development Commands
Use Flutter SDK `3.29.3` for this repository.
```bash
flutter pub get
```
Install Dart and Flutter dependencies.
```bash
cd ios && pod install && cd ..
flutter run -d ios
flutter run -d android
```
Install iOS pods, then run on iOS or Android.
```bash
flutter analyze
flutter test
```
Run static analysis and widget/unit tests.
```bash
flutter build apk --release
flutter build ios --release --no-codesign
flutter build ipa --release --export-options-plist=ios/ExportOptions.plist
```
Build Android, unsigned iOS, or App Store IPA artifacts.
## Coding Style & Naming Conventions
Follow Dart style with 2-space indentation and `flutter_lints` from `analysis_options.yaml`. Use `dart format` before larger changes. Prefer existing patterns: static API classes in `lib/apis/`, model classes in `lib/models/`, page-level controllers beside views, and shared widgets in `lib/widgets/`.
Name Dart files in `snake_case.dart`; classes, controllers, and models use `PascalCase`. Keep route names and paths centralized in `lib/routes/routes.dart`.
## Testing Guidelines
Use `flutter_test`; place tests in `test/` and name files `*_test.dart`. Add focused tests for routing, service behavior, utilities, and widget regressions when changing shared logic. Run `flutter test` and `flutter analyze` before submitting changes.
## Commit & Pull Request Guidelines
Recent history uses short Conventional Commit-style prefixes, often in Chinese, such as `fix: ...`, `chore: ...`, and `docs: ...`. Keep commits scoped and descriptive.
Pull requests should include a clear summary, affected platforms (`iOS`, `Android`, or both), test results, and screenshots or screen recordings for UI changes. Link related issues when available and call out release-sensitive changes such as signing, payments, permissions, app version, or native dependency updates.
## Security & Configuration Tips
Do not commit new secrets. Existing signing and app configuration files may contain sensitive values; avoid moving or printing them in logs. Keep Kotlin at `1.8.22` because payment dependencies rely on it, and preserve the iOS deployment target at `13.0+` unless release requirements change.
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## 项目概述
清控紫荆数智学堂 — Flutter 移动端数字学习平台,支持 Android 和 iOS。包含课程阅读、图书馆浏览、购书、笔记、讨论、支付(微信/支付宝/内购)、离线阅读等功能。
## 关键约束
- **Flutter SDK 版本固定为 3.29.3**
- **Kotlin 必须保持在 1.8.22**,不可升级到 2.x — fluwx(微信支付)和 tobias(支付宝)与此版本耦合
- AGP 8.7.0,minSdk 24,target Java 17
- iOS 部署目标 13.0+
## 常用命令
```bash
# 安装依赖
flutter pub get
# iOS (首次及每次修改 native 依赖后需 pod install)
cd ios && pod install && cd ..
flutter run -d ios
# Android
flutter run -d android
# 静态分析
flutter analyze
# 构建
flutter build apk --release # Android
flutter build ios --release --no-codesign # iOS
```
## 架构总览
### 入口与应用初始化 (`lib/main.dart`)
`Global.init()` 在 `main()` 中首先调用,依次执行:
1. 锁定竖屏方向
2. 初始化 SQLite(`SqlManager.init()`)
3. 注册 GetX 服务:`StorageService`、`HttpService`、`UserStore`、`ConfigStore`
4. 监听网络恢复事件,自动上传离线数据
5. 恢复登录态:`UserStore.to.profile()`
应用根组件包裹在 `ScreenUtilInit`(设计稿 375x667)、`GetBuilder<ConfigStore>`(主题切换)和 `MaterialApp.router`(GoRouter)中。
### 状态管理 — GetX
`lib/store/` 中包含两个核心 Store:
- **`UserStore`**:登录态、token、用户信息。`_isLogin` 控制鉴权,`needLogin` 是一个 `ValueNotifier<bool>`,作为 GoRouter 的 `refreshListenable` 用于路由守卫。
- **`ConfigStore`**:主题模式(亮色/暗色/跟随系统)。
各页面通过 `GetBuilder` 订阅 Store 变化。
### 路由 — GoRouter (`lib/routes/routes.dart`)
约 45 条命名路由。关键守卫逻辑:
- GoRouter 的 `redirect` 回调检查 `UserStore.to.needLogin`:未登录且目标路由非主页/登录页时,重定向到 `/login`
- 部分独立页面(如 `book_detail`)通过 `_RouteRedirect.auth()` 在 `builder` 中二次校验
- 自定义 `RouteObserver` 追踪导航历史
- `BottomToTopTransitionPage` 用于图片查看器
首页是 4 个底部 tab:课程 (`/course`)、图书馆 (`/library`)、书架 (`/shop`)、我的 (`/mine`)。图书馆是默认 tab(index 1)。
### HTTP 层 (`lib/services/http.dart`)
基于 **Dio** 的单例服务,注册为 GetX 的 lazy singleton。
- **请求签名**:每个请求自动附加 `appId`、`appSecret`、`timestamp`、`Sign`(参数按 key 排序后 MD5 + appSecret 二次 MD5)
- **Token 刷新**:`_RequestInterceptor` 拦截 403 响应,调用 `/v1/members/login/getToken` 刷新 token 后重试原请求;刷新失败则登出并跳转登录页
- **响应缓存**:`_CacheInterceptor` 使用 `flutter_cache_manager`,默认 TTL 12 小时
- 响应统一包装为 `ResponseModel(code, msg, data)`
### API 层 (`lib/apis/`)
所有 API 类为 abstract static 类,调用 `HttpService.to.get()` / `.post()` 并返回类型化模型。不持有状态。
### 本地存储
- **SharedPreferences**(通过 `StorageService`):token、用户信息、隐私协议接受状态、主题、搜索历史
- **SQLite**(通过 `lib/utils/sql.dart` 的 `SqlManager`):离线笔记/划线(`members_book_notes` 表)、阅读历史(`read_history` 表)
- 离线同步:网络恢复时,`Global.init()` 中注册的 `ConnectivitySubscription` 触发 `LibraryAPI.uploadOffline()` 上传离线队列中的笔记
### 核心阅读器 (`lib/pages/read_web/`)
基于 `flutter_inappwebview` 加载本地 HTML 资源(`assets/html/read.html`)。HTML 内嵌 Vue 2、jQuery、MathJax 和公式编辑器。支持高亮、划线、笔记、音频播放、TTS 和全文搜索。
### 安全
- 请求签名:MD5(`lib/utils/sign.dart`)
- 加密:AES + MD5(`lib/utils/encrypt_util.dart`),用于下载时的资源解密
- `lib/utils/app_config.dart` 包含 `appId`、`appSecret` 和加密密钥
- 服务端基础 URL:`https://project-api.ezijing.com/api/book`(定义在 `lib/utils/constants.dart`)
### 支付
三通道支付:微信(fluwx)、支付宝(tobias)、iOS 内购(flutter_inapp_purchase)。应用内还有"紫荆币"虚拟货币体系。
### 文件上传
通过后端接口获取临时凭证后,使用 `flutter_oss_aliyun` 直传阿里云 OSS。
## 注意事项
- `analysis_options.yaml` 使用 `flutter_lints` v2,不要升级到 v5(需要 Flutter SDK 3.29+ 的兼容版本)
- `lib/utils/app_config.dart` 和 `android/key.properties` 包含密钥,已提交到仓库
- release keystore 已提交到仓库根目录
org.gradle.jvmargs=-Xmx1536M
org.gradle.jvmargs=-Xmx4G -XX:+HeapDumpOnOutOfMemoryError
android.useAndroidX=true
android.enableJetifier=true
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>destination</key>
<string>export</string>
<key>generateAppStoreInformation</key>
<true/>
<key>method</key>
<string>app-store</string>
<key>provisioningProfiles</key>
<dict>
<key>com.zijing.book.flutterBook</key>
<string>book-app-app-store-20260514</string>
</dict>
<key>signingStyle</key>
<string>automatic</string>
<key>teamID</key>
<string>MYN43C5WGE</string>
<key>uploadBitcode</key>
<false/>
<key>uploadSymbols</key>
<true/>
</dict>
</plist>
......@@ -20,7 +20,5 @@
<string>????</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>MinimumOSVersion</key>
<string>12.0</string>
</dict>
</plist>
......@@ -36,9 +36,6 @@ PODS:
- OrderedSet (6.0.3)
- package_info_plus (0.4.5):
- Flutter
- path_provider_foundation (0.0.1):
- Flutter
- FlutterMacOS
- permission_handler_apple (9.3.0):
- Flutter
- ReachabilitySwift (5.2.4)
......@@ -68,7 +65,6 @@ DEPENDENCIES:
- image_picker_ios (from `.symlinks/plugins/image_picker_ios/ios`)
- just_audio (from `.symlinks/plugins/just_audio/darwin`)
- package_info_plus (from `.symlinks/plugins/package_info_plus/ios`)
- path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`)
- permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- sqflite_darwin (from `.symlinks/plugins/sqflite_darwin/darwin`)
......@@ -106,8 +102,6 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/just_audio/darwin"
package_info_plus:
:path: ".symlinks/plugins/package_info_plus/ios"
path_provider_foundation:
:path: ".symlinks/plugins/path_provider_foundation/darwin"
permission_handler_apple:
:path: ".symlinks/plugins/permission_handler_apple/ios"
shared_preferences_foundation:
......@@ -120,7 +114,7 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
audio_session: 9bb7f6c970f21241b19f5a3658097ae459681ba0
connectivity_plus: 481668c94744c30c53b8895afb39159d1e619bdf
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
Flutter: cabc95a1d2626b1b06e7179b784ebcf0c0cde467
flutter_app_update: 816fdb2e30e4832a7c45e3f108d391c42ef040a9
flutter_inapp_purchase: 722da12971a50f306f37e62fc1aaf576b1cbecf6
flutter_inappwebview_ios: b89ba3482b96fb25e00c967aae065701b66e9b99
......@@ -128,14 +122,13 @@ SPEC CHECKSUMS:
flutter_sound_core: 7f2626d249d3a57bfa6da892ef7e22d234482c1a
flutter_tts: b88dbc8655d3dc961bc4a796e4e16a4cc1795833
fluwx: 49b4472b54762abde51daa0bc348ba05520016ac
image_picker_ios: 7fe1ff8e34c1790d6fff70a32484959f563a928a
image_picker_ios: e0ece4aa2a75771a7de3fa735d26d90817041326
just_audio: 4e391f57b79cad2b0674030a00453ca5ce817eed
OrderedSet: e539b66b644ff081c73a262d24ad552a69be3a94
package_info_plus: 566e1b7a2f3900e4b0020914ad3fc051dcc95596
path_provider_foundation: 080d55be775b7414fd5a5ef3ac137b97b097e564
permission_handler_apple: 4ed2196e43d0651e8ff7ca3483a069d469701f2d
ReachabilitySwift: 32793e867593cfc1177f5d16491e3a197d2fccda
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
shared_preferences_foundation: 7036424c3d8ec98dfe75ff1667cb0cd531ec82bb
sqflite_darwin: 20b2a3a3b70e43edae938624ce550a3cbf66a3d0
tobias: f9c405df3ded93ad34539200fe3e7779cfb19081
WechatOpenSDK-XCFramework: ff342ae616bb86df3d236aca38059dfd4bc4a949
......
......@@ -468,11 +468,11 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
TARGETED_DEVICE_FAMILY = 1;
VALIDATE_PRODUCT = YES;
};
name = Profile;
......@@ -483,22 +483,22 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CODE_SIGN_IDENTITY = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 65TSK9H32H;
DEVELOPMENT_TEAM = MYN43C5WGE;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "清控紫荆数智学堂";
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.books";
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.zijing.book.flutterBook.max;
PRODUCT_BUNDLE_IDENTIFIER = com.zijing.book.flutterBook;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
PROVISIONING_PROFILE_SPECIFIER = "book-app-app-store-20260514";
VERSIONING_SYSTEM = "apple-generic";
};
name = Profile;
......@@ -592,11 +592,11 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
TARGETED_DEVICE_FAMILY = 1;
};
name = Debug;
};
......@@ -641,11 +641,11 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos;
SUPPORTED_PLATFORMS = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
TARGETED_DEVICE_FAMILY = 1;
VALIDATE_PRODUCT = YES;
};
name = Release;
......@@ -659,17 +659,17 @@
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 65TSK9H32H;
DEVELOPMENT_TEAM = MYN43C5WGE;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "清控紫荆数智学堂";
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.books";
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.zijing.book.flutterBook.max;
PRODUCT_BUNDLE_IDENTIFIER = com.zijing.book.flutterBook;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
VERSIONING_SYSTEM = "apple-generic";
......@@ -682,22 +682,22 @@
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CODE_SIGN_IDENTITY = "iPhone Distribution";
CODE_SIGN_STYLE = Manual;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 65TSK9H32H;
DEVELOPMENT_TEAM = MYN43C5WGE;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "清控紫荆数智学堂";
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.books";
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
IPHONEOS_DEPLOYMENT_TARGET = 13.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
);
PRODUCT_BUNDLE_IDENTIFIER = com.zijing.book.flutterBook.max;
PRODUCT_BUNDLE_IDENTIFIER = com.zijing.book.flutterBook;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
PROVISIONING_PROFILE_SPECIFIER = "book-app-app-store-20260514";
VERSIONING_SYSTEM = "apple-generic";
};
name = Release;
......
......@@ -26,6 +26,7 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
......@@ -54,6 +55,7 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
customLLDBInitFile = "$(SRCROOT)/Flutter/ephemeral/flutter_lldbinit"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
......
#import <Flutter/Flutter.h>
#import <UIKit/UIKit.h>
@interface AppDelegate : FlutterAppDelegate
@interface AppDelegate : FlutterAppDelegate <FlutterImplicitEngineDelegate>
@end
......@@ -5,9 +5,12 @@
- (BOOL)application:(UIApplication *)application
didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[GeneratedPluginRegistrant registerWithRegistry:self];
// Override point for customization after application launch.
return [super application:application didFinishLaunchingWithOptions:launchOptions];
}
- (void)didInitializeImplicitFlutterEngine:(NSObject<FlutterImplicitEngineBridge>*)engineBridge {
[GeneratedPluginRegistrant registerWithRegistry:engineBridge.pluginRegistry];
}
@end
......@@ -41,7 +41,7 @@
<string>weixin</string>
<key>CFBundleURLSchemes</key>
<array>
<string>1111</string>
<string>wx1111</string>
</array>
</dict>
</array>
......@@ -69,12 +69,35 @@
<string>清控紫荆数智学堂需要访问麦克风</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>清控紫荆数智学堂需要访问照片</string>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneClassName</key>
<string>UIWindowScene</string>
<key>UISceneConfigurationName</key>
<string>flutter</string>
<key>UISceneDelegateClassName</key>
<string>FlutterSceneDelegate</string>
<key>UISceneStoryboardFile</key>
<string>Main</string>
</dict>
</array>
</dict>
</dict>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIMainStoryboardFile</key>
<string>Main</string>
<key>UIRequiresFullScreen</key>
<true/>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationLandscapeLeft</string>
......
......@@ -34,15 +34,17 @@ class _ReadPageState extends State<ReadPage> {
chapterName: widget.chapterName,
bookDetailModel: widget.bookDetailModel,
noteId: widget.noteId),
builder: (readController) => WillPopScope(
onWillPop: () async {
builder: (readController) => PopScope(
canPop: false,
onPopInvokedWithResult: (didPop, result) {
if (!didPop) {
PopBackModel backModel = PopBackModel(
chapterId: readController.chapterId,
back: true,
chapterName: readController.chapterId);
context.pop(backModel);
CustomToast.dismiss();
return false;
}
},
child: Scaffold(
appBar: CustomAppBar(
......
......@@ -271,8 +271,8 @@ class _UserSetPageState extends State<UserSetPage> {
context: context,
barrierDismissible: !forcedUpgrade,
builder: (BuildContext context) {
return WillPopScope(
onWillPop: () => Future.value(!forcedUpgrade),
return PopScope(
canPop: forcedUpgrade,
child: AlertDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.0.w),
......@@ -365,8 +365,8 @@ class _UserSetPageState extends State<UserSetPage> {
context: context,
barrierDismissible: !forcedUpgrade,
builder: (BuildContext context) {
return WillPopScope(
onWillPop: () => Future.value(!forcedUpgrade),
return PopScope(
canPop: forcedUpgrade,
child: AlertDialog(
insetPadding: EdgeInsets.zero, // 设置水平边距
shape: RoundedRectangleBorder(
......
......@@ -72,7 +72,6 @@ abstract class AppTheme {
static ThemeData get light {
var scheme = ColorScheme.light(
onBackground: const Color(0xFF333333),
surface: Colors.white,
onSurface: const Color(0xFF333333),
primary: primary,
......@@ -90,7 +89,6 @@ abstract class AppTheme {
static ThemeData get dark {
var scheme = ColorScheme.dark(
onBackground: Colors.white,
surface: const Color(0xFF252525),
onSurface: Colors.white,
primary: primary,
......@@ -113,7 +111,7 @@ abstract class AppTheme {
scaffoldBackgroundColor: scheme.surface,
splashColor: Colors.transparent,
highlightColor: Colors.transparent,
dialogTheme: DialogTheme(
dialogTheme: DialogThemeData(
elevation: 0,
titleTextStyle: TextStyle(
fontSize: 22.w,
......@@ -249,7 +247,7 @@ abstract class AppTheme {
borderRadius: BorderRadius.all(Radius.circular(6.w)),
),
),
tabBarTheme: TabBarTheme(
tabBarTheme: TabBarThemeData(
labelColor: scheme.primary,
unselectedLabelColor: scheme.surface,
labelStyle: TextStyle(
......
......@@ -105,8 +105,8 @@ abstract class Access {
context: context,
barrierDismissible: false, // 不允许点击外部关闭
builder: (BuildContext dialogContext) {
return WillPopScope(
onWillPop: () async => false, // 不允许返回键关闭
return PopScope(
canPop: false,
child: AlertDialog(
insetPadding: EdgeInsets.symmetric(horizontal: 40.w),
shape: RoundedRectangleBorder(
......@@ -212,8 +212,8 @@ abstract class Access {
showDialog(
context: context,
builder: (BuildContext dialogContext) {
return WillPopScope(
onWillPop: () => Future.value(true),
return PopScope(
canPop: true,
child: AlertDialog(
insetPadding: EdgeInsets.zero,
shape: RoundedRectangleBorder(
......
......@@ -311,7 +311,7 @@ class CustomInput extends StatelessWidget {
);
}
Widget? _suffixIcon(InputDecorationTheme decorationTheme) {
Widget? _suffixIcon(InputDecorationThemeData decorationTheme) {
if (iconData == null) return null;
return Padding(
padding: EdgeInsetsDirectional.only(
......@@ -570,7 +570,7 @@ class CustomInputCommands extends StatelessWidget {
);
}
Widget? _suffixIcon(InputDecorationTheme decorationTheme) {
Widget? _suffixIcon(InputDecorationThemeData decorationTheme) {
if (iconData == null) return null;
return Padding(
padding: EdgeInsetsDirectional.only(
......
差异被折叠。
......@@ -16,13 +16,13 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 1.0.1+2
version: 1.0.1+4
environment:
sdk: '>=3.0.0 <4.0.0'
fluwx:
app_id: '1111'
app_id: 'wx1111'
debug_logging: true
# Dependencies specify other packages that your package needs in order to work.
......@@ -59,14 +59,15 @@ dependencies:
# 录音
flutter_tts: ^3.8.5
# webview
flutter_inappwebview: ^6.0.0
intl: ^0.19.0
flutter_inappwebview: ^6.1.0
intl: ^0.20.2
# 录音
flutter_sound: 9.30.0
permission_handler: ^12.0.0
audio_session: ^0.1.6
flutter_sound_platform_interface: 9.30.0
path: ^1.9.0
# 获取目录结构
path_provider: ^2.0.2
# 下拉刷新 上拉加载更多
......
#!/bin/bash
set -e
# =============================================
# 清控紫荆数智学堂 — iOS 发布脚本
# 用法: ./scripts/ios_publish.sh [--submit]
# =============================================
APP_ID="6476735372"
PROJECT_DIR="$(cd "$(dirname "$0")/.." && pwd)"
# 从 pubspec.yaml 读取版本号
VERSION=$(grep '^version:' "$PROJECT_DIR/pubspec.yaml" | sed 's/version: //' | cut -d'+' -f1)
BUILD=$(grep '^version:' "$PROJECT_DIR/pubspec.yaml" | sed 's/version: //' | cut -d'+' -f2)
# 解析参数
SUBMIT=false
for arg in "$@"; do
case $arg in
--submit) SUBMIT=true ;;
esac
done
echo "========================================="
echo " App: 清控紫荆数智学堂 ($APP_ID)"
echo " 版本: $VERSION (build $BUILD)"
echo "========================================="
# 1. 安装依赖
echo "[1/4] 安装 Flutter 依赖..."
cd "$PROJECT_DIR"
flutter pub get
# 2. Pod install
echo "[2/4] 安装 CocoaPods 依赖..."
cd "$PROJECT_DIR/ios"
pod install
# 3. 构建 IPA(Flutter 内置流程:build → archive → export)
echo "[3/4] 构建 IPA..."
cd "$PROJECT_DIR"
# 生成 ExportOptions.plist(如果不存在)
EXPORT_OPTIONS="$PROJECT_DIR/ios/ExportOptions.plist"
if [ ! -f "$EXPORT_OPTIONS" ]; then
cat > "$EXPORT_OPTIONS" << 'XMLEOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>method</key>
<string>app-store</string>
<key>destination</key>
<string>export</string>
<key>teamID</key>
<string>MYN43C5WGE</string>
<key>uploadBitcode</key>
<false/>
<key>uploadSymbols</key>
<true/>
<key>signingStyle</key>
<string>automatic</string>
<key>generateAppStoreInformation</key>
<true/>
</dict>
</plist>
XMLEOF
echo "已生成 ExportOptions.plist"
fi
flutter build ipa --release \
--export-options-plist="$EXPORT_OPTIONS"
IPA_PATH="$PROJECT_DIR/build/ios/ipa/flutter_book.ipa"
if [ ! -f "$IPA_PATH" ]; then
echo "错误: IPA 文件未生成: $IPA_PATH"
exit 1
fi
echo "IPA 生成成功: $IPA_PATH"
# 4. 上传到 App Store
echo "[4/4] 上传到 App Store..."
if [ "$SUBMIT" = true ]; then
asc publish appstore \
--app "$APP_ID" \
--ipa "$IPA_PATH" \
--version "$VERSION" \
--submit --confirm
else
asc builds upload \
--app "$APP_ID" \
--ipa "$IPA_PATH"
fi
echo ""
echo "========================================="
echo " 上传完成!"
echo " 版本: $VERSION (build $BUILD)"
if [ "$SUBMIT" = true ]; then
echo " 已自动提交审核"
else
echo " 未提交审核,可用以下命令提交:"
echo " asc review status --app $APP_ID"
echo " 或运行: $0 --submit"
fi
echo "========================================="
Markdown 格式
0% 或
您添加了 0 人 到此讨论。请谨慎行事。
请先完成此评论的编辑!
请 注册 或者 后发表评论