Commit 394892ba by Administrator

Merge branch 'feature-2601-sign-in-with-apple' into feature-2601

2 parents 0e242fa1 ec942b2b
......@@ -11,6 +11,20 @@ flutter run -d 00008030-001C75810E42402E --release
flutter run -d 00008140-001068C93AB8801C --release --dart-define=env=pro
flutter run -d c165c6eb6ae36f56bf23091342bfd4641dc2a9f0 --release --dart-define=env=dev --dart-define=version=1.0.21
ipad air
flutter run -d 00008122-001C504036E8401C --dart-define=env=dev --dart-define=version=1.0.26
xcrun simctl list devices
xcrun simctl delete
# 删除所有不可用的模拟器
xcrun simctl delete unavailable
# 删除所有已关闭的模拟器
xcrun simctl delete shutdown
gao 00008130-0010788A2E01001C
yongosng 00008110-000A284C2178801E
......
......@@ -2,18 +2,18 @@
<!-- 网络权限 -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<!--<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />-->
<!--<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />-->
<uses-permission android:name="android.permission.CAMERA" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />
<!--<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />-->
<!--<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />-->
<!--<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />-->
<!--<uses-permission android:name="android.permission.FOREGROUND_SERVICE_LOCATION" />-->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" />
......
......@@ -8,11 +8,11 @@
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
27204D3668BC41D3DFD050DC /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 28C423CBEE34E92DB25C7C53 /* Pods_RunnerTests.framework */; };
1600F8F364E182C3635C3546 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B2450CB3B5E968BD7CC513E6 /* Pods_Runner.framework */; };
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
47A4F228E531F72377EE28BF /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = C57547C68429A7A5C2125CDB /* Pods_Runner.framework */; };
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
9616D648F43B99AC033DF2C6 /* Pods_RunnerTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DC4A6BC3E644F00288FAFDCD /* Pods_RunnerTests.framework */; };
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
......@@ -42,15 +42,15 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
04FE2DFA9FAEE9EA5F187A0C /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
12C8D86897104F41435510DA /* Pods-RunnerTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.debug.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.debug.xcconfig"; sourceTree = "<group>"; };
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
28C423CBEE34E92DB25C7C53 /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
152CA013299636766168ED1E /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
25127E4231AF7C80752C9782 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
331C807B294A618700263BE5 /* RunnerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RunnerTests.swift; sourceTree = "<group>"; };
331C8081294A63A400263BE5 /* RunnerTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = RunnerTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
60028B3654D4964232FDECAE /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
682D901C29EE7A22929D3821 /* Pods-RunnerTests.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.profile.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.profile.xcconfig"; sourceTree = "<group>"; };
743CD0FB5E4D23DE4D8BC926 /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
......@@ -60,16 +60,16 @@
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
BB67BD4E498303EA92403A8B /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
C57547C68429A7A5C2125CDB /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
9A5019F25CF19E1678164379 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
B2450CB3B5E968BD7CC513E6 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
D900F592122DCCA6D37CEE01 /* Runner.entitlements */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = "<group>"; };
DC39D7842EFB981B00D795A8 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Main.strings"; sourceTree = "<group>"; };
DC39D7852EFB981B00D795A8 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/LaunchScreen.strings"; sourceTree = "<group>"; };
DC39D7862EFB985300D795A8 /* Base */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Base; path = Base.lproj/Info.plist; sourceTree = "<group>"; };
DC39D7882EFB987F00D795A8 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = en; path = en.lproj/Info.plist; sourceTree = "<group>"; };
DC39D7892EFB988000D795A8 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "zh-Hans"; path = "zh-Hans.lproj/Info.plist"; sourceTree = "<group>"; };
FB179580220E6F6CB2E26B1B /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
FDBF0D4EACE51939D9D79B3F /* Pods-RunnerTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-RunnerTests.release.xcconfig"; path = "Target Support Files/Pods-RunnerTests/Pods-RunnerTests.release.xcconfig"; sourceTree = "<group>"; };
DC4A6BC3E644F00288FAFDCD /* Pods_RunnerTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_RunnerTests.framework; sourceTree = BUILT_PRODUCTS_DIR; };
E94A65B4132ADF0336017236 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
......@@ -77,7 +77,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
27204D3668BC41D3DFD050DC /* Pods_RunnerTests.framework in Frameworks */,
9616D648F43B99AC033DF2C6 /* Pods_RunnerTests.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
......@@ -85,7 +85,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
47A4F228E531F72377EE28BF /* Pods_Runner.framework in Frameworks */,
1600F8F364E182C3635C3546 /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
......@@ -119,7 +119,7 @@
97C146EF1CF9000F007C117D /* Products */,
331C8082294A63A400263BE5 /* RunnerTests */,
F4367DCDE78D6820D90794C8 /* Pods */,
A31235DB448250F4AF9E50A4 /* Frameworks */,
9F7FE946B3449150A78BC577 /* Frameworks */,
);
sourceTree = "<group>";
};
......@@ -148,11 +148,11 @@
path = Runner;
sourceTree = "<group>";
};
A31235DB448250F4AF9E50A4 /* Frameworks */ = {
9F7FE946B3449150A78BC577 /* Frameworks */ = {
isa = PBXGroup;
children = (
C57547C68429A7A5C2125CDB /* Pods_Runner.framework */,
28C423CBEE34E92DB25C7C53 /* Pods_RunnerTests.framework */,
B2450CB3B5E968BD7CC513E6 /* Pods_Runner.framework */,
DC4A6BC3E644F00288FAFDCD /* Pods_RunnerTests.framework */,
);
name = Frameworks;
sourceTree = "<group>";
......@@ -160,12 +160,12 @@
F4367DCDE78D6820D90794C8 /* Pods */ = {
isa = PBXGroup;
children = (
FB179580220E6F6CB2E26B1B /* Pods-Runner.debug.xcconfig */,
60028B3654D4964232FDECAE /* Pods-Runner.release.xcconfig */,
BB67BD4E498303EA92403A8B /* Pods-Runner.profile.xcconfig */,
04FE2DFA9FAEE9EA5F187A0C /* Pods-RunnerTests.debug.xcconfig */,
FDBF0D4EACE51939D9D79B3F /* Pods-RunnerTests.release.xcconfig */,
682D901C29EE7A22929D3821 /* Pods-RunnerTests.profile.xcconfig */,
25127E4231AF7C80752C9782 /* Pods-Runner.debug.xcconfig */,
E94A65B4132ADF0336017236 /* Pods-Runner.release.xcconfig */,
9A5019F25CF19E1678164379 /* Pods-Runner.profile.xcconfig */,
12C8D86897104F41435510DA /* Pods-RunnerTests.debug.xcconfig */,
743CD0FB5E4D23DE4D8BC926 /* Pods-RunnerTests.release.xcconfig */,
152CA013299636766168ED1E /* Pods-RunnerTests.profile.xcconfig */,
);
path = Pods;
sourceTree = "<group>";
......@@ -177,7 +177,7 @@
isa = PBXNativeTarget;
buildConfigurationList = 331C8087294A63A400263BE5 /* Build configuration list for PBXNativeTarget "RunnerTests" */;
buildPhases = (
98BB316D4DBAEF9065CFD64B /* [CP] Check Pods Manifest.lock */,
E60DBEEF3FE57B54B3F1B783 /* [CP] Check Pods Manifest.lock */,
331C807D294A63A400263BE5 /* Sources */,
331C807F294A63A400263BE5 /* Resources */,
12C8B2DF0818C1515D0F7BB6 /* Frameworks */,
......@@ -196,15 +196,15 @@
isa = PBXNativeTarget;
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
buildPhases = (
2B929ADA9ABA7CA055CE5CCA /* [CP] Check Pods Manifest.lock */,
7B3A759DC90C2AB8746BE38E /* [CP] Check Pods Manifest.lock */,
9740EEB61CF901F6004384FC /* Run Script */,
97C146EA1CF9000F007C117D /* Sources */,
97C146EB1CF9000F007C117D /* Frameworks */,
97C146EC1CF9000F007C117D /* Resources */,
9705A1C41CF9048500538489 /* Embed Frameworks */,
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
A88D5CFE4BE75D17DA401603 /* [CP] Embed Pods Frameworks */,
B2192F345AE5B4363B81DA0E /* [CP] Copy Pods Resources */,
38FB079D5D6F683D1505197A /* [CP] Embed Pods Frameworks */,
173C1BB9430C40079F726D97 /* [CP] Copy Pods Resources */,
);
buildRules = (
);
......@@ -277,60 +277,57 @@
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
2B929ADA9ABA7CA055CE5CCA /* [CP] Check Pods Manifest.lock */ = {
173C1BB9430C40079F726D97 /* [CP] Copy Pods Resources */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
);
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
name = "[CP] Copy Pods Resources";
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
showEnvVarsInLog = 0;
};
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
38FB079D5D6F683D1505197A /* [CP] Embed Pods Frameworks */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
name = "Thin Binary";
outputPaths = (
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
showEnvVarsInLog = 0;
};
9740EEB61CF901F6004384FC /* Run Script */ = {
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputPaths = (
"${TARGET_BUILD_DIR}/${INFOPLIST_PATH}",
);
name = "Run Script";
name = "Thin Binary";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
};
98BB316D4DBAEF9065CFD64B /* [CP] Check Pods Manifest.lock */ = {
7B3A759DC90C2AB8746BE38E /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
......@@ -345,45 +342,48 @@
outputFileListPaths = (
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
A88D5CFE4BE75D17DA401603 /* [CP] Embed Pods Frameworks */ = {
9740EEB61CF901F6004384FC /* Run Script */ = {
isa = PBXShellScriptBuildPhase;
alwaysOutOfDate = 1;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
inputPaths = (
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
name = "Run Script";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
showEnvVarsInLog = 0;
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
};
B2192F345AE5B4363B81DA0E /* [CP] Copy Pods Resources */ = {
E60DBEEF3FE57B54B3F1B783 /* [CP] Check Pods Manifest.lock */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
);
name = "[CP] Copy Pods Resources";
inputPaths = (
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
"${PODS_ROOT}/Manifest.lock",
);
name = "[CP] Check Pods Manifest.lock";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
);
outputPaths = (
"$(DERIVED_FILE_DIR)/Pods-RunnerTests-checkManifestLockResult.txt",
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
......@@ -510,6 +510,8 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 42794B2KBU;
ENABLE_BITCODE = NO;
......@@ -521,6 +523,7 @@
);
PRODUCT_BUNDLE_IDENTIFIER = cn.banxe.appframe;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
......@@ -529,7 +532,7 @@
};
331C8088294A63A400263BE5 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 04FE2DFA9FAEE9EA5F187A0C /* Pods-RunnerTests.debug.xcconfig */;
baseConfigurationReference = 12C8D86897104F41435510DA /* Pods-RunnerTests.debug.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
......@@ -547,7 +550,7 @@
};
331C8089294A63A400263BE5 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = FDBF0D4EACE51939D9D79B3F /* Pods-RunnerTests.release.xcconfig */;
baseConfigurationReference = 743CD0FB5E4D23DE4D8BC926 /* Pods-RunnerTests.release.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
......@@ -563,7 +566,7 @@
};
331C808A294A63A400263BE5 /* Profile */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 682D901C29EE7A22929D3821 /* Pods-RunnerTests.profile.xcconfig */;
baseConfigurationReference = 152CA013299636766168ED1E /* Pods-RunnerTests.profile.xcconfig */;
buildSettings = {
BUNDLE_LOADER = "$(TEST_HOST)";
CODE_SIGN_STYLE = Automatic;
......@@ -701,6 +704,8 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 42794B2KBU;
ENABLE_BITCODE = NO;
......@@ -712,6 +717,7 @@
);
PRODUCT_BUNDLE_IDENTIFIER = cn.banxe.appframe;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
......@@ -726,6 +732,8 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
DEVELOPMENT_TEAM = 42794B2KBU;
ENABLE_BITCODE = NO;
......@@ -737,6 +745,7 @@
);
PRODUCT_BUNDLE_IDENTIFIER = cn.banxe.appframe;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
SWIFT_VERSION = 5.0;
VERSIONING_SYSTEM = "apple-generic";
......
......@@ -64,11 +64,11 @@
</dict>
</dict>
<key>NSCameraUsageDescription</key>
<string>需要访问相机</string>
<string>提交作业需要使用相机</string>
<key>NSMicrophoneUsageDescription</key>
<string>需要麦克风权限用于录音</string>
<string>提交作业需要使用录音</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>需要访问相册选择图片或视频</string>
<string>提交作业需要使用图片或视频</string>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UILaunchStoryboardName</key>
......@@ -90,17 +90,17 @@
</array>
<key>io.flutter.embedded_views_preview</key>
<true/>
<key>NSLocalNetworkUsageDescription</key>
<string>此应用需要访问本地网络以发现和连接智能设备</string>
<!-- <key>NSLocalNetworkUsageDescription</key>
<string>此应用需要访问本地网络以发现和连接智能设备</string> -->
<key>NSBonjourServices</key>
<array>
<string>_dartvm._tcp</string>
<string>_dartobservatory._tcp</string>
</array>
<key>UIBackgroundModes</key>
<!-- <key>UIBackgroundModes</key>
<array>
<string>audio</string>
</array>
</array> -->
<key>UIViewControllerBasedStatusBarAppearance</key>
<true/>
</dict>
......
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10117" systemVersion="15F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="BYZ-38-t0r">
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="24506" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="BYZ-38-t0r">
<device id="retina6_12" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10085"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="24504"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Flutter View Controller-->
......@@ -14,13 +16,14 @@
<viewControllerLayoutGuide type="bottom" id="wfy-db-euE"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="8bC-Xf-vdC">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<rect key="frame" x="0.0" y="0.0" width="393" height="852"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="dkx-z0-nzr" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="139" y="131"/>
</scene>
</scenes>
</document>
......@@ -64,11 +64,11 @@
</dict>
</dict>
<key>NSCameraUsageDescription</key>
<string>需要访问相机</string>
<string>提交作业需要使用相机</string>
<key>NSMicrophoneUsageDescription</key>
<string>需要麦克风权限用于录音</string>
<string>提交作业需要使用录音</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>需要访问相册选择图片或视频</string>
<string>提交作业需要使用图片或视频</string>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UILaunchStoryboardName</key>
......@@ -90,31 +90,32 @@
</array>
<key>io.flutter.embedded_views_preview</key>
<true/>
<key>NSLocalNetworkUsageDescription</key>
<string>此应用需要访问本地网络以发现和连接智能设备</string>
<!-- <key>NSLocalNetworkUsageDescription</key>
<string>此应用需要访问本地网络以发现和连接智能设备</string> -->
<key>NSBonjourServices</key>
<array>
<string>_dartvm._tcp</string>
<string>_dartobservatory._tcp</string>
</array>
<key>UIBackgroundModes</key>
<!-- <key>UIBackgroundModes</key>
<array>
<string>audio</string>
</array>
</array> -->
<key>UIViewControllerBasedStatusBarAppearance</key>
<true/>
<!-- 持续定位权限:用于后台持续获取位置(如导航、运动追踪) -->
<key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>为了在您针对老师布置信息反馈或作业消息提交时,可能需要涉及位置信息的提交要求,我们需要访问您的位置信息</string>
<!-- <key>NSLocationAlwaysAndWhenInUseUsageDescription</key>
<string>为了在您针对老师布置信息反馈或作业消息提交时,可能需要涉及位置信息的提交要求,我们需要访问您的位置信息</string> -->
<!-- 蓝牙权限:用于连接蓝牙设备 -->
<key>NSBluetoothAlwaysUsageDescription</key>
<string>为了搜索、连接并管理您的智能蓝牙设备(如手环、传感器),我们需要使用蓝牙功能</string>
<!-- <key>NSBluetoothAlwaysUsageDescription</key>
<string>为了搜索、连接并管理您的智能蓝牙设备(如手环、传感器),我们需要使用蓝牙功能</string> -->
<!-- 在使用时定位权限:用于应用在前台时获取位置 -->
<key>NSLocationWhenInUseUsageDescription</key>
<!-- <key>NSLocationWhenInUseUsageDescription</key>
<string>为了在您针对老师布置信息反馈或作业消息提交时,可能需要涉及位置信息的提交要求,我们需要在使用应用时获取您的位置信息。</string>
-->
<key>App Uses Non-Exempt Encryption</key>
<false/>
</dict>
......
......@@ -2,6 +2,10 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.developer.applesignin</key>
<array>
<string>Default</string>
</array>
<key>com.apple.developer.associated-domains</key>
<array>
<string>applinks:dev.banxiaoer.net</string>
......
......@@ -64,11 +64,11 @@
</dict>
</dict>
<key>NSCameraUsageDescription</key>
<string>需要访问相机</string>
<string>提交作业需要使用相机</string>
<key>NSMicrophoneUsageDescription</key>
<string>需要麦克风权限用于录音</string>
<string>提交作业需要使用录音</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>需要访问相册选择图片或视频</string>
<string>提交作业需要使用图片或视频</string>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UILaunchStoryboardName</key>
......@@ -90,18 +90,18 @@
</array>
<key>io.flutter.embedded_views_preview</key>
<true/>
<key>NSLocalNetworkUsageDescription</key>
<string>此应用需要访问本地网络以发现和连接智能设备</string>
<!-- <key>NSLocalNetworkUsageDescription</key>
<string>此应用需要访问本地网络以发现和连接智能设备</string> -->
<key>NSBonjourServices</key>
<array>
<string>_dartvm._tcp</string>
<!-- 对于更新的Flutter版本,有时还需要添加以下行 -->
<string>_dartobservatory._tcp</string>
</array>
<key>UIBackgroundModes</key>
<!-- <key>UIBackgroundModes</key>
<array>
<string>audio</string>
</array>
</array> -->
<key>UIViewControllerBasedStatusBarAppearance</key>
<true/>
</dict>
......
......@@ -64,11 +64,11 @@
</dict>
</dict>
<key>NSCameraUsageDescription</key>
<string>需要访问相机</string>
<string>提交作业需要使用相机</string>
<key>NSMicrophoneUsageDescription</key>
<string>需要麦克风权限用于录音</string>
<string>提交作业需要使用录音</string>
<key>NSPhotoLibraryUsageDescription</key>
<string>需要访问相册选择图片或视频</string>
<string>提交作业需要使用图片或视频</string>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>UILaunchStoryboardName</key>
......@@ -90,18 +90,18 @@
</array>
<key>io.flutter.embedded_views_preview</key>
<true/>
<key>NSLocalNetworkUsageDescription</key>
<string>此应用需要访问本地网络以发现和连接智能设备</string>
<!-- <key>NSLocalNetworkUsageDescription</key>
<string>此应用需要访问本地网络以发现和连接智能设备</string> -->
<key>NSBonjourServices</key>
<array>
<string>_dartvm._tcp</string>
<!-- 对于更新的Flutter版本,有时还需要添加以下行 -->
<string>_dartobservatory._tcp</string>
</array>
<key>UIBackgroundModes</key>
<!-- <key>UIBackgroundModes</key>
<array>
<string>audio</string>
</array>
</array> -->
<key>UIViewControllerBasedStatusBarAppearance</key>
<true/>
</dict>
......
import 'package:appframe/config/constant.dart';
import 'package:appframe/config/locator.dart';
import 'package:appframe/config/routes.dart';
import 'package:appframe/data/repositories/user_auth_repository.dart';
import 'package:appframe/data/repositories/wechat_auth_repository.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:fluwx/fluwx.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:sign_in_with_apple/sign_in_with_apple.dart';
class LoginMainState extends Equatable {
final bool agreed;
final bool showAgreed;
final bool showNeedWechatForApple;
final int loginType; // 1=Wechat,2=Apple
final String appleUserIdentifier;
final bool loading;
final bool showSnackBar;
final String snackBarMsg;
final bool wechatInstalled;
const LoginMainState({
this.agreed = false,
this.showAgreed = false,
this.showNeedWechatForApple = false,
this.loginType = 0,
this.appleUserIdentifier = '',
this.loading = false,
this.showSnackBar = false,
this.snackBarMsg = '',
this.wechatInstalled = false,
});
LoginMainState copyWith({
bool? agreed,
bool? showAgreed,
bool? showNeedWechatForApple,
int? loginType,
String? appleUserIdentifier,
bool? loading,
bool? showSnackBar,
String? snackBarMsg,
bool? wechatInstalled,
}) {
return LoginMainState(
agreed: agreed ?? this.agreed,
showAgreed: showAgreed ?? this.showAgreed,
showNeedWechatForApple: showNeedWechatForApple ?? this.showNeedWechatForApple,
loginType: loginType ?? this.loginType,
appleUserIdentifier: appleUserIdentifier ?? this.appleUserIdentifier,
loading: loading ?? this.loading,
wechatInstalled: wechatInstalled ?? this.wechatInstalled,
);
}
......@@ -41,9 +54,11 @@ class LoginMainState extends Equatable {
List<Object?> get props => [
agreed,
showAgreed,
showNeedWechatForApple,
loginType,
appleUserIdentifier,
loading,
showSnackBar,
snackBarMsg,
wechatInstalled,
];
}
......@@ -51,11 +66,19 @@ class LoginMainCubit extends Cubit<LoginMainState> {
late final Fluwx _fluwx;
late final FluwxCancelable _fluwxCancelable;
late final WechatAuthRepository _wechatAuthRepository;
late final UserAuthRepository _userAuthRepository;
LoginMainCubit(super.initialState) {
_fluwx = getIt.get<Fluwx>();
// 处理微信安装检测
_fluwx.isWeChatInstalled.then((value) {
emit(state.copyWith(wechatInstalled: value));
});
_fluwxCancelable = _fluwx.addSubscriber(_responseListener);
_wechatAuthRepository = getIt.get<WechatAuthRepository>();
_userAuthRepository = getIt.get<UserAuthRepository>();
}
void toggleAgreed(bool value) {
......@@ -64,26 +87,108 @@ class LoginMainCubit extends Cubit<LoginMainState> {
void confirmAgreed() {
emit(state.copyWith(agreed: true, showAgreed: false));
if (state.loginType == 1) {
wechatAuth();
} else if (state.loginType == 2) {
appleAuth();
}
}
void cancelAgreed() {
emit(state.copyWith(showAgreed: false));
}
void wechatAuth() async {
///
/// 通过 Apple 登录
///
void appleAuth() async {
emit(state.copyWith(loginType: 2));
if (!state.agreed) {
emit(state.copyWith(showAgreed: true));
return;
}
AuthorizationCredentialAppleID credential = await SignInWithApple.getAppleIDCredential(
scopes: [
AppleIDAuthorizationScopes.email,
AppleIDAuthorizationScopes.fullName,
],
);
debugPrint('用户唯一标识: ${credential.userIdentifier}');
debugPrint('用户邮箱: ${credential.email}');
debugPrint('用户姓名: ${credential.givenName} ${credential.familyName}');
// 应将 credential.authorizationCode 发送给后端服务器,由后端与 Apple 服务器验证该码的有效性
debugPrint('授权码 (authorizationCode): ${credential.authorizationCode}');
debugPrint('身份令牌 (identityToken): ${credential.identityToken}');
if (credential.userIdentifier == null) {
Fluttertoast.showToast(msg: '授权失败', backgroundColor: Colors.red);
return;
}
///
/// 处理 credential,发送到服务器获取用户信息。有关联用户信息,则登录成功;没有关联用户信息,则弹出提示框提示用户授权微信认证
///
var resultData = await _userAuthRepository.appleLogin(
credential.userIdentifier!, credential.authorizationCode, credential.identityToken!) as Map<String, dynamic>?;
if (resultData == null) {
Fluttertoast.showToast(msg: '登录请求处理失败', backgroundColor: Colors.red);
return;
}
if (resultData['code'] != 0) {
Fluttertoast.showToast(msg: resultData['error'], backgroundColor: Colors.red);
return;
}
var data = resultData['data'] as Map<String, dynamic>;
int binding = resultData['binding'];
if (binding == 1) {
_handleLoginSuccess(data);
} else {
// 未绑定时,也会返回 sessionCode 和 userCode
if (state.wechatInstalled) {
// 已安装微信APP,直接拉起微信授权,不使用 sessionCode 和 userCode
// 设置 appleUserIdentifier 状态,通知用户需要授权微信认证
emit(state.copyWith(appleUserIdentifier: data['appleUid']!, showNeedWechatForApple: true));
} else {
// 未安装微信APP,使用 sessionCode 和 userCode
_handleLoginSuccess(data);
}
}
}
Future<void> wechatAuthForApple() async {
emit(state.copyWith(showNeedWechatForApple: false, loginType: 2));
var authResult = await _fluwx.authBy(
which: NormalAuth(scope: 'snsapi_userinfo', state: 'wechat_sdk_test'),
);
if (!authResult) {
emit(state.copyWith(showSnackBar: true, snackBarMsg: '微信授权处理失败'));
emit(state.copyWith(showSnackBar: false));
Fluttertoast.showToast(msg: '微信授权处理失败', backgroundColor: Colors.red);
return;
}
// 控制显示加载框
emit(state.copyWith(loading: true));
}
Future<void> wechatAuth() async {
emit(state.copyWith(loginType: 1));
if (!state.agreed) {
emit(state.copyWith(showAgreed: true));
return;
}
var authResult = await _fluwx.authBy(
which: NormalAuth(scope: 'snsapi_userinfo', state: 'wechat_sdk_test'),
);
if (!authResult) {
Fluttertoast.showToast(msg: '微信授权处理失败', backgroundColor: Colors.red);
return;
}
......@@ -110,28 +215,38 @@ class LoginMainCubit extends Cubit<LoginMainState> {
// 请求接口异常
if (resultData == null) {
emit(state.copyWith(loading: false, showSnackBar: true, snackBarMsg: '登录请求处理失败'));
emit(state.copyWith(showSnackBar: false));
Fluttertoast.showToast(msg: '登录请求处理失败', backgroundColor: Colors.red);
return;
}
// 状态码错误
if (resultData['resultCode'] != '001') {
emit(state.copyWith(loading: false, showSnackBar: true, snackBarMsg: '登录请求状态失败'));
emit(state.copyWith(showSnackBar: false));
Fluttertoast.showToast(msg: '登录请求状态失败', backgroundColor: Colors.red);
return;
}
var data = resultData['data'] as Map<String, dynamic>;
_handleLoginSuccess(data);
}
}
void _handleLoginSuccess(Map<String, dynamic> data) {
var roles = data['roles'];
// 过滤出家长角色的数据
if (roles?.isNotEmpty ?? false) {
roles.removeWhere((element) => element['userType'] != 2);
} else {
roles = [];
}
var sessionCode = data['sessionCode'];
var userCode = data['userCode'];
var classCode = '';
var userType = 0;
var stuId = '';
var className = '';
var stuName = '';
var relation = '';
var sharedPreferences = getIt.get<SharedPreferences>();
......@@ -140,8 +255,10 @@ class LoginMainCubit extends Cubit<LoginMainState> {
classCode = role['classCode'];
userType = role['userType'];
stuId = role['stuId'];
className = role['className'];
stuName = role['stuName'];
relation = role['relation'] ?? '';
// 将角色中的班级数据处理后,进行缓存
List<String> classIdList = [];
for (var role in roles) {
classIdList.add(role['classCode'] as String);
......@@ -154,13 +271,11 @@ class LoginMainCubit extends Cubit<LoginMainState> {
var preUserCode = sharedPreferences.getString('pre_userCode') ?? '';
if (userCode != preUserCode) {
// 新用户登录
sharedPreferences.setString('pre_userCode', userCode);
sharedPreferences.setString('pre_classCode', classCode);
sharedPreferences.setInt('pre_userType', userType);
sharedPreferences.setString('pre_stuId', stuId);
} else {
// 前一个登录用户重新登录
var preClassCode = sharedPreferences.getString('pre_classCode') ?? '';
var preUserType = sharedPreferences.getInt('pre_userType') ?? 0;
var preStuId = sharedPreferences.getString('pre_stuId') ?? '';
......@@ -186,6 +301,16 @@ class LoginMainCubit extends Cubit<LoginMainState> {
sharedPreferences.setString('auth_classCode', classCode);
sharedPreferences.setInt('auth_userType', userType);
sharedPreferences.setString('auth_stuId', stuId);
sharedPreferences.setString('auth_className', className);
sharedPreferences.setString('auth_stuName', stuName);
sharedPreferences.setString('auth_relation', relation);
debugPrint('loginType: ${state.loginType} appleUid: ${state.appleUserIdentifier}');
// 针对 Apple 登录
if (state.loginType == 2 && state.appleUserIdentifier.isNotEmpty) {
// appleUserIdentifier未绑定,则进行绑定
_userAuthRepository.newBinding(state.appleUserIdentifier, userCode);
}
router.go(
'/web',
......@@ -198,7 +323,6 @@ class LoginMainCubit extends Cubit<LoginMainState> {
},
);
}
}
@override
Future<void> close() {
......
......@@ -7,22 +7,19 @@ import 'package:appframe/data/repositories/phone_auth_repository.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:shared_preferences/shared_preferences.dart';
class LoginPhoneState extends Equatable {
final bool agreed;
final bool showAgreed;
final bool showSnackBar;
final String snackBarMsg;
final bool allowSend;
final int seconds;
const LoginPhoneState({
this.agreed = false,
this.showAgreed = false,
this.showSnackBar = false,
this.snackBarMsg = '',
this.allowSend = true,
this.seconds = 0,
});
......@@ -30,16 +27,12 @@ class LoginPhoneState extends Equatable {
LoginPhoneState copyWith({
bool? agreed,
bool? showAgreed,
bool? showSnackBar,
String? snackBarMsg,
bool? allowSend,
int? seconds,
}) {
return LoginPhoneState(
agreed: agreed ?? this.agreed,
showAgreed: showAgreed ?? this.showAgreed,
showSnackBar: showSnackBar ?? this.showSnackBar,
snackBarMsg: snackBarMsg ?? this.snackBarMsg,
allowSend: allowSend ?? this.allowSend,
seconds: seconds ?? this.seconds,
);
......@@ -49,8 +42,6 @@ class LoginPhoneState extends Equatable {
List<Object?> get props => [
agreed,
showAgreed,
showSnackBar,
snackBarMsg,
allowSend,
seconds,
];
......@@ -100,16 +91,15 @@ class LoginPhoneCubit extends Cubit<LoginPhoneState> {
// 验证手机号码
String phone = _phoneController.text;
if (!RegExp(r'^1[3-9][0-9]{9}$').hasMatch(phone)) {
emit(state.copyWith(showSnackBar: true, snackBarMsg: '请输入正确的手机号码'));
emit(state.copyWith(showSnackBar: false));
Fluttertoast.showToast(msg: '请输入正确的手机号码', backgroundColor: Colors.red);
return;
}
// 发送验证码
var result = await _phoneAuthRepository.verifyCode(phone, 0);
if (result['code'] != 0) {
emit(state.copyWith(showSnackBar: true, snackBarMsg: result['error']));
emit(state.copyWith(showSnackBar: false));
Fluttertoast.showToast(msg: result['error'], backgroundColor: Colors.red);
return;
}
......@@ -125,14 +115,12 @@ class LoginPhoneCubit extends Cubit<LoginPhoneState> {
String verifyCode = _codeController.text;
if (!RegExp(r'^1[3-9][0-9]{9}$').hasMatch(phone)) {
emit(state.copyWith(showSnackBar: true, snackBarMsg: '请输入正确的手机号码'));
emit(state.copyWith(showSnackBar: false));
Fluttertoast.showToast(msg: '请输入正确的手机号码', backgroundColor: Colors.red);
return;
}
if (!RegExp(r'^\d{4}$').hasMatch(verifyCode)) {
emit(state.copyWith(showSnackBar: true, snackBarMsg: '请输入正确的验证码'));
emit(state.copyWith(showSnackBar: false));
Fluttertoast.showToast(msg: '请输入正确的验证码', backgroundColor: Colors.red);
return;
}
......@@ -143,26 +131,35 @@ class LoginPhoneCubit extends Cubit<LoginPhoneState> {
var resultData = await _phoneAuthRepository.login(phone, verifyCode);
if (resultData == null) {
emit(state.copyWith(showSnackBar: true, snackBarMsg: '登录请求失败'));
emit(state.copyWith(showSnackBar: false));
Fluttertoast.showToast(msg: '登录请求失败', backgroundColor: Colors.red);
return;
}
if (resultData['code'] != 0) {
emit(state.copyWith(showSnackBar: true, snackBarMsg: resultData['error']));
emit(state.copyWith(showSnackBar: false));
Fluttertoast.showToast(msg: resultData['error'], backgroundColor: Colors.red);
return;
}
var data = resultData['data'] as Map<String, dynamic>;
_handleLoginSuccess(data);
}
void _handleLoginSuccess(Map<String, dynamic> data) {
var roles = data['roles'];
// 过滤出家长角色的数据
if (roles?.isNotEmpty ?? false) {
roles.removeWhere((element) => element['userType'] != 2);
} else {
roles = [];
}
var sessionCode = data['sessionCode'];
var userCode = data['userCode'];
var classCode = '';
var userType = 0;
var stuId = '';
var className = '';
var stuName = '';
var relation = '';
var sharedPreferences = getIt.get<SharedPreferences>();
......@@ -171,6 +168,9 @@ class LoginPhoneCubit extends Cubit<LoginPhoneState> {
classCode = role['classCode'];
userType = role['userType'];
stuId = role['stuId'];
className = role['className'];
stuName = role['stuName'];
relation = role['relation'] ?? '';
// 将角色中的班级数据处理后,进行缓存
List<String> classIdList = [];
......@@ -217,6 +217,9 @@ class LoginPhoneCubit extends Cubit<LoginPhoneState> {
sharedPreferences.setString('auth_classCode', classCode);
sharedPreferences.setInt('auth_userType', userType);
sharedPreferences.setString('auth_stuId', stuId);
sharedPreferences.setString('auth_className', className);
sharedPreferences.setString('auth_stuName', stuName);
sharedPreferences.setString('auth_relation', relation);
router.go(
'/web',
......
import 'dart:convert';
import 'dart:typed_data';
import 'package:appframe/config/constant.dart';
import 'package:appframe/config/locator.dart';
......@@ -8,7 +7,9 @@ import 'package:appframe/data/repositories/wechat_auth_repository.dart';
import 'package:crypto/crypto.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:fluwx/fluwx.dart';
import 'package:shared_preferences/shared_preferences.dart';
......@@ -16,30 +17,22 @@ class LoginQrState extends Equatable {
final int status;
final Uint8List? image;
final String tip;
final bool showSnackBar;
final String snackBarMsg;
const LoginQrState({
this.status = 0,
this.image,
this.tip = '',
this.showSnackBar = false,
this.snackBarMsg = '',
});
LoginQrState copyWith({
int? status,
Uint8List? image,
String? tip,
bool? showSnackBar,
String? snackBarMsg,
}) {
return LoginQrState(
status: status ?? this.status,
image: image ?? this.image,
tip: tip ?? this.tip,
showSnackBar: showSnackBar ?? this.showSnackBar,
snackBarMsg: snackBarMsg ?? this.snackBarMsg,
);
}
......@@ -48,8 +41,6 @@ class LoginQrState extends Equatable {
status,
image,
tip,
showSnackBar,
snackBarMsg,
];
}
......@@ -71,15 +62,13 @@ class LoginQrCubit extends Cubit<LoginQrState> {
var resultData = await _wechatAuthRepository.getTicket() as Map<String, dynamic>?;
// 请求接口异常
if (resultData == null) {
emit(state.copyWith(showSnackBar: true, snackBarMsg: '生成二维码失败'));
emit(state.copyWith(showSnackBar: false));
Fluttertoast.showToast(msg: '生成二维码失败', backgroundColor: Colors.red);
return;
}
// 状态码错误
if (resultData['resultCode'] != '001') {
emit(state.copyWith(showSnackBar: true, snackBarMsg: '生成二维码状态错误'));
emit(state.copyWith(showSnackBar: false));
Fluttertoast.showToast(msg: '生成二维码状态错误', backgroundColor: Colors.red);
return;
}
......@@ -100,8 +89,7 @@ class LoginQrCubit extends Cubit<LoginQrState> {
);
if (!authResult) {
emit(state.copyWith(showSnackBar: true, snackBarMsg: '请求微信失败'));
emit(state.copyWith(showSnackBar: false));
Fluttertoast.showToast(msg: '请求微信失败', backgroundColor: Colors.red);
return;
}
}
......@@ -151,28 +139,37 @@ class LoginQrCubit extends Cubit<LoginQrState> {
// 请求接口异常
if (resultData == null) {
emit(state.copyWith(showSnackBar: true, snackBarMsg: '登录请求处理失败'));
emit(state.copyWith(showSnackBar: false));
Fluttertoast.showToast(msg: '登录请求处理失败', backgroundColor: Colors.red);
return;
}
// 状态码错误
if (resultData['resultCode'] != '001') {
emit(state.copyWith(showSnackBar: true, snackBarMsg: '登录请求状态失败'));
emit(state.copyWith(showSnackBar: false));
Fluttertoast.showToast(msg: '登录请求状态失败', backgroundColor: Colors.red);
return;
}
var data = resultData['data'] as Map<String, dynamic>;
_handleLoginSuccess(data);
}
void _handleLoginSuccess(Map<String, dynamic> data) {
var roles = data['roles'];
// 过滤出家长角色的数据
if (roles?.isNotEmpty ?? false) {
roles.removeWhere((element) => element['userType'] != 2);
} else {
roles = [];
}
var sessionCode = data['sessionCode'];
var userCode = data['userCode'];
var classCode = '';
var userType = 0;
var stuId = '';
var className = '';
var stuName = '';
var relation = '';
var sharedPreferences = getIt.get<SharedPreferences>();
......@@ -181,6 +178,9 @@ class LoginQrCubit extends Cubit<LoginQrState> {
classCode = role['classCode'];
userType = role['userType'];
stuId = role['stuId'];
className = role['className'];
stuName = role['stuName'];
relation = role['relation'];
// 将角色中的班级数据处理后,进行缓存
List<String> classIdList = [];
......@@ -227,6 +227,9 @@ class LoginQrCubit extends Cubit<LoginQrState> {
sharedPreferences.setString('auth_classCode', classCode);
sharedPreferences.setInt('auth_userType', userType);
sharedPreferences.setString('auth_stuId', stuId);
sharedPreferences.setString('auth_className', className);
sharedPreferences.setString('auth_stuName', stuName);
sharedPreferences.setString('auth_relation', relation);
router.go(
'/web',
......
......@@ -12,12 +12,22 @@ class AccountState extends Equatable {
final String nickname;
final String imgIcon;
// 孩子信息
final String className;
final String stuName;
// 是否绑定孩子
final bool bindStu;
const AccountState({
this.loaded = false,
this.name = '',
this.phone = '',
this.nickname = '',
this.imgIcon = '',
this.className = '',
this.stuName = '',
this.bindStu = true,
});
AccountState copyWith({
......@@ -26,6 +36,9 @@ class AccountState extends Equatable {
String? phone,
String? nickname,
String? imgIcon,
String? className,
String? stuName,
bool? bindStu,
}) {
return AccountState(
loaded: loaded ?? this.loaded,
......@@ -33,6 +46,9 @@ class AccountState extends Equatable {
phone: phone ?? this.phone,
nickname: nickname ?? this.nickname,
imgIcon: imgIcon ?? this.imgIcon,
className: className ?? this.className,
stuName: stuName ?? this.stuName,
bindStu: bindStu ?? this.bindStu,
);
}
......@@ -43,6 +59,9 @@ class AccountState extends Equatable {
phone,
nickname,
imgIcon,
className,
stuName,
bindStu,
];
}
......@@ -57,11 +76,14 @@ class AccountCubit extends Cubit<AccountState> {
Future<void> init() async {
var sharedPreferences = getIt.get<SharedPreferences>();
var userCode = sharedPreferences.getString('auth_userCode') ?? '';
var className = sharedPreferences.getString('auth_className') ?? '';
var stuName = sharedPreferences.getString('auth_stuName') ?? '';
try {
var result = await _phoneAuthRepository.bindCheck(userCode);
var code = result['code'];
var data = result['data'];
if (code != 0) {
emit(state.copyWith(loaded: true, bindStu: false));
return;
}
......@@ -72,6 +94,8 @@ class AccountCubit extends Cubit<AccountState> {
phone: data['phone'],
nickname: data['nickname'],
imgIcon: data['imgIcon'],
className: className,
stuName: stuName,
),
);
} catch (e) {
......
......@@ -5,7 +5,7 @@ import 'package:appframe/config/constant.dart';
import 'package:appframe/config/env_config.dart';
import 'package:appframe/config/locator.dart';
import 'package:appframe/config/routes.dart';
import 'package:appframe/data/repositories/phone_auth_repository.dart';
import 'package:appframe/data/repositories/user_auth_repository.dart';
import 'package:appframe/services/api_service.dart';
import 'package:dio/dio.dart';
import 'package:equatable/equatable.dart';
......@@ -68,7 +68,7 @@ class AccountUserCubit extends Cubit<AccountUserState> {
late TextEditingController _nameController;
late TextEditingController _nickNameController;
late final PhoneAuthRepository _phoneAuthRepository;
late final UserAuthRepository _userAuthRepository;
TextEditingController get nameController => _nameController;
......@@ -78,7 +78,7 @@ class AccountUserCubit extends Cubit<AccountUserState> {
_nameController = TextEditingController(text: state.name);
_nickNameController = TextEditingController(text: state.nickname);
_phoneAuthRepository = getIt.get<PhoneAuthRepository>();
_userAuthRepository = getIt.get<UserAuthRepository>();
}
void updateAvatar(String avatarPath) {
......@@ -110,7 +110,7 @@ class AccountUserCubit extends Cubit<AccountUserState> {
var sharedPreferences = getIt.get<SharedPreferences>();
var userCode = sharedPreferences.getString('auth_userCode') ?? '';
var result = await _phoneAuthRepository.updateUser(userCode, name, nickname, imgPath);
var result = await _userAuthRepository.updateUser(userCode, name, nickname, imgPath);
emit(state.copyWith(isLoading: false));
......
import 'dart:io';
import 'package:appframe/config/constant.dart';
import 'package:appframe/config/locator.dart';
import 'package:appframe/config/routes.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:fluwx/fluwx.dart';
import 'package:path_provider/path_provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
class SettingState extends Equatable {
final String h5Version;
final bool wechatInstalled;
const SettingState({
this.h5Version = '',
this.wechatInstalled = false,
});
SettingState copyWith({
String? h5Version,
bool? wechatInstalled,
}) {
return SettingState(
h5Version: h5Version ?? this.h5Version,
wechatInstalled: wechatInstalled ?? this.wechatInstalled,
);
}
@override
List<Object?> get props => [h5Version, wechatInstalled];
}
class SettingCubit extends Cubit<SettingState> {
final SharedPreferences _prefs = getIt.get<SharedPreferences>();
final Fluwx _fluwx = getIt.get<Fluwx>();
SettingCubit() : super(const SettingState()) {
_init();
}
void _init() {
_readH5ShowVersion();
_checkWechat();
}
void _readH5ShowVersion() {
var h5Version = _prefs.getString(Constant.h5ShowVersionKey) ?? 'unknown';
emit(state.copyWith(h5Version: h5Version));
}
// 微信安装检测
void _checkWechat() {
_fluwx.isWeChatInstalled.then((value) {
emit(state.copyWith(wechatInstalled: value));
});
}
/// 跳转客服(微信小程序)
void goCs() {
_fluwx.open(
target: MiniProgram(
username: 'gh_0ed02e873abc',
path: '/pages/agentChat/index?showAuthDirectly=1&agentId=eiXH0MAJmjgl',
miniProgramType: WXMiniProgramType.release,
),
);
}
/// 退出登录
Future<void> logout() async {
// 删除所有auth_开头的key
_prefs.getKeys().forEach((key) {
if (key.startsWith('auth_')) {
_prefs.remove(key);
}
});
// IM 登出
// await getIt.get<ImService>().logout();
router.go('/loginMain');
}
/// 切换日志模式
Future<void> handleToggleDebug() async {
var debug = _prefs.getInt('debug') ?? 0;
debug = (debug == 0 ? 1 : 0);
_prefs.setInt('debug', debug);
// 通知 WebCubit 更新 H5 状态
WebCubitHolder.instance?.notifyDebugStatus(debug);
}
/// 清理缓存
Future<void> clearStorage() async {
// 1 清理 WebView 相关缓存
WebCubitHolder.instance?.clearWebCache();
// 2 清理非 h5_version 的缓存
_prefs.getKeys().forEach((key) async {
if (!key.startsWith('h5')) {
await _prefs.remove(key);
}
});
// 3 清理 http_dist_assets 下的非当前版本号的文件和目录
var dir = await getApplicationSupportDirectory();
var httpDir = Directory('${dir.path}/${Constant.h5DistDir}');
if (httpDir.existsSync()) {
var version = _prefs.getString(Constant.h5VersionKey) ?? Constant.h5Version;
await for (final entity in httpDir.list()) {
if (entity is Directory) {
if (!entity.path.endsWith(version)) {
await entity.delete(recursive: true);
}
} else if (entity is File) {
if (!entity.path.endsWith('$version.zip')) {
await entity.delete();
}
}
}
}
// 4 清理临时目录下的所有文件和目录
var tempDir = await getTemporaryDirectory();
if (tempDir.existsSync()) {
await for (final entity in tempDir.list()) {
if (entity is Directory) {
await entity.delete(recursive: true);
} else {
await entity.delete();
}
}
}
}
}
......@@ -17,6 +17,7 @@ import 'package:dio/dio.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:fluwx/fluwx.dart';
import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart';
......@@ -66,12 +67,6 @@ class WebState extends Equatable {
final bool chooseVideoCmdFlag;
final String chooseVideoCmdMessage;
/// 提示信息
final bool showSnackBar;
final String snackBarMsg;
final String h5Version;
/// 用于测试监测问题
final String testMsg;
......@@ -99,9 +94,6 @@ class WebState extends Equatable {
this.chooseImageCmdMessage = '',
this.chooseVideoCmdFlag = false,
this.chooseVideoCmdMessage = '',
this.showSnackBar = false,
this.snackBarMsg = '',
this.h5Version = '',
this.testMsg = '',
});
......@@ -130,9 +122,6 @@ class WebState extends Equatable {
String? chooseImageCmdMessage,
bool? chooseVideoCmdFlag,
String? chooseVideoCmdMessage,
bool? showSnackBar,
String? snackBarMsg,
String? h5Version,
String? testMsg,
}) {
return WebState(
......@@ -159,9 +148,6 @@ class WebState extends Equatable {
chooseImageCmdMessage: chooseImageCmdMessage ?? this.chooseImageCmdMessage,
chooseVideoCmdFlag: chooseVideoCmdFlag ?? this.chooseVideoCmdFlag,
chooseVideoCmdMessage: chooseVideoCmdMessage ?? this.chooseVideoCmdMessage,
showSnackBar: showSnackBar ?? this.showSnackBar,
snackBarMsg: snackBarMsg ?? this.snackBarMsg,
h5Version: h5Version ?? this.h5Version,
testMsg: testMsg ?? this.testMsg,
);
}
......@@ -190,9 +176,6 @@ class WebState extends Equatable {
chooseImageCmdMessage,
chooseVideoCmdFlag,
chooseVideoCmdMessage,
showSnackBar,
snackBarMsg,
h5Version,
testMsg,
];
}
......@@ -200,7 +183,6 @@ class WebState extends Equatable {
class WebCubit extends Cubit<WebState> with WidgetsBindingObserver {
late final MessageDispatcher _dispatcher;
late final WebViewController _controller;
late final Fluwx _fluwx;
HttpServer? _server;
PlayerService? _playerService;
RecorderService? _recorderService;
......@@ -270,11 +252,7 @@ class WebCubit extends Cubit<WebState> with WidgetsBindingObserver {
// 加载H5页面
_loadHtml();
// 读取 h5 版本号
_readH5ShowVersion();
// 初始化其它一些属性
_fluwx = getIt.get<Fluwx>();
_playerService = getIt.get<PlayerService>();
_playerService?.sendResponse = _sendResponse;
_recorderService = getIt.get<RecorderService>();
......@@ -282,11 +260,9 @@ class WebCubit extends Cubit<WebState> with WidgetsBindingObserver {
// 登录IM
_loginIM();
// 针对ios系统,处理侧滑返回
if (Platform.isIOS) {
// 注册 WebCubit 实例,供其它页面使用
WebCubitHolder.register(this);
}
}
Future<Map<String, String>> _getVersionConfig() async {
Dio dio = Dio();
......@@ -396,11 +372,6 @@ class WebCubit extends Cubit<WebState> with WidgetsBindingObserver {
_controller.loadRequest(Uri.parse(serverUrl));
}
void _readH5ShowVersion() {
var h5Version = getIt.get<SharedPreferences>().getString(Constant.h5ShowVersionKey) ?? 'unknown';
emit(state.copyWith(h5Version: h5Version));
}
Future<void> _loginIM() async {
if (Constant.needIM) {
var imService = getIt.get<ImService>();
......@@ -487,16 +458,24 @@ class WebCubit extends Cubit<WebState> with WidgetsBindingObserver {
}
///
/// 跳转客服(微信小程序)
/// 清理 WebView 缓存
///
void goCs() {
_fluwx.open(
target: MiniProgram(
username: 'gh_0ed02e873abc',
path: '/pages/agentChat/index?showAuthDirectly=1&agentId=eiXH0MAJmjgl',
miniProgramType: WXMiniProgramType.release,
),
);
Future<void> clearWebCache() async {
_controller.clearLocalStorage();
_controller.clearCache();
}
///
/// 通知 H5 更新调试模式
///
void notifyDebugStatus(int debug) {
var resp = {
'unique': '',
'cmd': 'toggleDebug',
'data': {'debug': debug},
'errMsg': ''
};
_sendResponse(resp);
}
void goLogin() {
......@@ -536,21 +515,6 @@ class WebCubit extends Cubit<WebState> with WidgetsBindingObserver {
_sendResponse(resp);
}
Future<void> handleToggleDebug() async {
var sharedPreferences = getIt.get<SharedPreferences>();
var debug = sharedPreferences.getInt('debug') ?? 0;
debug = (debug == 0 ? 1 : 0);
sharedPreferences.setInt('debug', debug);
var resp = {
'unique': '',
'cmd': 'toggleDebug',
'data': {'debug': debug},
'errMsg': ''
};
_sendResponse(resp);
}
bool setTitleBar(String title, String color, String bgColor, String icon) {
int parsedTitleColor = _hexStringToInt(color);
int parsedBgColor = _hexStringToInt(bgColor);
......@@ -584,68 +548,6 @@ class WebCubit extends Cubit<WebState> with WidgetsBindingObserver {
router.push('/account');
}
Future<void> clearStorage() async {
// 1 清理 localStorage
_controller.clearLocalStorage();
_controller.clearCache();
// 2 清理非 h5_version 的缓存
var sharedPreferences = getIt.get<SharedPreferences>();
sharedPreferences.getKeys().forEach((key) async {
if (!key.startsWith('h5')) {
await sharedPreferences.remove(key);
}
});
// 3 清理 http_dist_assets 下的非当前版本号的文件和目录
var dir = await getApplicationSupportDirectory();
var httpDir = Directory('${dir.path}/${Constant.h5DistDir}');
if (httpDir.existsSync()) {
var version = sharedPreferences.getString(Constant.h5VersionKey) ?? Constant.h5Version;
await for (final FileSystemEntity entity in httpDir.list()) {
if (entity is Directory) {
// 删除目录
if (!entity.path.endsWith(version)) {
await entity.delete(recursive: true);
}
} else if (entity is File) {
// 删除文件
if (!entity.path.endsWith('$version.zip')) {
await entity.delete();
}
}
}
}
// 4 清理临时目录下的所有文件和目录
var tempDir = await getTemporaryDirectory();
if (tempDir.existsSync()) {
await for (final FileSystemEntity entity in tempDir.list()) {
if (entity is Directory) {
await entity.delete(recursive: true);
} else {
await entity.delete();
}
}
}
}
Future<void> logout() async {
// 删除所有auth_开头的key
var sharedPreferences = getIt.get<SharedPreferences>();
sharedPreferences.getKeys().forEach((key) {
if (key.startsWith('auth_')) {
sharedPreferences.remove(key);
}
});
// IM 登出
// await getIt.get<ImService>().logout();
goLogin();
}
void updateSelectedIndex(int index) {
emit(state.copyWith(selectedIndex: index));
}
......@@ -1128,12 +1030,22 @@ class WebCubit extends Cubit<WebState> with WidgetsBindingObserver {
var vol = await volumeController.getVolume();
debugPrint('检测音量: $vol');
if (vol == 0) {
emit(state.copyWith(showSnackBar: true, snackBarMsg: '设备处于静音状态,请调整音量'));
emit(state.copyWith(showSnackBar: false, snackBarMsg: ''));
// emit(state.copyWith(showSnackBar: true, snackBarMsg: '设备处于静音状态,请调整音量'));
// emit(state.copyWith(showSnackBar: false, snackBarMsg: ''));
Fluttertoast.showToast(
msg: '设备处于静音状态,请调整音量',
backgroundColor: Colors.red,
gravity: ToastGravity.TOP,
);
_isFirstTimeOfCheck = false;
} else if (vol <= 0.15) {
emit(state.copyWith(showSnackBar: true, snackBarMsg: '设备音量过低,建议调高音量'));
emit(state.copyWith(showSnackBar: false, snackBarMsg: ''));
// emit(state.copyWith(showSnackBar: true, snackBarMsg: '设备音量过低,建议调高音量'));
// emit(state.copyWith(showSnackBar: false, snackBarMsg: ''));
Fluttertoast.showToast(
msg: '设备音量过低,建议调高音量',
backgroundColor: Colors.red,
gravity: ToastGravity.TOP,
);
_isFirstTimeOfCheck = false;
}
}
......@@ -1222,9 +1134,7 @@ class WebCubit extends Cubit<WebState> with WidgetsBindingObserver {
await _playerService?.close();
await _recorderService?.close();
if (Platform.isIOS) {
WebCubitHolder.unregister();
}
// 移除观察者
WidgetsBinding.instance.removeObserver(this);
......
......@@ -75,9 +75,12 @@ class Constant {
/// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///
static const String bxeBaseUrl = EnvConfig.env == 'dev' ? 'https://dev.banxiaoer.net' : 'https://bxr.banxiaoer.com';
static const String iotAppBaseUrl =
EnvConfig.env == 'dev' ? 'https://iotapp-dev.banxiaoer.com/iotapp' : 'https://iotapp.banxiaoer.com/iotapp';
// static const String iotAppBaseUrl ='http://192.168.2.124:8080/iotapp';
/// 微信登录相关
/// /////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///
......
......@@ -34,6 +34,7 @@ import 'package:appframe/data/repositories/message/video_info_handler.dart';
import 'package:appframe/data/repositories/message/wifi_info_handler.dart';
import 'package:appframe/data/repositories/message/window_info_handler.dart';
import 'package:appframe/data/repositories/phone_auth_repository.dart';
import 'package:appframe/data/repositories/user_auth_repository.dart';
import 'package:appframe/data/repositories/wechat_auth_repository.dart';
import 'package:appframe/services/api_service.dart';
import 'package:appframe/services/dispatcher.dart';
......@@ -219,4 +220,5 @@ Future<void> setupLocator() async {
/// repository
getIt.registerLazySingleton<WechatAuthRepository>(() => WechatAuthRepository());
getIt.registerLazySingleton<PhoneAuthRepository>(() => PhoneAuthRepository());
getIt.registerLazySingleton<UserAuthRepository>(() => UserAuthRepository());
}
......@@ -13,6 +13,7 @@ import 'package:appframe/ui/pages/setting/account_logoff_page.dart';
import 'package:appframe/ui/pages/setting/account_page.dart';
import 'package:appframe/ui/pages/setting/account_phone_page.dart';
import 'package:appframe/ui/pages/setting/account_user_page.dart';
import 'package:appframe/ui/pages/setting/setting_page.dart';
import 'package:appframe/ui/pages/web_page.dart';
import 'package:appframe/ui/widgets/ios_edge_swipe_detector.dart';
import 'package:flutter/material.dart';
......@@ -100,6 +101,12 @@ final GoRouter router = GoRouter(
return const ReloadPage();
},
),
GoRoute(
path: '/setting',
builder: (BuildContext context, GoRouterState state) {
return const SettingPage();
},
),
],
);
......@@ -158,7 +165,7 @@ class AppRouteObserver extends NavigatorObserver {
}
///
/// 只针对iOS使用
/// 用于全局访问 WebCubit 实例
///
class WebCubitHolder {
static WebCubit? instance;
......
......@@ -13,12 +13,18 @@ class RoleInfoHandler extends MessageHandler {
final String classCode = params['classCode'] as String;
final int userType = params['userType'] as int;
final String stuId = params['stuId'] as String ?? '';
final String className = params['className'] as String ?? '';
final String stuName = params['stuName'] as String ?? '';
final String relation = params['relation'] as String ?? '';
var sharedPreferences = getIt.get<SharedPreferences>();
sharedPreferences.setString('auth_userCode', userId);
sharedPreferences.setString('auth_classCode', classCode);
sharedPreferences.setInt('auth_userType', userType);
sharedPreferences.setString('auth_stuId', stuId);
sharedPreferences.setString('auth_className', className);
sharedPreferences.setString('auth_stuName', stuName);
sharedPreferences.setString('auth_relation', relation);
sharedPreferences.setString('pre_userCode', userId);
sharedPreferences.setString('pre_classCode', classCode);
......
......@@ -112,23 +112,4 @@ class PhoneAuthRepository {
);
return resp.data;
}
///
/// {
/// "code": 0,
/// "error": "操作成功"
/// }
///
Future<dynamic> updateUser(String userid, String name, String nickName, String avatar) async {
Response resp = await _appService.post(
'/api/v1/comm/user/update',
{
"userid": userid,
"name": name,
"nickName": nickName,
"avatar": avatar,
},
);
return resp.data;
}
}
import 'dart:io';
import 'package:appframe/config/locator.dart';
import 'package:appframe/services/api_service.dart';
import 'package:dio/dio.dart';
class UserAuthRepository {
late final ApiService _appService;
UserAuthRepository() {
_appService = getIt<ApiService>(instanceName: 'appApiService');
}
///
/// {
/// "code": 0,
/// "error": "操作成功"
/// }
///
Future<dynamic> updateUser(String userid, String name, String nickName, String avatar) async {
Response resp = await _appService.post(
'/api/v1/comm/user/update',
{
"userid": userid,
"name": name,
"nickName": nickName,
"avatar": avatar,
},
);
return resp.statusCode == HttpStatus.ok ? resp.data : null;
}
Future<dynamic> appleLogin(String userid, String authorizationCode, String identityToken) async {
Response resp = await _appService.post(
'/api/v1/comm/user/applelogin',
{
"user": userid,
"authorizationCode": authorizationCode,
"identityToken": identityToken,
},
);
return resp.statusCode == HttpStatus.ok ? resp.data : null;
}
///
/// {
/// "code": 1,
/// "data":"bxe userid" // 存在会返回
/// "error": "",
/// }
Future<dynamic> exchangeId(String userid) async {
Response resp = await _appService.post(
'/api/v1/comm/user/exchangeid',
{
"userId": userid,
"type": "apple",
},
);
return resp.statusCode == HttpStatus.ok ? resp.data : null;
}
///
/// {
/// "error": "",
/// "code": 0,
/// }
Future<dynamic> newBinding(String userid, String bxeUserId) async {
Response resp = await _appService.post(
'/api/v1/comm/user/newbinding',
{
"userId": userid,
"bxeUserId": bxeUserId,
"type": "apple",
},
);
return resp.statusCode == HttpStatus.ok ? resp.data : null;
}
}
import 'dart:io';
import 'package:appframe/config/locator.dart';
import 'package:appframe/services/api_service.dart';
import 'package:dio/dio.dart';
......@@ -21,7 +23,7 @@ class WechatAuthRepository {
debugPrint('登录结果: $resp');
return resp.statusCode == 200 ? resp.data : null;
return resp.statusCode == HttpStatus.ok ? resp.data : null;
}
Future<dynamic> getTicket() async {
......@@ -35,6 +37,6 @@ class WechatAuthRepository {
debugPrint('获取ticket: $resp');
return resp.statusCode == 200 ? resp.data : null;
return resp.statusCode == HttpStatus.ok ? resp.data : null;
}
}
import 'dart:io';
import 'package:appframe/bloc/login_main_cubit.dart';
import 'package:appframe/config/locator.dart';
import 'package:appframe/config/routes.dart';
import 'package:appframe/ui/widgets/login/login_page_agreed_widget.dart';
import 'package:appframe/ui/widgets/tip_overlay_widget.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:fluwx/fluwx.dart';
import 'package:sign_in_with_apple/sign_in_with_apple.dart';
class LoginMainPage extends StatelessWidget {
const LoginMainPage({super.key});
......@@ -18,28 +18,27 @@ class LoginMainPage extends StatelessWidget {
child: BlocConsumer<LoginMainCubit, LoginMainState>(
builder: (context, state) {
var loginMainCubit = context.read<LoginMainCubit>();
return Stack(
children: [
Scaffold(
backgroundColor: Colors.white,
body: SafeArea(
top: false,
child: SingleChildScrollView(
child: Column(
children: [
SizedBox(height: 100),
Center(
child: Image.asset(
'assets/images/login_v2/banner_1.png',
),
var appleLoginBtn = Platform.isIOS
? [
Padding(
padding: EdgeInsets.symmetric(horizontal: 42.5),
child: SizedBox(
width: double.infinity,
height: 50,
child: SignInWithAppleButton(
text: '通过Apple登录',
borderRadius: BorderRadius.circular(27),
onPressed: () {
loginMainCubit.appleAuth();
},
),
SizedBox(height: 30),
Center(
child: Image.asset(
'assets/images/login_v2/main.png',
),
),
SizedBox(height: 40),
SizedBox(height: 15),
]
: [];
var wechatLoginBtn = (!Platform.isIOS || state.wechatInstalled)
? [
Padding(
padding: EdgeInsets.symmetric(horizontal: 42.5),
child: SizedBox(
......@@ -47,7 +46,7 @@ class LoginMainPage extends StatelessWidget {
height: 50,
child: ElevatedButton(
onPressed: () async {
if (await getIt.get<Fluwx>().isWeChatInstalled) {
if (state.wechatInstalled) {
loginMainCubit.wechatAuth();
} else {
if (!context.mounted) return;
......@@ -74,6 +73,30 @@ class LoginMainPage extends StatelessWidget {
),
),
SizedBox(height: 15),
]
: [];
var scaffold = Scaffold(
backgroundColor: Colors.white,
body: SafeArea(
top: false,
child: SingleChildScrollView(
child: Column(
children: [
SizedBox(height: 100),
Center(
child: Image.asset(
'assets/images/login_v2/banner_1.png',
),
),
SizedBox(height: 30),
Center(
child: Image.asset(
'assets/images/login_v2/main.png',
),
),
SizedBox(height: 40),
...appleLoginBtn,
...wechatLoginBtn,
_buildAgreement(context, loginMainCubit, state.agreed),
SizedBox(height: 82.5),
Text(
......@@ -111,7 +134,10 @@ class LoginMainPage extends StatelessWidget {
),
),
),
),
);
return Stack(
children: [
scaffold,
state.loading
? Container(
color: Colors.black54,
......@@ -134,8 +160,8 @@ class LoginMainPage extends StatelessWidget {
listener: (context, state) {
if (state.showAgreed) {
_showAgreementDialog(context, context.read<LoginMainCubit>());
} else if (state.showSnackBar) {
TipOverlayUtil.showTip(context, state.snackBarMsg);
} else if (state.showNeedWechatForApple) {
_showNeedWechatDialogForApple(context, context.read<LoginMainCubit>());
}
},
),
......@@ -352,4 +378,58 @@ class LoginMainPage extends StatelessWidget {
},
);
}
void _showNeedWechatDialogForApple(BuildContext context, LoginMainCubit loginMainCubit) {
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext ctx) {
return PopScope(
canPop: false,
child: AlertDialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(5),
),
),
title: Text(
'温馨提示',
style: TextStyle(
fontSize: 17,
color: Color(0xFF000000),
// fontWeight: FontWeight.bold,
),
textAlign: TextAlign.center,
),
content: Text.rich(
TextSpan(
text: '为了避免您之前在微信小程序的使用数据不丢失,必须绑定微信才可以继续!',
style: TextStyle(color: Color(0xFF666666), fontSize: 14),
),
),
actions: [
Center(
child: TextButton(
onPressed: () {
Navigator.of(ctx).pop('OK');
loginMainCubit.wechatAuthForApple();
},
style: TextButton.styleFrom(
foregroundColor: Color(0xFF7691FA),
textStyle: TextStyle(fontSize: 17),
minimumSize: Size.fromHeight(40),
padding: EdgeInsets.zero,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.zero,
),
),
child: Text('绑定微信'),
),
),
],
),
);
},
);
}
}
import 'dart:io';
import 'package:appframe/bloc/login_phone_cubit.dart';
import 'package:appframe/config/routes.dart';
import 'package:appframe/ui/widgets/login/login_page_agreed_widget.dart';
import 'package:appframe/ui/widgets/tip_overlay_widget.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
......@@ -17,30 +18,42 @@ class LoginPhonePage extends StatelessWidget {
child: BlocConsumer<LoginPhoneCubit, LoginPhoneState>(
builder: (ctx, state) {
var loginPhoneCubit = ctx.read<LoginPhoneCubit>();
return Scaffold(
backgroundColor: Colors.white,
body: SafeArea(
top: false,
child: SingleChildScrollView(
child: Column(
var iosBackHome = Platform.isIOS
? [
InkWell(
onTap: () {
loginPhoneCubit.goLoginMain();
},
borderRadius: BorderRadius.circular(4),
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Image.asset(
'assets/images/login_v2/banner_2.png',
width: MediaQuery.of(context).size.width,
fit: BoxFit.fitWidth,
Icon(
Icons.arrow_back_ios,
size: 16,
color: Color(0xFF7691FA),
),
SizedBox(height: 15.5),
Padding(
padding: EdgeInsets.symmetric(horizontal: 30),
child: Column(children: [
_buildInputFields(loginPhoneCubit, state),
SizedBox(height: 15),
_buildLoginButton(ctx),
]),
SizedBox(width: 4),
Text(
'其他方式登录',
style: TextStyle(
fontSize: 14,
color: Color(0xFF7691FA),
decoration: TextDecoration.underline,
decorationColor: Color(0xFF7691FA),
),
SizedBox(height: 15),
_buildAgreement(ctx, loginPhoneCubit, state.agreed),
SizedBox(height: 140),
strutStyle: StrutStyle(height: 16 / 14),
),
],
),
),
),
]
: [];
var androidBackHome = Platform.isAndroid
? [
Text(
'其他方式登录',
style: TextStyle(
......@@ -72,6 +85,34 @@ class LoginPhonePage extends StatelessWidget {
),
],
),
]
: [];
return Scaffold(
backgroundColor: Colors.white,
body: SafeArea(
top: false,
child: SingleChildScrollView(
child: Column(
children: [
Image.asset(
'assets/images/login_v2/banner_2.png',
width: MediaQuery.of(context).size.width,
fit: BoxFit.fitWidth,
),
SizedBox(height: 15.5),
Padding(
padding: EdgeInsets.symmetric(horizontal: 30),
child: Column(children: [
_buildInputFields(loginPhoneCubit, state),
SizedBox(height: 15),
_buildLoginButton(ctx),
]),
),
SizedBox(height: 15),
_buildAgreement(ctx, loginPhoneCubit, state.agreed),
SizedBox(height: 140),
...iosBackHome,
...androidBackHome,
],
),
),
......@@ -81,8 +122,6 @@ class LoginPhonePage extends StatelessWidget {
listener: (context, state) {
if (state.showAgreed) {
_showAgreementDialog(context, context.read<LoginPhoneCubit>());
} else if (state.showSnackBar) {
TipOverlayUtil.showTip(context, state.snackBarMsg);
}
},
),
......
import 'dart:io';
import 'package:appframe/bloc/login_qr_cubit.dart';
import 'package:appframe/ui/widgets/tip_overlay_widget.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
......@@ -13,24 +14,44 @@ class LoginQrPage extends StatelessWidget {
child: BlocConsumer<LoginQrCubit, LoginQrState>(
builder: (context, state) {
var loginQrCubit = context.read<LoginQrCubit>();
return Scaffold(
backgroundColor: Colors.white,
body: SafeArea(
top: false,
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
var iosBackHome = Platform.isIOS
? [
Center(
child: InkWell(
onTap: () {
loginQrCubit.goLoginMain();
},
borderRadius: BorderRadius.circular(4),
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Image.asset(
'assets/images/login_v2/banner_2.png',
width: MediaQuery.of(context).size.width,
fit: BoxFit.fitWidth,
Icon(
Icons.arrow_back_ios,
size: 16,
color: Color(0xFF7691FA),
),
SizedBox(
height: 380,
child: _buildQrCode(loginQrCubit, state),
SizedBox(width: 4),
Text(
'其他方式登录',
style: TextStyle(
fontSize: 14,
color: Color(0xFF7691FA),
decoration: TextDecoration.underline,
decorationColor: Color(0xFF7691FA),
),
SizedBox(height: 20),
strutStyle: StrutStyle(height: 16 / 14),
),
],
),
),
),
),
]
: [];
var androidBackHome = Platform.isAndroid
? [
Center(
child: Text(
'其他方式登录',
......@@ -55,17 +76,35 @@ class LoginQrPage extends StatelessWidget {
),
],
),
]
: [];
return Scaffold(
backgroundColor: Colors.white,
body: SafeArea(
top: false,
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Image.asset(
'assets/images/login_v2/banner_2.png',
width: MediaQuery.of(context).size.width,
fit: BoxFit.fitWidth,
),
SizedBox(
height: 380,
child: _buildQrCode(loginQrCubit, state),
),
SizedBox(height: 20),
...iosBackHome,
...androidBackHome,
],
),
),
),
);
},
listener: (context, state) {
if (state.showSnackBar) {
TipOverlayUtil.showTip(context, state.snackBarMsg);
}
},
listener: (context, state) {},
));
}
......
import 'package:appframe/bloc/setting/account_cubit.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:fluttertoast/fluttertoast.dart';
class AccountPage extends StatelessWidget {
const AccountPage({super.key});
......@@ -11,6 +12,41 @@ class AccountPage extends StatelessWidget {
create: (context) => AccountCubit(AccountState()),
child: BlocConsumer<AccountCubit, AccountState>(
builder: (context, state) {
var childInfoTable = (state.className.isNotEmpty && state.stuName.isNotEmpty)
? [
SizedBox(height: 24.0),
Align(
alignment: Alignment.centerLeft,
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 4.0),
child: Text(
'孩子信息',
style: TextStyle(
fontSize: 12.0,
color: Colors.black87,
),
),
),
),
SizedBox(height: 8.0),
Container(
margin: EdgeInsets.symmetric(horizontal: 4.0),
decoration: BoxDecoration(
color: Color(0xFFF7F9FF),
borderRadius: BorderRadius.circular(8.0),
border: Border.all(color: Color(0xFFE0E6F7), width: 1),
),
child: Column(
children: [
_buildInfoRow('班级', state.className, Icons.school),
Divider(height: 1, color: Color(0xFFE0E6F7)),
_buildInfoRow('学生', state.stuName, Icons.person),
],
),
),
]
: [];
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
......@@ -22,6 +58,7 @@ class AccountPage extends StatelessWidget {
),
),
body: state.loaded
? (state.bindStu
? Column(
children: [
// 用户头像和昵称部分
......@@ -45,6 +82,7 @@ class AccountPage extends StatelessWidget {
state.nickname,
style: TextStyle(fontSize: 12.0),
),
...childInfoTable,
],
),
),
......@@ -105,7 +143,7 @@ class AccountPage extends StatelessWidget {
width: double.infinity,
height: 47,
child: ElevatedButton(
onPressed: () async {
onPressed: () {
// 判断是否有绑定手机号
if (state.phone != '') {
context.read<AccountCubit>().goLogoff();
......@@ -113,39 +151,7 @@ class AccountPage extends StatelessWidget {
}
final accountCubit = context.read<AccountCubit>();
bool? confirm = await showDialog<bool>(
context: context,
builder: (BuildContext ctx) {
return AlertDialog(
title: Text('确认注销'),
content: Text('您确定要注销当前用户吗?'),
actions: [
TextButton(
onPressed: () {
Navigator.of(ctx).pop(false);
},
child: Text('取消'),
),
TextButton(
onPressed: () {
Navigator.of(ctx).pop(true);
},
child: Text('确认'),
),
],
);
},
);
if (confirm == true) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('已申请注销,等待流程处理'),
backgroundColor: Colors.green,
),
);
accountCubit.unbind();
}
_showLogoffDialog(context, accountCubit);
},
style: ElevatedButton.styleFrom(
backgroundColor: Color(0xFFE74C3C),
......@@ -173,6 +179,17 @@ class AccountPage extends StatelessWidget {
),
],
)
: Container(
alignment: Alignment(0, -0.6),
child: Text(
'暂未关联孩子信息\n\n请在首页操作绑定孩子信息',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 16.0,
color: Colors.grey,
),
),
))
: const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
......@@ -189,4 +206,73 @@ class AccountPage extends StatelessWidget {
),
);
}
Widget _buildInfoRow(String label, String value, IconData icon) {
return Padding(
padding: EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0),
child: Row(
children: [
Icon(
icon,
size: 18.0,
color: Color(0xFF7691FA),
),
SizedBox(width: 8.0),
SizedBox(
width: 60,
child: Text(
label,
style: TextStyle(
fontSize: 14.0,
color: Colors.grey[600],
fontWeight: FontWeight.w500,
),
),
),
Expanded(
child: Text(
value,
style: TextStyle(
fontSize: 14.0,
color: Colors.black87,
),
),
),
],
),
);
}
void _showLogoffDialog(BuildContext context, AccountCubit accountCubit) {
showDialog<bool>(
context: context,
builder: (BuildContext ctx) {
return AlertDialog(
title: Text('确认注销'),
content: Text('您确定要注销当前用户吗?'),
actions: [
TextButton(
onPressed: () {
Navigator.of(ctx).pop(false);
},
child: Text('取消'),
),
TextButton(
onPressed: () {
Navigator.of(ctx).pop(true);
Fluttertoast.showToast(
msg: '已申请注销,等待流程处理',
backgroundColor: Colors.green,
toastLength: Toast.LENGTH_LONG,
gravity: ToastGravity.BOTTOM,
);
accountCubit.unbind();
},
child: Text('确认'),
),
],
);
},
);
}
}
import 'dart:io';
import 'package:appframe/bloc/setting/setting_cubit.dart';
import 'package:appframe/config/constant.dart';
import 'package:appframe/config/env_config.dart';
import 'package:appframe/config/routes.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
class SettingPage extends StatelessWidget {
const SettingPage({super.key});
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => SettingCubit(),
child: BlocBuilder<SettingCubit, SettingState>(
builder: (context, state) {
final settingCubit = context.read<SettingCubit>();
var customerService = Platform.isAndroid || state.wechatInstalled
? [
Card(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 2),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(6),
),
child: Column(
children: [
ListTile(
leading: const Icon(Icons.headset_mic, size: 20),
title: const Text('在线客服', style: TextStyle(fontSize: 14)),
onTap: () {
settingCubit.goCs();
},
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
trailing: const Icon(Icons.arrow_forward_ios, size: 14),
dense: true,
visualDensity: VisualDensity.compact,
),
],
),
),
const SizedBox(height: 8),
]
: [];
return Scaffold(
appBar: AppBar(
title: const Text('设置', style: TextStyle(color: Colors.white, fontSize: 18)),
centerTitle: true,
backgroundColor: const Color(0xFF7691FA),
iconTheme: const IconThemeData(color: Colors.white),
),
body: Column(
children: [
Expanded(
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const SizedBox(height: 16),
Card(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 2),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(6),
),
child: Column(
children: [
ListTile(
leading: const Icon(Icons.lock, size: 20),
title: const Text('账号与安全', style: TextStyle(fontSize: 14)),
onTap: () {
router.push('/account');
},
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
trailing: const Icon(Icons.arrow_forward_ios, size: 14),
dense: true,
visualDensity: VisualDensity.compact,
),
],
),
),
const SizedBox(height: 8),
Card(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 2),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(6),
),
child: Column(
children: [
ListTile(
leading: const Icon(Icons.book_outlined, size: 20),
title: const Text('版本记录', style: TextStyle(fontSize: 14)),
onTap: () {
router.push(
'/link',
extra: {'url': 'https://bxr.banxiaoer.net/apps/versions.html', 'title': '版本记录'},
);
},
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
trailing: const Icon(Icons.arrow_forward_ios, size: 14),
dense: true,
visualDensity: VisualDensity.compact,
),
const Divider(height: 1, thickness: 0.5, indent: 16, endIndent: 16),
ListTile(
leading: const Icon(Icons.book_outlined, size: 20),
title: const Text('用户协议', style: TextStyle(fontSize: 14)),
onTap: () {
router.push(
'/link',
extra: {
'url': 'https://bxr.banxiaoer.net/apps/useragreement.html',
'title': '用户协议'
},
);
},
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
trailing: const Icon(Icons.arrow_forward_ios, size: 14),
dense: true,
visualDensity: VisualDensity.compact,
),
const Divider(height: 1, thickness: 0.5, indent: 16, endIndent: 16),
ListTile(
leading: const Icon(Icons.book_outlined, size: 20),
title: const Text('隐私设置', style: TextStyle(fontSize: 14)),
onTap: () {
router.push(
'/link',
extra: {
'url': 'https://bxr.banxiaoer.net/apps/privacysettings.html',
'title': '隐私设置'
},
);
},
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
trailing: const Icon(Icons.arrow_forward_ios, size: 14),
dense: true,
visualDensity: VisualDensity.compact,
),
const Divider(height: 1, thickness: 0.5, indent: 16, endIndent: 16),
ListTile(
leading: const Icon(Icons.book_outlined, size: 20),
title: const Text('关于', style: TextStyle(fontSize: 14)),
onTap: () {
router.push(
'/link',
extra: {'url': 'https://bxr.banxiaoer.net/apps/produce.html', 'title': '关于'},
);
},
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
trailing: const Icon(Icons.arrow_forward_ios, size: 14),
dense: true,
visualDensity: VisualDensity.compact,
),
],
),
),
const SizedBox(height: 8),
...customerService,
Card(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 2),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(6),
),
child: Column(
children: [
ListTile(
leading: const Icon(Icons.cleaning_services, size: 20),
title: const Text('清理缓存', style: TextStyle(fontSize: 14)),
onTap: () {
_showClearCacheDialog(context, settingCubit);
},
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
dense: true,
visualDensity: VisualDensity.compact,
),
const Divider(height: 1, thickness: 0.5, indent: 16, endIndent: 16),
ListTile(
leading: const Icon(Icons.logout, size: 20),
title: const Text('退出登录', style: TextStyle(fontSize: 14)),
onTap: () {
settingCubit.logout();
},
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
dense: true,
visualDensity: VisualDensity.compact,
),
],
),
),
const SizedBox(height: 8),
EnvConfig.isDev()
? Card(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 2),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(6),
),
child: Column(
children: [
ListTile(
leading: const Icon(Icons.timeline, size: 20),
title: const Text('切换日志模式', style: TextStyle(fontSize: 14)),
onTap: () {
settingCubit.handleToggleDebug();
},
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
dense: true,
visualDensity: VisualDensity.compact,
),
],
),
)
: const SizedBox(),
],
),
),
),
const Divider(),
Container(
width: double.infinity,
padding: const EdgeInsets.all(16),
child: Center(
child: Column(
children: [
if (EnvConfig.isDev()) ...[
const Text(
'开发版',
style: TextStyle(
color: Colors.grey,
fontSize: 14,
),
),
const SizedBox(height: 4),
],
const Text(
'V1.0',
style: TextStyle(
color: Colors.grey,
fontSize: 12,
),
),
const SizedBox(height: 8),
const Text(
'粤ICP备2025475184号-15A',
style: TextStyle(color: Colors.grey, fontSize: 12),
),
const SizedBox(height: 8),
const Text(
'Copyright © 中山班小二科技有限公司',
style: TextStyle(color: Colors.grey, fontSize: 12),
),
const SizedBox(height: 8),
Text(
'${Constant.appVersion}-${state.h5Version}',
style: const TextStyle(
color: Colors.grey,
fontSize: 12,
),
),
const SizedBox(height: 10),
],
),
),
),
],
),
);
},
),
);
}
void _showClearCacheDialog(BuildContext context, SettingCubit settingCubit) {
showDialog(
context: context,
builder: (BuildContext ctx) {
return AlertDialog(
title: const Text('清理缓存'),
content: const Text('清理缓存后将需要重新登录,是否继续?'),
actions: <Widget>[
TextButton(
child: const Text('取消'),
onPressed: () {
Navigator.of(ctx).pop();
},
),
TextButton(
child: const Text('确认'),
onPressed: () {
Navigator.of(ctx).pop();
settingCubit.clearStorage();
settingCubit.logout();
},
),
],
);
},
);
}
}
import 'dart:io';
import 'package:appframe/bloc/web_cubit.dart';
import 'package:appframe/config/constant.dart';
import 'package:appframe/config/env_config.dart';
import 'package:appframe/config/locator.dart';
import 'package:appframe/config/routes.dart';
import 'package:appframe/ui/widgets/tip_overlay_widget.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:go_router/go_router.dart';
......@@ -56,7 +54,6 @@ class WebPage extends StatelessWidget {
builder: (ctx, state) {
final scaffold = Scaffold(
appBar: _buildAppBar(ctx, state),
endDrawer: _buildDrawer(ctx, state),
body: Stack(
children: [
state.loaded
......@@ -151,8 +148,6 @@ class WebPage extends StatelessWidget {
context.read<WebCubit>().chooseImage(context);
} else if (state.chooseVideoCmdFlag) {
context.read<WebCubit>().chooseVideo(context);
} else if (state.showSnackBar) {
TipOverlayUtil.showTip(context, state.snackBarMsg);
}
},
),
......@@ -183,290 +178,14 @@ class WebPage extends StatelessWidget {
},
)
: null),
);
}
Drawer _buildDrawer(BuildContext ctx, WebState state) {
return Drawer(
width: MediaQuery.of(ctx).size.width * 0.8,
child: Column(children: [
DrawerHeader(
decoration: BoxDecoration(
color: Color(0xFF7691FA),
),
child: Align(
alignment: Alignment.centerLeft,
child: Text(
'设置',
style: TextStyle(
// color: Theme.of(ctx).colorScheme.onPrimary,
fontSize: 24,
color: Colors.white,
),
),
),
),
Expanded(
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Card(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 2),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(6),
),
child: Column(
children: [
ListTile(
leading: const Icon(Icons.lock, size: 20),
title: const Text('账号与安全', style: TextStyle(fontSize: 14)),
onTap: () {
Navigator.pop(ctx);
ctx.read<WebCubit>().goAccount();
},
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
trailing: Icon(Icons.arrow_forward_ios, size: 14),
dense: true,
visualDensity: VisualDensity.compact,
),
],
),
),
SizedBox(height: 8),
Card(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 2),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(6),
),
child: Column(
children: [
ListTile(
leading: const Icon(Icons.book_outlined, size: 20),
title: const Text('版本记录', style: TextStyle(fontSize: 14)),
onTap: () {
Navigator.pop(ctx);
router.push(
'/link',
extra: {'url': 'https://bxr.banxiaoer.net/apps/versions.html', 'title': '版本记录'},
);
},
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
trailing: Icon(Icons.arrow_forward_ios, size: 14),
dense: true,
visualDensity: VisualDensity.compact,
),
Divider(height: 1, thickness: 0.5, indent: 16, endIndent: 16),
ListTile(
leading: const Icon(Icons.book_outlined, size: 20),
title: const Text('用户协议', style: TextStyle(fontSize: 14)),
onTap: () {
Navigator.pop(ctx);
router.push(
'/link',
extra: {'url': 'https://bxr.banxiaoer.net/apps/useragreement.html', 'title': '用户协议'},
);
},
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
trailing: Icon(Icons.arrow_forward_ios, size: 14),
dense: true,
visualDensity: VisualDensity.compact,
),
Divider(height: 1, thickness: 0.5, indent: 16, endIndent: 16),
ListTile(
leading: const Icon(Icons.book_outlined, size: 20),
title: const Text('隐私设置', style: TextStyle(fontSize: 14)),
onTap: () {
Navigator.pop(ctx);
router.push(
'/link',
extra: {'url': 'https://bxr.banxiaoer.net/apps/privacysettings.html', 'title': '隐私设置'},
);
},
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
trailing: Icon(Icons.arrow_forward_ios, size: 14),
dense: true,
visualDensity: VisualDensity.compact,
),
Divider(height: 1, thickness: 0.5, indent: 16, endIndent: 16),
ListTile(
leading: const Icon(Icons.book_outlined, size: 20),
title: const Text('关于', style: TextStyle(fontSize: 14)),
onTap: () {
Navigator.pop(ctx);
router.push(
'/link',
extra: {'url': 'https://bxr.banxiaoer.net/apps/produce.html', 'title': '关于'},
);
},
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
trailing: Icon(Icons.arrow_forward_ios, size: 14),
dense: true,
visualDensity: VisualDensity.compact,
),
],
),
),
SizedBox(height: 8),
Card(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 2),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(6),
),
child: Column(
children: [
ListTile(
leading: const Icon(Icons.headset_mic, size: 20),
title: const Text('在线客服', style: TextStyle(fontSize: 14)),
onTap: () {
Navigator.pop(ctx);
ctx.read<WebCubit>().goCs();
},
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
trailing: Icon(Icons.arrow_forward_ios, size: 14),
dense: true,
visualDensity: VisualDensity.compact,
),
],
),
),
SizedBox(height: 8),
Card(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 2),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(6),
),
child: Column(
children: [
ListTile(
leading: const Icon(Icons.cleaning_services, size: 20),
title: const Text('清理缓存', style: TextStyle(fontSize: 14)),
onTap: () {
Navigator.pop(ctx);
_showClearCacheDialog(ctx);
},
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
dense: true,
visualDensity: VisualDensity.compact,
),
Divider(height: 1, thickness: 0.5, indent: 16, endIndent: 16),
ListTile(
leading: const Icon(Icons.logout, size: 20),
title: const Text('退出登录', style: TextStyle(fontSize: 14)),
onTap: () {
Navigator.pop(ctx);
ctx.read<WebCubit>().logout();
},
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
dense: true,
visualDensity: VisualDensity.compact,
),
],
),
),
SizedBox(height: 8),
EnvConfig.isDev()
? Card(
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 2),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(6),
),
child: Column(
children: [
ListTile(
leading: const Icon(Icons.timeline, size: 20),
title: const Text('切换日志模式', style: TextStyle(fontSize: 14)),
onTap: () {
Navigator.pop(ctx);
ctx.read<WebCubit>().handleToggleDebug();
},
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
// trailing: Icon(Icons.arrow_forward_ios, size: 14),
dense: true,
visualDensity: VisualDensity.compact, // 视觉密度设为紧凑
),
],
),
)
: SizedBox(),
],
),
),
),
const Divider(),
Container(
width: double.infinity,
padding: const EdgeInsets.all(1),
child: Center(
child: Column(
children: [
if (EnvConfig.isDev()) ...[
Text(
'开发版',
style: TextStyle(
color: Colors.grey,
fontSize: 14,
),
),
SizedBox(height: 4),
],
Text(
'V1.0',
style: TextStyle(
color: Colors.grey,
fontSize: 12,
),
),
SizedBox(height: 8),
Text(
'粤ICP备2025475184号-15A',
),
SizedBox(height: 8),
Text(
'Copyright © 中山班小二科技有限公司',
),
SizedBox(height: 8),
Text(
'${Constant.appVersion}-${state.h5Version}',
style: TextStyle(
color: Colors.grey,
fontSize: 12,
),
),
SizedBox(height: 10),
],
),
),
),
]));
}
void _showClearCacheDialog(BuildContext ctx) {
showDialog(
context: ctx,
builder: (BuildContext context) {
return AlertDialog(
title: Text('清理缓存'),
content: Text('清理缓存后将需要重新登录,是否继续?'),
actions: <Widget>[
TextButton(
child: Text('取消'),
actions: [
IconButton(
icon: const Icon(Icons.settings),
onPressed: () {
Navigator.of(context).pop();
},
),
TextButton(
child: Text('确认'),
onPressed: () {
Navigator.of(context).pop();
var webCubit = ctx.read<WebCubit>();
webCubit.clearStorage();
webCubit.goLogin();
router.push('/setting');
},
),
],
);
},
);
}
}
......@@ -22,6 +22,7 @@ import package_info_plus
import path_provider_foundation
import photo_manager
import shared_preferences_foundation
import sign_in_with_apple
import tencent_cloud_chat_sdk
import url_launcher_macos
import video_player_avfoundation
......@@ -46,6 +47,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
PhotoManagerPlugin.register(with: registry.registrar(forPlugin: "PhotoManagerPlugin"))
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
SignInWithApplePlugin.register(with: registry.registrar(forPlugin: "SignInWithApplePlugin"))
TencentCloudChatSdkPlugin.register(with: registry.registrar(forPlugin: "TencentCloudChatSdkPlugin"))
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin"))
......
......@@ -46,10 +46,10 @@
<key>io.flutter.embedded_views_preview</key>
<true/>
<key>NSPhotoLibraryUsageDescription</key>
<string>需要访问相册选择图片或视频</string>
<string>提交作业需要使用图片或视频</string>
<key>NSCameraUsageDescription</key>
<string>需要访问相机</string>
<string>提交作业需要使用相机</string>
<key>NSMicrophoneUsageDescription</key>
<string>需要麦克风权限用于录音</string>
<string>提交作业需要使用录音</string>
</dict>
</plist>
name: appframe
description: "app frame project."
publish_to: 'none'
version: 1.0.3
version: 1.0.6
environment:
sdk: ">=3.5.0 <4.0.0"
......@@ -53,6 +53,9 @@ dependencies:
wechat_assets_picker: ^9.8.0
wechat_camera_picker: ^4.4.0
# --- Apple 登录 ---
sign_in_with_apple: ^7.0.1
# --- 音视频与直播 (重灾区) ---
# 确保 ffmpeg_kit 版本与你的架构兼容。
# 如果只是为了压缩视频,建议评估是否移除 video_compress,直接用 ffmpeg
......@@ -84,6 +87,8 @@ dependencies:
open_file: ^3.5.10
cupertino_icons: ^1.0.8
fluttertoast: ^9.0.0
dev_dependencies:
flutter_test:
sdk: flutter
......
Styling with Markdown is supported
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!