提交 67ef61b0 authored 作者: 王鹏飞's avatar 王鹏飞

Refactor HttpService and Access utility for improved permission handling and code clarity

- Updated HttpService to enhance header management and error handling. - Improved Access utility to include permission explanation dialogs before requesting permissions. - Modified AssetsPicker to check for permissions before accessing the camera or gallery. - Adjusted constants for server URLs and agreements for better maintainability. - Cleaned up pubspec.yaml and pubspec.lock by removing unused dependencies.
上级 883b112b
......@@ -51,30 +51,18 @@ android {
versionName flutterVersionName
}
signingConfigs {
// release {
// keyAlias keystoreProperties['keyAlias']
// keyPassword keystoreProperties['keyPassword']
// storeFile file(keystoreProperties['storeFile'])
// storePassword keystoreProperties['storePassword']
// }
release{
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile file(keystoreProperties['storeFile'])
storePassword keystoreProperties['storePassword']
}
debug{
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile file(keystoreProperties['storeFile'])
storePassword keystoreProperties['storePassword']
}
// debug 使用默认签名
}
buildTypes {
debug {
signingConfig signingConfigs.release
// 使用默认 debug 签名
}
release {
// TODO: Add your own signing config for the release build.
......
......@@ -14,7 +14,7 @@
</queries>
<application
android:label="紫荆数智学堂"
android:label="清控紫荆数智学堂"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
......
storePassword=123456
keyPassword=123456
storePassword=zijing123456
keyPassword=zijing123456
keyAlias=zijing
storeFile=/Users/apple/zijiing_key.jks
#storeFile=zijiing_key.jks
\ No newline at end of file
storeFile=../zijing_release.jks
PODS:
- audio_session (0.0.1):
- Flutter
- Bugly (2.6.1)
- connectivity_plus (0.0.1):
- Flutter
- ReachabilitySwift
......@@ -10,9 +9,6 @@ PODS:
- Flutter (1.0.0)
- flutter_app_update (0.0.1):
- Flutter
- flutter_bugly (0.0.1):
- Bugly
- Flutter
- flutter_inapp_purchase (0.0.1):
- Flutter
- flutter_inappwebview_ios (0.0.1):
......@@ -47,10 +43,6 @@ PODS:
- permission_handler_apple (9.1.1):
- Flutter
- ReachabilitySwift (5.0.0)
- screen_protector (1.2.1):
- Flutter
- ScreenProtectorKit (~> 1.3.1)
- ScreenProtectorKit (1.3.1)
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
......@@ -70,7 +62,6 @@ DEPENDENCIES:
- device_info_plus (from `.symlinks/plugins/device_info_plus/ios`)
- Flutter (from `Flutter`)
- flutter_app_update (from `.symlinks/plugins/flutter_app_update/ios`)
- flutter_bugly (from `.symlinks/plugins/flutter_bugly/ios`)
- flutter_inapp_purchase (from `.symlinks/plugins/flutter_inapp_purchase/ios`)
- flutter_inappwebview_ios (from `.symlinks/plugins/flutter_inappwebview_ios/ios`)
- flutter_sound (from `.symlinks/plugins/flutter_sound/ios`)
......@@ -81,18 +72,15 @@ DEPENDENCIES:
- 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`)
- screen_protector (from `.symlinks/plugins/screen_protector/ios`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- sqflite (from `.symlinks/plugins/sqflite/darwin`)
- tobias (from `.symlinks/plugins/tobias/ios`)
SPEC REPOS:
trunk:
- Bugly
- flutter_sound_core
- OrderedSet
- ReachabilitySwift
- ScreenProtectorKit
- WechatOpenSDK-XCFramework
EXTERNAL SOURCES:
......@@ -106,8 +94,6 @@ EXTERNAL SOURCES:
:path: Flutter
flutter_app_update:
:path: ".symlinks/plugins/flutter_app_update/ios"
flutter_bugly:
:path: ".symlinks/plugins/flutter_bugly/ios"
flutter_inapp_purchase:
:path: ".symlinks/plugins/flutter_inapp_purchase/ios"
flutter_inappwebview_ios:
......@@ -128,8 +114,6 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/path_provider_foundation/darwin"
permission_handler_apple:
:path: ".symlinks/plugins/permission_handler_apple/ios"
screen_protector:
:path: ".symlinks/plugins/screen_protector/ios"
shared_preferences_foundation:
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
sqflite:
......@@ -139,12 +123,10 @@ EXTERNAL SOURCES:
SPEC CHECKSUMS:
audio_session: 9bdd3bf46960d4322cb8c3cb6138295dcfe84eee
Bugly: 217ac2ce5f0f2626d43dbaa4f70764c953a26a31
connectivity_plus: 481668c94744c30c53b8895afb39159d1e619bdf
device_info_plus: 335f3ce08d2e174b9fdc3db3db0f4e3b1f66bd89
Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854
flutter_app_update: 816fdb2e30e4832a7c45e3f108d391c42ef040a9
flutter_bugly: 12197049262a692eab0bd2834d465a5647d920c2
flutter_inapp_purchase: 722da12971a50f306f37e62fc1aaf576b1cbecf6
flutter_inappwebview_ios: 25b61a1b550d1068e4ddaf490fc1d03c2ce6828d
flutter_sound: 49be32081884d275fe91d48262f4b1fcd86e10d3
......@@ -158,8 +140,6 @@ SPEC CHECKSUMS:
path_provider_foundation: 608fcb11be570ce83519b076ab6a1fffe2474f05
permission_handler_apple: 3787117e48f80715ff04a3830ca039283d6a4f29
ReachabilitySwift: 985039c6f7b23a1da463388634119492ff86c825
screen_protector: 3d90d44ac886b25335aebd93230b454aef45794a
ScreenProtectorKit: 83a6281b02c7a5902ee6eac4f5045f674e902ae4
shared_preferences_foundation: 0b09b969fb36da5551c0bc4a2dbd9d0ff9387478
sqflite: c35dad70033b8862124f8337cc994a809fcd9fa3
tobias: 50b529c2501d277c83fef9976803a001eb58a057
......
......@@ -489,7 +489,7 @@
DEVELOPMENT_TEAM = MYN43C5WGE;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "紫荆数智学堂";
INFOPLIST_KEY_CFBundleDisplayName = "清控紫荆数智学堂";
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.books";
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
LD_RUNPATH_SEARCH_PATHS = (
......@@ -662,7 +662,7 @@
DEVELOPMENT_TEAM = MYN43C5WGE;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "紫荆数智学堂";
INFOPLIST_KEY_CFBundleDisplayName = "清控紫荆数智学堂";
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.books";
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
LD_RUNPATH_SEARCH_PATHS = (
......@@ -688,7 +688,7 @@
DEVELOPMENT_TEAM = MYN43C5WGE;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
INFOPLIST_KEY_CFBundleDisplayName = "紫荆数智学堂";
INFOPLIST_KEY_CFBundleDisplayName = "清控紫荆数智学堂";
INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.books";
IPHONEOS_DEPLOYMENT_TARGET = 12.0;
LD_RUNPATH_SEARCH_PATHS = (
......
......@@ -7,7 +7,7 @@
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>紫荆数智学堂</string>
<string>清控紫荆数智学堂</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
......@@ -47,15 +47,15 @@
<true/>
</dict>
<key>NSAppleMusicUsageDescription</key>
<string>紫荆数智学堂需要访问媒体</string>
<string>清控紫荆数智学堂需要访问媒体</string>
<key>NSCameraUsageDescription</key>
<string>紫荆数智学堂需要访问相机</string>
<string>清控紫荆数智学堂需要访问相机</string>
<key>NSMicrophoneUsageDescription</key>
<string>紫荆数智学堂需要访问麦克风</string>
<string>清控紫荆数智学堂需要访问麦克风</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>紫荆数智学堂需要访问照片</string>
<string>清控紫荆数智学堂需要访问照片</string>
<key>NSSpeechRecognitionUsageDescription</key>
<string>紫荆数智学堂需要语言识别</string>
<string>清控紫荆数智学堂需要语言识别</string>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UILaunchStoryboardName</key>
......
......@@ -18,13 +18,6 @@ class Global {
await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
// 初始化数据库
SqlManager.init();
// 检测网络变化
Connectivity().onConnectivityChanged.listen((ConnectivityResult result) {
Console.log('网络变化--------------------------------$result');
if (result != ConnectivityResult.none && UserStore.to.isLogin) {
upload();
}
});
// 监测app生命周期
// WidgetsBinding.instance?.addObserver(AppLifecycleObserver());
......@@ -32,12 +25,20 @@ class Global {
// 配置存储
Get.putAsync<StorageService>(() => StorageService().init()),
]).whenComplete(() {
// 先注册依赖最广的用户状态,避免启动早期被其他模块提前读取。
Get.put<UserStore>(UserStore());
// 网络
Get.put<HttpService>(HttpService());
//配置基本设置
Get.put<ConfigStore>(ConfigStore());
//
Get.put<UserStore>(UserStore());
});
// UserStore 注册后再监听网络变化,避免回调触发时找不到实例。
Connectivity().onConnectivityChanged.listen((ConnectivityResult result) {
Console.log('网络变化--------------------------------$result');
if (result != ConnectivityResult.none && UserStore.to.isLogin) {
upload();
}
});
}
......
......@@ -6,7 +6,6 @@ import 'package:flutter_book/routes/index.dart';
import 'package:flutter_book/store/index.dart';
import 'package:flutter_book/theme.dart';
import 'package:flutter_book/widgets/index.dart';
import 'package:flutter_bugly/flutter_bugly.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:get/get.dart';
import 'package:oktoast/oktoast.dart';
......@@ -19,17 +18,9 @@ void main() {
Future.wait([
UserStore.to.profile(),
]).whenComplete(() {
FlutterBugly.postCatchedException(() {
// 如果需要 ensureInitialized,请在这里运行。
WidgetsFlutterBinding.ensureInitialized();
runApp(const MyApp());
FlutterBugly.init(
androidAppId: "8b4da96535",
iOSAppId: "290703a371",
);
});
// runApp(const MyApp());
//FlutterNativeSplash.remove();
// 如果需要 ensureInitialized,请在这里运行。
WidgetsFlutterBinding.ensureInitialized();
runApp(const MyApp());
});
});
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle.light);
......@@ -46,7 +37,7 @@ class MyApp extends StatelessWidget {
builder: (context, child) => GetBuilder<ConfigStore>(
builder: (config) => MaterialApp.router(
debugShowCheckedModeBanner: false,
title: '紫荆数智学堂',
title: '清控紫荆数智学堂',
theme: AppTheme.light,
darkTheme: AppTheme.dark,
themeMode: ThemeMode.light,
......
......@@ -116,12 +116,114 @@ class LoginController extends GetxController {
update();
}
/// 显示用户协议弹框
Future<bool> _showAgreementDialog(BuildContext context) async {
await showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.0.w),
),
contentPadding: EdgeInsets.zero,
title: Center(
child: Padding(
padding: EdgeInsets.only(top: 24.w),
child: Text(
'用户协议及隐私协议',
style: TextStyle(
fontSize: 16.w,
fontWeight: Fonts.medium,
color: Colours.c3,
),
),
),
),
content: Wrap(
children: [
Container(
margin: EdgeInsets.only(top: 20.w, bottom: 29.w),
padding: EdgeInsets.symmetric(horizontal: 20.w),
child: Center(
child: Text(
'已阅读并同意《用户协议》、《隐私政策》',
style: TextStyle(
fontSize: 14.w,
color: Colours.c6,
),
textAlign: TextAlign.center,
),
),
),
Container(
height: 1.w,
width: double.infinity,
color: Colours.cLine,
),
],
),
actionsPadding: EdgeInsets.zero,
actions: <Widget>[
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
GestureDetector(
onTap: () => Navigator.of(context).pop(),
child: Container(
width: 105.w,
height: 39.5.w,
color: Colours.cFF,
alignment: Alignment.center,
child: Text(
'不同意',
style: TextStyle(
fontSize: 15.w,
fontWeight: Fonts.medium,
color: Colours.c6,
),
),
),
),
Container(
height: 39.5.w,
width: 1,
color: Colours.cLine,
),
GestureDetector(
onTap: () {
Navigator.of(context).pop();
setAgree();
},
child: Container(
width: 105.w,
height: 39.5.w,
color: Colours.cFF,
alignment: Alignment.center,
child: Text(
'同意',
style: TextStyle(
fontSize: 15.w,
fontWeight: Fonts.boldSemi,
color: Colours.cAB1941,
),
),
),
),
],
),
],
);
},
);
return agree;
}
/// 登录
void onLogin(BuildContext context) async {
Tools.unfocus();
if (!agree) {
Toast.show('请先阅读并同意《用户协议》和《隐私政策》');
return;
await _showAgreementDialog(context);
if (!agree) return;
}
String type = '1';
......@@ -163,8 +265,8 @@ class LoginController extends GetxController {
/// 发送验证码
void sendCode() async {
if (!agree) {
Toast.show('请先阅读并同意《用户协议》和《隐私政策》');
return;
await _showAgreementDialog(Get.context!);
if (!agree) return;
}
final result =
await AccountAPI.sendCode(phone: phoneInput.text, type: 'login');
......
......@@ -215,13 +215,13 @@ class _LoginPageState extends State<LoginPage> {
GestureDetector(
child: Text('《用户协议》',style: TextStyle(color: Colours.cBlue,fontSize:14.w,height: 1.4)),
onTap: (){
context.pushNamed(Routes.terms,queryParameters: {'url':'$kServerUrl$kUserAgreement','title':'用户协议'});
context.pushNamed(Routes.terms,queryParameters: {'url':kUserAgreement,'title':'用户协议'});
},
),
GestureDetector(
child: Text('《隐私政策》',style: TextStyle(color: Colours.cBlue,fontSize:14.w,height: 1.4)),
onTap: (){
context.pushNamed(Routes.terms,queryParameters: {'url':'$kServerUrl$kUserPriAgreement','title':'隐私政策'});
context.pushNamed(Routes.terms,queryParameters: {'url':kUserPriAgreement,'title':'隐私政策'});
},
),
],
......
差异被折叠。
......@@ -192,8 +192,11 @@ class _ReadInputDiscussState extends State<ReadInputDiscuss> {
onTap: () async{
final assets = await AssetsPicker.image(
context: context,
purpose: '用于从相册中选择图片添加到讨论中,仅用于上传和显示图片内容。',
);
widget.controller.addDiscussInputImages(assets!.path);
if (assets != null) {
widget.controller.addDiscussInputImages(assets.path);
}
},
child: SizedBox(
// color: Colors.red,
......
library splash_page;
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_book/apis/index.dart';
import 'package:go_router/go_router.dart';
import '../../routes/index.dart';
import '../../services/index.dart';
import '../../utils/index.dart';
part 'view.dart';
\ No newline at end of file
part 'view.dart';
......@@ -8,65 +8,172 @@ class SplashPage extends StatefulWidget {
}
class _SplashPageState extends State<SplashPage> {
bool _navigated = false;
@override
void initState() {
super.initState();
Future.wait([
Future.delayed(const Duration(seconds: 2))
]).whenComplete(() async {
final netStatus = await Tools.checkCurrentNetStatus();
if (netStatus){
final ads = await CommonAPI.list(type: '1');
if (ads.isNotEmpty) {
context.pushReplacementNamed(Routes.ad,extra: ads);
}
else {
context.pushReplacementNamed(Routes.main);
}
}
else {
context.pushReplacementNamed(Routes.main);
WidgetsBinding.instance.addPostFrameCallback((_) {
_startLaunchFlow();
});
}
Future<void> _startLaunchFlow() async {
await _showPrivacyNoticeIfNeeded();
if (!mounted || _navigated) return;
await Future.delayed(const Duration(seconds: 2));
if (!mounted || _navigated) return;
final netStatus = await Tools.checkCurrentNetStatus();
if (!mounted || _navigated) return;
if (netStatus) {
final ads = await CommonAPI.list(type: '1');
if (!mounted || _navigated) return;
if (ads.isNotEmpty) {
_navigated = true;
context.pushReplacementNamed(Routes.ad, extra: ads);
return;
}
}
});
_navigated = true;
context.pushReplacementNamed(Routes.main);
}
Future<void> _showPrivacyNoticeIfNeeded() async {
if (StorageService.to.getBool(kLocalPrivacyNoticeShown)) {
return;
}
await showDialog<void>(
context: context,
barrierDismissible: false,
builder: (dialogContext) {
return AlertDialog(
scrollable: true,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
title: const Text(
'隐私声明',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w600,
color: Colours.c3,
),
),
content: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
RichText(
text: TextSpan(
style: const TextStyle(
fontSize: 14,
height: 1.6,
color: Colours.c6,
),
children: [
const TextSpan(
text: '欢迎使用清控紫荆数智学堂。为保障你的个人信息安全,我们会按照',
),
TextSpan(
text: '《用户协议》',
style: const TextStyle(color: Color(0xFF2A82D9)),
recognizer: TapGestureRecognizer()
..onTap = () {
context.pushNamed(
Routes.terms,
queryParameters: {
'url': kUserAgreement,
'title': '用户协议',
},
);
},
),
const TextSpan(text: '和'),
TextSpan(
text: '《隐私政策》',
style: const TextStyle(color: Color(0xFF2A82D9)),
recognizer: TapGestureRecognizer()
..onTap = () {
context.pushNamed(
Routes.terms,
queryParameters: {
'url': kUserPriAgreement,
'title': '隐私政策',
},
);
},
),
const TextSpan(
text: '处理账号信息、设备信息、学习记录等内容。',
),
],
),
),
],
),
),
actions: [
SizedBox(
width: double.infinity,
child: TextButton(
onPressed: () async {
await StorageService.to
.setBool(kLocalPrivacyNoticeShown, true);
if (dialogContext.mounted) {
Navigator.of(dialogContext).pop();
}
},
child: const Text('我知道了'),
),
),
],
);
},
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
extendBodyBehindAppBar: true,
// appBar: AppBar(),
body: SizedBox(
height: double.infinity,
width: double.infinity,
child: Image.asset('assets/images/splash.png',fit: BoxFit.cover,),
)
// const Column(
// crossAxisAlignment: CrossAxisAlignment.stretch,
// mainAxisAlignment: MainAxisAlignment.center,
// children: [
// Expanded(
// flex: 5,
// child: Center(
// child: CustomEmpty(
// icon: CustomImage.asset(
// url: 'assets/images/logo.png',
// fit: BoxFit.contain,
// ),
// title: Text('紫荆数智学堂'),
// ),
// )
// ),
// // Expanded(
// // flex: 2,
// // child: Center(child: CupertinoActivityIndicator(),),
// // )
// ],
// ),
);
extendBodyBehindAppBar: true,
// appBar: AppBar(),
body: SizedBox(
height: double.infinity,
width: double.infinity,
child: Image.asset(
'assets/images/splash.png',
fit: BoxFit.cover,
),
)
// const Column(
// crossAxisAlignment: CrossAxisAlignment.stretch,
// mainAxisAlignment: MainAxisAlignment.center,
// children: [
// Expanded(
// flex: 5,
// child: Center(
// child: CustomEmpty(
// icon: CustomImage.asset(
// url: 'assets/images/logo.png',
// fit: BoxFit.contain,
// ),
// title: Text('清控紫荆数智学堂'),
// ),
// )
// ),
// // Expanded(
// // flex: 2,
// // child: Center(child: CupertinoActivityIndicator(),),
// // )
// ],
// ),
);
}
}
......@@ -44,9 +44,16 @@ class _AboutPageState extends State<AboutPage> {
child: const CustomImage.asset(url: 'assets/images/icon.png'),
),
Gaps.vGaps15,
Text('紫荆数智学堂',style: TextStyle(fontSize: 17.w,fontWeight: Fonts.medium,color: Colours.c3),),
Text(
'清控紫荆数智学堂',
style: TextStyle(
fontSize: 17.w,
fontWeight: Fonts.medium,
color: Colours.c3),
),
Gaps.vGaps5,
Text('V${packageInfo.version??''}',style: TextStyle(fontSize: 13.w,color: Colours.c9)),
Text('V${packageInfo.version}',
style: TextStyle(fontSize: 13.w, color: Colours.c9)),
],
),
SafeArea(
......@@ -56,29 +63,50 @@ class _AboutPageState extends State<AboutPage> {
mainAxisAlignment: MainAxisAlignment.center,
children: [
GestureDetector(
child: Text('《用户协议》',textAlign: TextAlign.right,style: TextStyle(fontSize: 10.w,color: const Color(0xFF2A82D9)),),
onTap: (){
context.pushNamed(Routes.terms,queryParameters: {'url':'$kServerUrl$kUserAgreement','title':'用户协议'});
child: Text(
'《用户协议》',
textAlign: TextAlign.right,
style: TextStyle(
fontSize: 10.w, color: const Color(0xFF2A82D9)),
),
onTap: () {
context.pushNamed(Routes.terms, queryParameters: {
'url': kUserAgreement,
'title': '用户协议'
});
},
),
Gaps.vGaps15,
Container(width: 1.w,height: 10.w,color: const Color(0xFFC8C8C8),),
Container(
width: 1.w,
height: 10.w,
color: const Color(0xFFC8C8C8),
),
Gaps.vGaps15,
GestureDetector(
child: Text('《隐私协议》',textAlign: TextAlign.left,style: TextStyle(fontSize: 10.w,color: const Color(0xFF2A82D9))),
onTap:(){
context.pushNamed(Routes.terms,queryParameters: {'url':'$kServerUrl$kUserPriAgreement','title':'隐私政策'});
} ,
child: Text('《隐私协议》',
textAlign: TextAlign.left,
style: TextStyle(
fontSize: 10.w, color: const Color(0xFF2A82D9))),
onTap: () {
context.pushNamed(Routes.terms, queryParameters: {
'url': kUserPriAgreement,
'title': '隐私政策'
});
},
)
],
),
Gaps.hGaps10,
Text('Copyright © 2024 Zijing Education. All rights reserved.\n清控紫荆(北京)教育科技股份有限公司\n京ICP证150431号',style: TextStyle(color: Colours.c9,fontSize:9.w),textAlign: TextAlign.center,),
Text(
'Copyright © 2024 Zijing Education. All rights reserved.\n清控紫荆(北京)教育科技股份有限公司\n京ICP证150431号',
style: TextStyle(color: Colours.c9, fontSize: 9.w),
textAlign: TextAlign.center,
),
Gaps.vGaps25,
],
),
)
],
),
);
......@@ -86,8 +114,8 @@ class _AboutPageState extends State<AboutPage> {
void _getVersion() async {
final info = await PackageInfo.fromPlatform();
setState(() {
packageInfo = info;
});
setState(() {
packageInfo = info;
});
}
}
part of recharge;
class CoinRechargePage extends StatefulWidget {
const CoinRechargePage({Key? key}) : super(key: key);
......@@ -9,12 +7,13 @@ class CoinRechargePage extends StatefulWidget {
State<CoinRechargePage> createState() => _CoinRechargePageState();
}
class _CoinRechargePageState extends State<CoinRechargePage> with AutomaticKeepAliveClientMixin {
class _CoinRechargePageState extends State<CoinRechargePage>
with AutomaticKeepAliveClientMixin {
@override
Widget build(BuildContext context) {
return GetBuilder<CoinRechargeController>(
init: CoinRechargeController(context: context),
builder:(controller) => Column(
builder: (controller) => Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
......@@ -27,14 +26,14 @@ class _CoinRechargePageState extends State<CoinRechargePage> with AutomaticKeepA
const Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('紫荆币充值',textAlign:TextAlign.center),
Text('紫荆币充值', textAlign: TextAlign.center),
],
),
Positioned(
right: 0.w,
top: 0.w,
child: GestureDetector(
onTap: (){
onTap: () {
context.pop();
},
child: SizedBox(
......@@ -48,16 +47,15 @@ class _CoinRechargePageState extends State<CoinRechargePage> with AutomaticKeepA
),
),
Container(
margin: EdgeInsets.symmetric(horizontal: 20.w),
child: _buildAudioGridView(controller)
),
margin: EdgeInsets.symmetric(horizontal: 20.w),
child: _buildAudioGridView(controller)),
Gaps.vGaps15,
_buildListView(controller),
Gaps.vGaps15,
Container(
margin: EdgeInsets.symmetric(horizontal:15.w),
margin: EdgeInsets.symmetric(horizontal: 15.w),
child: CustomGradientButton(
text: '立即充值${controller.rechargeModel.priceName??''}',
text: '立即充值${controller.rechargeModel.priceName ?? ''}',
isEnabled: true,
onPressed: () {
controller.createRechargeOrder();
......@@ -66,15 +64,25 @@ class _CoinRechargePageState extends State<CoinRechargePage> with AutomaticKeepA
),
Gaps.vGaps15,
GestureDetector(
onTap: (){
context.pushNamed(Routes.terms,queryParameters: {'url':'$kServerUrl$kUserRechargeAgreement','title':'用户充值协议'});
onTap: () {
context.pushNamed(Routes.terms, queryParameters: {
'url': kUserRechargeAgreement,
'title': '用户充值协议'
});
},
child: RichText(text: TextSpan(
children: [
TextSpan(text: '充值即代表同意',style: TextStyle(fontSize: 13.w,height: 1.5,color: Colours.c9)),
TextSpan(text: '《用户充值协议》',style: TextStyle(fontSize: 13.w,height: 1.5,color: const Color(0xFF2A82D9))),
]
)),
child: RichText(
text: TextSpan(children: [
TextSpan(
text: '充值即代表同意',
style: TextStyle(
fontSize: 13.w, height: 1.5, color: Colours.c9)),
TextSpan(
text: '《用户充值协议》',
style: TextStyle(
fontSize: 13.w,
height: 1.5,
color: const Color(0xFF2A82D9))),
])),
),
Gaps.vGaps15
],
......@@ -82,7 +90,7 @@ class _CoinRechargePageState extends State<CoinRechargePage> with AutomaticKeepA
);
}
Widget _buildAudioGridView(CoinRechargeController controller){
Widget _buildAudioGridView(CoinRechargeController controller) {
return GridView.builder(
// padding: const EdgeInsets.only(left: 13,top: 10),
physics: const NeverScrollableScrollPhysics(),
......@@ -91,26 +99,41 @@ class _CoinRechargePageState extends State<CoinRechargePage> with AutomaticKeepA
crossAxisCount: 3,
crossAxisSpacing: 10.w,
mainAxisSpacing: 10.w,
childAspectRatio: 2
),
childAspectRatio: 2),
itemBuilder: (BuildContext context, int index) {
CoinModel model = controller.data[index];
return GestureDetector(
onTap: (){
onTap: () {
controller.choose(model);
},
child: Container(
decoration: BoxDecoration(
color: model.selected?AppTheme.primary.withOpacity(0.1):Colors.white,
borderRadius: BorderRadius.circular(8.w),
border: Border.all(width: 0.5.w,color: model.selected?AppTheme.primary:const Color(0xFFDADADA))
),
color: model.selected
? AppTheme.primary.withOpacity(0.1)
: Colors.white,
borderRadius: BorderRadius.circular(8.w),
border: Border.all(
width: 0.5.w,
color: model.selected
? AppTheme.primary
: const Color(0xFFDADADA))),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(model.beanName??'',style: TextStyle(color: model.selected?AppTheme.primary:Colours.c3,fontSize: 14.w,height: 1.5),),
Text(model.priceName??'',style: TextStyle(color: model.selected?AppTheme.primary:Colours.c9,fontSize: 11.w,height: 1.5),),
Text(
model.beanName ?? '',
style: TextStyle(
color: model.selected ? AppTheme.primary : Colours.c3,
fontSize: 14.w,
height: 1.5),
),
Text(
model.priceName ?? '',
style: TextStyle(
color: model.selected ? AppTheme.primary : Colours.c9,
fontSize: 11.w,
height: 1.5),
),
],
),
),
......@@ -119,18 +142,21 @@ class _CoinRechargePageState extends State<CoinRechargePage> with AutomaticKeepA
itemCount: controller.data.length,
);
}
Widget _buildListView(CoinRechargeController controller){
Widget _buildListView(CoinRechargeController controller) {
return ListView.builder(
physics: const NeverScrollableScrollPhysics(),
padding: EdgeInsets.symmetric(horizontal: 20.w),
shrinkWrap:true,
itemBuilder: (BuildContext context, int index){
shrinkWrap: true,
itemBuilder: (BuildContext context, int index) {
PayModel model = controller.pays[index];
return GestureDetector(
onTap: (){
onTap: () {
controller.setPayModel(model);
},
child: BuildPayWay(model:model,));
child: BuildPayWay(
model: model,
));
},
itemCount: controller.pays.length,
);
......
......@@ -167,8 +167,11 @@ class _UserEditNotePageState extends State<UserEditNotePage> {
onTap: () async{
final assets = await AssetsPicker.image(
context: context,
purpose: '用于从相册中选择图片添加到笔记中,仅用于上传和显示图片内容。',
);
controller.addImage(assets!.path);
if (assets != null) {
controller.addImage(assets.path);
}
},
child: SizedBox(
// color: Colors.red,
......
......@@ -168,9 +168,12 @@ class _UserInfoPageState extends State<UserInfoPage> {
Console.log("点击拍照");
final assets = await AssetsPicker.camera(
context: context,
purpose: '用于使用相机拍照设置用户头像,仅用于上传和显示您的头像图片。',
);
controller.upload(path: assets!.path);
controller.show();
if (assets != null) {
controller.upload(path: assets.path);
controller.show();
}
},
child: Container(
height: 45.w,
......@@ -202,9 +205,12 @@ class _UserInfoPageState extends State<UserInfoPage> {
Console.log("点击选择现有图片");
final assets = await AssetsPicker.image(
context: context,
purpose: '用于从相册中选择图片设置用户头像,仅用于上传和显示您的头像图片。',
);
controller.upload(path: assets!.path);
controller.show();
if (assets != null) {
controller.upload(path: assets.path);
controller.show();
}
},
child: Container(
height: 45.w,
......
......@@ -48,6 +48,7 @@ import 'package:flutter_book/pages/user_set/index.dart';
import 'package:flutter_book/pages/user_wrong/index.dart';
import 'package:flutter_book/pages/user_wrong_des/index.dart';
import 'package:flutter_book/pages/version_des/index.dart';
import 'package:get/get.dart';
import 'package:go_router/go_router.dart';
import '../models/index.dart';
......@@ -67,7 +68,6 @@ import '../pages/user_terms/index.dart';
import '../pages/version/index.dart';
import '../store/index.dart';
part 'observers.dart';
part 'routes.dart';
part 'transitions.dart';
\ No newline at end of file
part 'transitions.dart';
差异被折叠。
差异被折叠。
part of utils;
abstract class Access {
/// 图片权限
static Future<bool> photos() async {
/// [showExplanation] 是否在申请权限前显示说明对话框
/// [purpose] 权限使用目的说明
/// [context] 上下文,用于显示对话框
static Future<bool> photos({
bool showExplanation = false,
String purpose = '',
BuildContext? context,
}) async {
// 如果需要在申请权限前显示说明,且提供了上下文
if (showExplanation && context != null && purpose.isNotEmpty) {
final bool? shouldRequest = await _showPermissionExplanationDialog(
context: context,
permissionName: Platform.isIOS ? '相册权限' : '存储权限',
purpose: purpose,
);
// 用户取消,不申请权限
if (shouldRequest != true) {
return false;
}
}
if (Platform.isIOS) {
final result = await [Permission.photos].request();
return result[Permission.photos] == PermissionStatus.granted ||
......@@ -17,11 +37,146 @@ abstract class Access {
}
/// 相机权限
static Future<bool> camera() async {
/// [showExplanation] 是否在申请权限前显示说明对话框
/// [purpose] 权限使用目的说明
/// [context] 上下文,用于显示对话框
static Future<bool> camera({
bool showExplanation = false,
String purpose = '',
BuildContext? context,
}) async {
// 如果需要在申请权限前显示说明,且提供了上下文
if (showExplanation && context != null && purpose.isNotEmpty) {
final bool? shouldRequest = await _showPermissionExplanationDialog(
context: context,
permissionName: '相机权限',
purpose: purpose,
);
// 用户取消,不申请权限
if (shouldRequest != true) {
return false;
}
}
final result = await [Permission.camera].request();
return result[Permission.camera] == PermissionStatus.granted;
}
/// 显示权限使用目的说明对话框
/// 对话框不会自动消失,需要用户主动确认
static Future<bool?> _showPermissionExplanationDialog({
required BuildContext context,
required String permissionName,
required String purpose,
}) async {
return await showDialog<bool>(
context: context,
barrierDismissible: false, // 不允许点击外部关闭
builder: (BuildContext dialogContext) {
return WillPopScope(
onWillPop: () async => false, // 不允许返回键关闭
child: AlertDialog(
insetPadding: EdgeInsets.symmetric(horizontal: 40.w),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.w),
),
title: Text(
'权限申请说明',
style: TextStyle(
fontSize: 18.w,
color: Colours.c3,
fontWeight: Fonts.boldSemi,
),
textAlign: TextAlign.center,
),
content: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'权限名称:',
style: TextStyle(
fontSize: 15.w,
color: Colours.c3,
fontWeight: Fonts.medium,
),
),
SizedBox(height: 5.w),
Text(
permissionName,
style: TextStyle(
fontSize: 15.w,
color: Colours.c6,
),
),
SizedBox(height: 15.w),
Text(
'使用目的:',
style: TextStyle(
fontSize: 15.w,
color: Colours.c3,
fontWeight: Fonts.medium,
),
),
SizedBox(height: 5.w),
Text(
purpose,
style: TextStyle(
fontSize: 15.w,
color: Colours.c6,
height: 1.5,
),
),
],
),
actions: <Widget>[
Row(
children: [
Expanded(
child: TextButton(
onPressed: () {
Navigator.of(dialogContext).pop(false);
},
child: Text(
'取消',
style: TextStyle(
fontSize: 16.w,
color: Colours.c6,
fontWeight: Fonts.medium,
),
),
),
),
Container(
width: 1,
height: 40.w,
color: Colours.cLine,
),
Expanded(
child: TextButton(
onPressed: () {
Navigator.of(dialogContext).pop(true);
},
child: Text(
'确定',
style: TextStyle(
fontSize: 16.w,
color: Colours.cAB1941,
fontWeight: Fonts.boldSemi,
),
),
),
),
],
),
],
),
);
},
);
}
/// 打开设置
static Future<void> setting() async => await openAppSettings();
......@@ -36,5 +191,4 @@ abstract class Access {
final result = await [Permission.microphone].request();
return result[Permission.microphone] == PermissionStatus.granted;
}
}
......@@ -4,22 +4,47 @@ abstract class AssetsPicker {
static final ImagePicker _imagePicker = ImagePicker();
/// 获取图库
/// [purpose] 权限使用目的说明,用于在申请权限前告知用户
static Future<XFile?> image({
required BuildContext context,
ImageSource source = ImageSource.gallery,
double maxWidth = 1024,
double maxHeight = 1024,
String purpose = '用于从相册中选择图片,用于设置头像、上传图片等功能。',
}) async {
if (!(await Access.photos())) {
if (context.mounted) {
// CustomDialog.showAccess(
// context: context,
// content: const Text('获取相册权限'),
// );
_showSettingDialog(context,'获取相册权限');
// 先检查权限状态
bool hasPermission = false;
if (Platform.isIOS) {
final status = await Permission.photos.status;
hasPermission = status == PermissionStatus.granted || status == PermissionStatus.limited;
} else if (Platform.isAndroid) {
final status = await Permission.storage.status;
hasPermission = status == PermissionStatus.granted;
}
// 如果没有权限,先显示说明对话框,然后申请权限
if (!hasPermission) {
final granted = await Access.photos(
showExplanation: true,
purpose: purpose,
context: context,
);
if (!granted) {
// 用户拒绝或取消,检查是否需要跳转设置
if (context.mounted) {
final status = Platform.isIOS
? await Permission.photos.status
: await Permission.storage.status;
if (status.isPermanentlyDenied) {
_showSettingDialog(context, '获取相册权限');
}
}
return null;
}
return null;
}
return _imagePicker.pickImage(
source: source,
// maxWidth: maxWidth,
......@@ -27,23 +52,41 @@ abstract class AssetsPicker {
// imageQuality: 100
);
}
/// 拍照
/// [purpose] 权限使用目的说明,用于在申请权限前告知用户
static Future<XFile?> camera({
required BuildContext context,
ImageSource source = ImageSource.camera,
double maxWidth = 512,
double maxHeight = 512,
String purpose = '用于使用相机拍照,用于设置头像、上传图片等功能。',
}) async {
if (!(await Access.photos())) {
if (context.mounted) {
// CustomDialog.showAccess(
// context: context,
// content: const Text('获取拍照权限'),
// );
_showSettingDialog(context,'获取拍照权限');
// 先检查权限状态
final cameraStatus = await Permission.camera.status;
bool hasPermission = cameraStatus == PermissionStatus.granted;
// 如果没有相机权限,先显示说明对话框,然后申请权限
if (!hasPermission) {
final granted = await Access.camera(
showExplanation: true,
purpose: purpose,
context: context,
);
if (!granted) {
// 用户拒绝或取消,检查是否需要跳转设置
if (context.mounted) {
final status = await Permission.camera.status;
if (status.isPermanentlyDenied) {
_showSettingDialog(context, '获取相机权限');
}
}
return null;
}
return null;
}
return _imagePicker.pickImage(
source: source,
// maxWidth: maxWidth,
......
part of utils;
// 服务器地址
// const String kServerUrl = 'http://192.168.11.88:81';
// const String kServerUrl = 'http://8.141.148.247:7421';
const String kServerUrl = 'https://ebook-app.ezijing.com';
const String kHtmlBaseServer = 'http://150.158.138.40:9200';
const String kServerUrl = 'https://project-api.ezijing.com/api/book';
// const String kServerUrl = 'http://172.16.51.175:7421';
const String kHtmlBaseServer = 'https://webapp-pub.ezijing.com/book-app';
const String kLocalToken = 'local_token';
const String kLocalAccessToken = 'local_access_token';
const String kLocalAccount = 'local_account';
const String kLocalPassword = 'local_password';
const String kLocalUserInfo = 'local_user_info';
const String kLocalPrivacyNoticeShown = 'local_privacy_notice_shown';
const String kSearchHistory = 'search_history';
const String kFailOrder = 'failOrder';
const String kNoteTable = 'members_book_notes';
const String kReadTable = 'read_history';
const String kUserAgreement = '/html/agreement/ser_agreement.html';
const String kUserPriAgreement = '/html/agreement/pri_agreement.html';
const String kUserRechargeAgreement = '/html/agreement/rec_agreement.html';
const String kUserAgreement = '$kHtmlBaseServer/agreement/ser_agreement.html';
const String kUserPriAgreement =
'$kHtmlBaseServer/agreement/pri_agreement.html';
const String kUserRechargeAgreement =
'$kHtmlBaseServer/agreement/rec_agreement.html';
// 错题详情页 html
const String kUserWrongDes = '$kHtmlBaseServer/evaluating_wrong.html';
// 阅读页 html
String kReadTestUnderLineBook = '$kHtmlBaseServer/read_unline.html?t=${DateTime.now().millisecondsSinceEpoch}';
String kReadTestUnderLineBook =
'$kHtmlBaseServer/read_unline.html?t=${DateTime.now().millisecondsSinceEpoch}';
const String kReadBook = '$kHtmlBaseServer/read.html';
// 答题页
const String kAnswer = '$kHtmlBaseServer/evaluating.html';
......@@ -33,18 +36,17 @@ const String kReadInfo = '$kHtmlBaseServer/read_info.html';
// 阅读页 图片预览
const String kScaleImage = '$kHtmlBaseServer/read_img.html';
abstract class C {
static const String localAccount = 'storage_account';
static const String localThemeMode = 'storage_theme_mode';
static const String localLanguage = 'storage_lang';
/// 自动主题
static const String themeSystem = '0';
/// 亮色主题
static const String themeLight = '1';
/// 暗色主题
static const String themeDark = '2';
}
\ No newline at end of file
}
......@@ -49,14 +49,6 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.1.18"
azlistview:
dependency: "direct main"
description:
name: azlistview
sha256: "93e865f11777a271b439f0d6b00799c0797e9daeec2e082a2e01373809c4b90d"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.0.0"
badges:
dependency: "direct main"
description:
......@@ -262,14 +254,6 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "3.0.4"
flutter_bugly:
dependency: "direct main"
description:
name: flutter_bugly
sha256: a2d13cb35bedb907cb020ea4862264dcc9044675a49a8313bb3ab2e61af79c77
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.4.4"
flutter_cache_manager:
dependency: "direct main"
description:
......@@ -549,14 +533,6 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.18.0"
ionicons:
dependency: "direct main"
description:
name: ionicons
sha256: "5496bc65a16115ecf05b15b78f494ee4a8869504357668f0a11d689e970523cf"
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.2.2"
js:
dependency: transitive
description:
......@@ -837,14 +813,6 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "6.1.2"
pull_to_refresh_flutter3:
dependency: "direct main"
description:
name: pull_to_refresh_flutter3
sha256: "223a6241067162dc15cf8c46c05af998ce7aa85e0703d8f696101eb1b5629d76"
url: "https://pub.flutter-io.cn"
source: hosted
version: "2.0.1"
rational:
dependency: transitive
description:
......@@ -869,22 +837,6 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.26.0"
screen_protector:
dependency: "direct main"
description:
name: screen_protector
sha256: "541bdcd341de1e38026b5b94cc2a74cd95299d2c51150735165c4b445fa0209a"
url: "https://pub.flutter-io.cn"
source: hosted
version: "1.4.2"
scrollable_positioned_list:
dependency: transitive
description:
name: scrollable_positioned_list
sha256: "9566352ab9ba05794ee6c8864f154afba5d36c5637d0e3e32c615ba4ceb92772"
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.2.3"
shared_preferences:
dependency: "direct main"
description:
......
name: flutter_book
description: 紫荆数智学堂
description: 清控紫荆数智学堂
# The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
......@@ -39,7 +39,6 @@ dependencies:
flutter_localizations:
sdk: flutter
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.2
......@@ -53,11 +52,8 @@ dependencies:
shared_preferences: 2.1.1
# 屏幕适配
flutter_screenutil: 5.8.2
pull_to_refresh_flutter3: 2.0.1
# 加载图片
extended_image: 8.0.2
# 图标库
ionicons: 0.2.2
# 网络
dio: 5.3.3
crypto: 3.0.3
......@@ -97,8 +93,6 @@ dependencies:
package_info_plus: ^4.2.0
# 版本更新
flutter_app_update: ^3.0.4
# 能指定滑动位置的listView
azlistview: ^2.0.0
# 支付宝
tobias: ^3.3.0
# 内购
......@@ -120,12 +114,8 @@ dependencies:
# 安卓
android_id: ^0.3.6
# 防止截屏
# secure_application: ^3.8.0
screen_protector: ^1.4.2
# sentry_flutter: ^7.19.0
flutter_bugly: ^0.4.4
# secure_application: ^3.8.0
# sentry_flutter: ^7.19.0
dev_dependencies:
flutter_test:
......@@ -143,7 +133,6 @@ dev_dependencies:
# The following section is specific to Flutter packages.
flutter:
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
......@@ -151,14 +140,14 @@ flutter:
# To add assets to your application, add an assets section, like this:
assets:
- assets/images/
- assets/html/
- assets/html/assets/css/
- assets/html/assets/fonts/
- assets/html/assets/images/
- assets/html/assets/js/
- assets/html/src/assets/images/
- assets/html/src/assets/images/editor/
- assets/images/
- assets/html/
- assets/html/assets/css/
- assets/html/assets/fonts/
- assets/html/assets/images/
- assets/html/assets/js/
- assets/html/src/assets/images/
- assets/html/src/assets/images/editor/
# An image asset can refer to one or more resolution-specific "variants", see
# https://flutter.dev/assets-and-images/#resolution-aware
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论