diff --git a/.kotlin/errors/errors-1749093424243.log b/.kotlin/errors/errors-1749093424243.log new file mode 100644 index 0000000..8285180 --- /dev/null +++ b/.kotlin/errors/errors-1749093424243.log @@ -0,0 +1,33 @@ +kotlin version: 2.1.20 +error message: java.lang.NoSuchMethodError: 'org.jetbrains.kotlin.config.LanguageVersionSettings org.jetbrains.kotlin.codegen.state.KotlinTypeMapper$Companion.getLANGUAGE_VERSION_SETTINGS_DEFAULT()' + at com.google.devtools.ksp.processing.impl.ResolverImpl.(ResolverImpl.kt:147) + at com.google.devtools.ksp.AbstractKotlinSymbolProcessingExtension.doAnalysis(KotlinSymbolProcessingExtension.kt:231) + at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration(TopDownAnalyzerFacadeForJVM.kt:112) + at org.jetbrains.kotlin.cli.jvm.compiler.TopDownAnalyzerFacadeForJVM.analyzeFilesWithJavaIntegration$default(TopDownAnalyzerFacadeForJVM.kt:75) + at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze$lambda$7(KotlinToJVMBytecodeCompiler.kt:326) + at org.jetbrains.kotlin.cli.common.messages.AnalyzerWithCompilerReport.analyzeAndReport(AnalyzerWithCompilerReport.kt:112) + at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.analyze(KotlinToJVMBytecodeCompiler.kt:317) + at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.runFrontendAndGenerateIrUsingClassicFrontend(KotlinToJVMBytecodeCompiler.kt:154) + at org.jetbrains.kotlin.cli.jvm.compiler.KotlinToJVMBytecodeCompiler.compileModules$cli(KotlinToJVMBytecodeCompiler.kt:75) + at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:167) + at org.jetbrains.kotlin.cli.jvm.K2JVMCompiler.doExecute(K2JVMCompiler.kt:36) + at org.jetbrains.kotlin.cli.common.CLICompiler.execImpl(CLICompiler.kt:113) + at org.jetbrains.kotlin.cli.common.CLICompiler.exec(CLICompiler.kt:337) + at org.jetbrains.kotlin.daemon.CompileServiceImpl.compile(CompileServiceImpl.kt:1700) + at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(Unknown Source) + at java.base/java.lang.reflect.Method.invoke(Unknown Source) + at java.rmi/sun.rmi.server.UnicastServerRef.dispatch(Unknown Source) + at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source) + at java.rmi/sun.rmi.transport.Transport$1.run(Unknown Source) + at java.base/java.security.AccessController.doPrivileged(Unknown Source) + at java.rmi/sun.rmi.transport.Transport.serviceCall(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport.handleMessages(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run$0(Unknown Source) + at java.base/java.security.AccessController.doPrivileged(Unknown Source) + at java.rmi/sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(Unknown Source) + at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(Unknown Source) + at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(Unknown Source) + at java.base/java.lang.Thread.run(Unknown Source) + + diff --git a/.kotlin/metadata/kotlinCInteropLibraries/shared-iosArm64Cinterop-cinMain-Duh-Ng.klib b/.kotlin/metadata/kotlinCInteropLibraries/shared-iosArm64Cinterop-cinMain-Duh-Ng.klib new file mode 100644 index 0000000..116c2ea Binary files /dev/null and b/.kotlin/metadata/kotlinCInteropLibraries/shared-iosArm64Cinterop-cinMain-Duh-Ng.klib differ diff --git a/.kotlin/metadata/kotlinCInteropLibraries/shared-iosArm64Cinterop-cinMain-SXIigQ.klib b/.kotlin/metadata/kotlinCInteropLibraries/shared-iosArm64Cinterop-cinMain-SXIigQ.klib new file mode 100644 index 0000000..24cca31 Binary files /dev/null and b/.kotlin/metadata/kotlinCInteropLibraries/shared-iosArm64Cinterop-cinMain-SXIigQ.klib differ diff --git a/.kotlin/sessions/kotlin-compiler-7621307791481117302.salive b/.kotlin/sessions/kotlin-compiler-7621307791481117302.salive new file mode 100644 index 0000000..e69de29 diff --git a/androidApp/src/main/AndroidManifest.xml b/androidApp/src/main/AndroidManifest.xml index b698c8b..9c4a9c1 100644 --- a/androidApp/src/main/AndroidManifest.xml +++ b/androidApp/src/main/AndroidManifest.xml @@ -27,6 +27,7 @@ '../shared' pod 'Masonry', '~> 1.1.0' - + pod 'JTCalendar', '~> 2.2.6' pod 'YYKit', '~> 1.0.9' pod 'MBProgressHUD', '~> 1.2.0' @@ -15,4 +15,11 @@ target 'iosApp' do pod 'DateTools' pod 'MJExtension', '~> 3.4.1' pod 'FMDB' + post_install do |installer| + installer.pods_project.targets.each do |target| + target.build_configurations.each do |config| + config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '14.0' + end + end + end end diff --git a/iosApp/Podfile.lock b/iosApp/Podfile.lock index cabfb2a..5a71a7e 100644 --- a/iosApp/Podfile.lock +++ b/iosApp/Podfile.lock @@ -375,6 +375,6 @@ SPEC CHECKSUMS: Toast: 1f5ea13423a1e6674c4abdac5be53587ae481c4e YYKit: 7cda43304a8dc3696c449041e2cb3107b4e236e7 -PODFILE CHECKSUM: fc26b545e5ee4b5b80ec47254f6366edb022adfa +PODFILE CHECKSUM: 4a1ed0dd46b1d5bb4c5e90ddd23567651f3f53c4 COCOAPODS: 1.16.2 diff --git a/iosApp/Pods/Manifest.lock b/iosApp/Pods/Manifest.lock index cabfb2a..5a71a7e 100644 --- a/iosApp/Pods/Manifest.lock +++ b/iosApp/Pods/Manifest.lock @@ -375,6 +375,6 @@ SPEC CHECKSUMS: Toast: 1f5ea13423a1e6674c4abdac5be53587ae481c4e YYKit: 7cda43304a8dc3696c449041e2cb3107b4e236e7 -PODFILE CHECKSUM: fc26b545e5ee4b5b80ec47254f6366edb022adfa +PODFILE CHECKSUM: 4a1ed0dd46b1d5bb4c5e90ddd23567651f3f53c4 COCOAPODS: 1.16.2 diff --git a/iosApp/Pods/Pods.xcodeproj/project.pbxproj b/iosApp/Pods/Pods.xcodeproj/project.pbxproj index 728f78c..2fd31b9 100644 --- a/iosApp/Pods/Pods.xcodeproj/project.pbxproj +++ b/iosApp/Pods/Pods.xcodeproj/project.pbxproj @@ -4998,7 +4998,7 @@ CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/MJExtension"; IBSC_MODULE = MJExtension; INFOPLIST_FILE = "Target Support Files/MJExtension/ResourceBundle-MJExtension-MJExtension-Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; PRODUCT_NAME = MJExtension; SDKROOT = iphoneos; SKIP_INSTALL = YES; @@ -5025,7 +5025,7 @@ ENABLE_USER_SCRIPT_SANDBOXING = NO; INFOPLIST_FILE = "Target Support Files/Pods-iosApp/Pods-iosApp-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 16.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -5065,7 +5065,7 @@ GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = "Target Support Files/QMUIKit/QMUIKit-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -5094,7 +5094,7 @@ CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/Toast"; IBSC_MODULE = Toast; INFOPLIST_FILE = "Target Support Files/Toast/ResourceBundle-Toast-Toast-Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; PRODUCT_NAME = Toast; SDKROOT = iphoneos; SKIP_INSTALL = YES; @@ -5121,7 +5121,7 @@ GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = "Target Support Files/Masonry/Masonry-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -5211,7 +5211,7 @@ CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/MJExtension"; IBSC_MODULE = MJExtension; INFOPLIST_FILE = "Target Support Files/MJExtension/ResourceBundle-MJExtension-MJExtension-Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; PRODUCT_NAME = MJExtension; SDKROOT = iphoneos; SKIP_INSTALL = YES; @@ -5238,7 +5238,7 @@ GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = "Target Support Files/DateTools/DateTools-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -5278,7 +5278,7 @@ GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = "Target Support Files/MJRefresh/MJRefresh-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -5317,7 +5317,7 @@ GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = "Target Support Files/Toast/Toast-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -5344,7 +5344,7 @@ CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/QMUIKit"; IBSC_MODULE = QMUIKit; INFOPLIST_FILE = "Target Support Files/QMUIKit/ResourceBundle-QMUIResources-QMUIKit-Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; PRODUCT_NAME = QMUIResources; SDKROOT = iphoneos; SKIP_INSTALL = YES; @@ -5372,7 +5372,7 @@ GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = "Target Support Files/FMDB/FMDB-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -5411,7 +5411,7 @@ GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = "Target Support Files/MJExtension/MJExtension-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -5450,7 +5450,7 @@ GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = "Target Support Files/QMUIKit/QMUIKit-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -5488,7 +5488,7 @@ GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = "Target Support Files/DateTools/DateTools-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -5516,7 +5516,7 @@ CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/FMDB"; IBSC_MODULE = FMDB; INFOPLIST_FILE = "Target Support Files/FMDB/ResourceBundle-FMDB_Privacy-FMDB-Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; PRODUCT_NAME = FMDB_Privacy; SDKROOT = iphoneos; SKIP_INSTALL = YES; @@ -5533,7 +5533,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_ENABLE_OBJC_WEAK = NO; ENABLE_USER_SCRIPT_SANDBOXING = NO; - IPHONEOS_DEPLOYMENT_TARGET = 16.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -5563,7 +5563,7 @@ GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = "Target Support Files/MJRefresh/MJRefresh-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -5602,7 +5602,7 @@ ENABLE_USER_SCRIPT_SANDBOXING = NO; INFOPLIST_FILE = "Target Support Files/Pods-iosApp/Pods-iosApp-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 16.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -5642,7 +5642,7 @@ GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = "Target Support Files/MBProgressHUD/MBProgressHUD-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -5681,7 +5681,7 @@ GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = "Target Support Files/FMDB/FMDB-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -5720,7 +5720,7 @@ GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = "Target Support Files/YYKit/YYKit-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -5747,7 +5747,7 @@ CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/QMUIKit"; IBSC_MODULE = QMUIKit; INFOPLIST_FILE = "Target Support Files/QMUIKit/ResourceBundle-QMUIResources-QMUIKit-Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; PRODUCT_NAME = QMUIResources; SDKROOT = iphoneos; SKIP_INSTALL = YES; @@ -5764,7 +5764,7 @@ ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CLANG_ENABLE_OBJC_WEAK = NO; ENABLE_USER_SCRIPT_SANDBOXING = NO; - IPHONEOS_DEPLOYMENT_TARGET = 16.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -5782,7 +5782,7 @@ CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/Toast"; IBSC_MODULE = Toast; INFOPLIST_FILE = "Target Support Files/Toast/ResourceBundle-Toast-Toast-Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; PRODUCT_NAME = Toast; SDKROOT = iphoneos; SKIP_INSTALL = YES; @@ -5810,7 +5810,7 @@ GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = "Target Support Files/Toast/Toast-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -5849,7 +5849,7 @@ GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = "Target Support Files/Masonry/Masonry-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -5888,7 +5888,7 @@ GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = "Target Support Files/JTCalendar/JTCalendar-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -5916,7 +5916,7 @@ CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/QMUIKit"; IBSC_MODULE = QMUIKit; INFOPLIST_FILE = "Target Support Files/QMUIKit/ResourceBundle-QMUIKit-QMUIKit-Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; PRODUCT_NAME = QMUIKit; SDKROOT = iphoneos; SKIP_INSTALL = YES; @@ -5943,7 +5943,7 @@ GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = "Target Support Files/MBProgressHUD/MBProgressHUD-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -5972,7 +5972,7 @@ CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/MJRefresh"; IBSC_MODULE = MJRefresh; INFOPLIST_FILE = "Target Support Files/MJRefresh/ResourceBundle-MJRefresh.Privacy-MJRefresh-Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; PRODUCT_NAME = MJRefresh.Privacy; SDKROOT = iphoneos; SKIP_INSTALL = YES; @@ -5989,7 +5989,7 @@ CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/FMDB"; IBSC_MODULE = FMDB; INFOPLIST_FILE = "Target Support Files/FMDB/ResourceBundle-FMDB_Privacy-FMDB-Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; PRODUCT_NAME = FMDB_Privacy; SDKROOT = iphoneos; SKIP_INSTALL = YES; @@ -6006,7 +6006,7 @@ CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/MJRefresh"; IBSC_MODULE = MJRefresh; INFOPLIST_FILE = "Target Support Files/MJRefresh/ResourceBundle-MJRefresh.Privacy-MJRefresh-Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; PRODUCT_NAME = MJRefresh.Privacy; SDKROOT = iphoneos; SKIP_INSTALL = YES; @@ -6034,7 +6034,7 @@ GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = "Target Support Files/MJExtension/MJExtension-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 12.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -6073,7 +6073,7 @@ GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = "Target Support Files/YYKit/YYKit-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -6167,7 +6167,7 @@ CONFIGURATION_BUILD_DIR = "$(BUILD_DIR)/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME)/QMUIKit"; IBSC_MODULE = QMUIKit; INFOPLIST_FILE = "Target Support Files/QMUIKit/ResourceBundle-QMUIKit-QMUIKit-Info.plist"; - IPHONEOS_DEPLOYMENT_TARGET = 13.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; PRODUCT_NAME = QMUIKit; SDKROOT = iphoneos; SKIP_INSTALL = YES; @@ -6194,7 +6194,7 @@ GENERATE_INFOPLIST_FILE = NO; INFOPLIST_FILE = "Target Support Files/JTCalendar/JTCalendar-Info.plist"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 14.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", diff --git a/iosApp/iosApp.xcodeproj/project.pbxproj b/iosApp/iosApp.xcodeproj/project.pbxproj index 0d10da7..d58cef2 100644 --- a/iosApp/iosApp.xcodeproj/project.pbxproj +++ b/iosApp/iosApp.xcodeproj/project.pbxproj @@ -9,21 +9,182 @@ /* Begin PBXBuildFile section */ 058557BB273AAA24004C7B11 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 058557BA273AAA24004C7B11 /* Assets.xcassets */; }; 058557D9273AAEEB004C7B11 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */; }; + 204461E92DF06025009AF7B6 /* Pods_iosApp.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3F1F0F9655FE3CEE26EB7A52 /* Pods_iosApp.framework */; }; + 204461EA2DF06025009AF7B6 /* Pods_iosApp.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 3F1F0F9655FE3CEE26EB7A52 /* Pods_iosApp.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + 204461ED2DF06034009AF7B6 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 204461EC2DF06034009AF7B6 /* Foundation.framework */; }; + 204461EF2DF06041009AF7B6 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 204461EE2DF06041009AF7B6 /* Security.framework */; }; + 204462612DF07D18009AF7B6 /* ReadyDrawObj.m in Sources */ = {isa = PBXBuildFile; fileRef = 2044623A2DF07D18009AF7B6 /* ReadyDrawObj.m */; }; + 204462622DF07D18009AF7B6 /* DeviceCenter.m in Sources */ = {isa = PBXBuildFile; fileRef = 204462482DF07D18009AF7B6 /* DeviceCenter.m */; }; + 204462632DF07D18009AF7B6 /* ActivityObj.m in Sources */ = {isa = PBXBuildFile; fileRef = 204462312DF07D18009AF7B6 /* ActivityObj.m */; }; + 204462642DF07D18009AF7B6 /* MJPropertyType.m in Sources */ = {isa = PBXBuildFile; fileRef = 204462242DF07D18009AF7B6 /* MJPropertyType.m */; }; + 204462652DF07D18009AF7B6 /* MJExtensionConst.m in Sources */ = {isa = PBXBuildFile; fileRef = 2044621C2DF07D18009AF7B6 /* MJExtensionConst.m */; }; + 204462662DF07D18009AF7B6 /* NSObject+MJCoding.m in Sources */ = {isa = PBXBuildFile; fileRef = 204462282DF07D18009AF7B6 /* NSObject+MJCoding.m */; }; + 204462672DF07D18009AF7B6 /* MJProperty.m in Sources */ = {isa = PBXBuildFile; fileRef = 204462202DF07D18009AF7B6 /* MJProperty.m */; }; + 204462682DF07D18009AF7B6 /* NSObject+MJClass.m in Sources */ = {isa = PBXBuildFile; fileRef = 204462262DF07D18009AF7B6 /* NSObject+MJClass.m */; }; + 204462692DF07D18009AF7B6 /* NSObject+MJProperty.m in Sources */ = {isa = PBXBuildFile; fileRef = 2044622C2DF07D18009AF7B6 /* NSObject+MJProperty.m */; }; + 2044626A2DF07D18009AF7B6 /* SleepObj.m in Sources */ = {isa = PBXBuildFile; fileRef = 2044623F2DF07D18009AF7B6 /* SleepObj.m */; }; + 2044626B2DF07D18009AF7B6 /* MJPropertyKey.m in Sources */ = {isa = PBXBuildFile; fileRef = 204462222DF07D18009AF7B6 /* MJPropertyKey.m */; }; + 2044626C2DF07D18009AF7B6 /* MJFoundation.m in Sources */ = {isa = PBXBuildFile; fileRef = 2044621E2DF07D18009AF7B6 /* MJFoundation.m */; }; + 2044626D2DF07D18009AF7B6 /* NSDate+DateTools.m in Sources */ = {isa = PBXBuildFile; fileRef = 204462182DF07D18009AF7B6 /* NSDate+DateTools.m */; }; + 2044626E2DF07D18009AF7B6 /* NSObject+MJKeyValue.m in Sources */ = {isa = PBXBuildFile; fileRef = 2044622A2DF07D18009AF7B6 /* NSObject+MJKeyValue.m */; }; + 2044626F2DF07D18009AF7B6 /* OTAHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 204462352DF07D18009AF7B6 /* OTAHelper.m */; }; + 204462702DF07D18009AF7B6 /* DTTimePeriodChain.m in Sources */ = {isa = PBXBuildFile; fileRef = 204462122DF07D18009AF7B6 /* DTTimePeriodChain.m */; }; + 204462712DF07D18009AF7B6 /* DTError.m in Sources */ = {isa = PBXBuildFile; fileRef = 2044620E2DF07D18009AF7B6 /* DTError.m */; }; + 204462722DF07D18009AF7B6 /* LocalizeTool.m in Sources */ = {isa = PBXBuildFile; fileRef = 2044624D2DF07D18009AF7B6 /* LocalizeTool.m */; }; + 204462732DF07D18009AF7B6 /* HMUserdefaultUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 2044624A2DF07D18009AF7B6 /* HMUserdefaultUtil.m */; }; + 204462742DF07D18009AF7B6 /* SleepHrObj.m in Sources */ = {isa = PBXBuildFile; fileRef = 2044623D2DF07D18009AF7B6 /* SleepHrObj.m */; }; + 204462752DF07D18009AF7B6 /* DTTimePeriodCollection.m in Sources */ = {isa = PBXBuildFile; fileRef = 204462142DF07D18009AF7B6 /* DTTimePeriodCollection.m */; }; + 204462762DF07D18009AF7B6 /* DTTimePeriod.m in Sources */ = {isa = PBXBuildFile; fileRef = 204462102DF07D18009AF7B6 /* DTTimePeriod.m */; }; + 204462772DF07D18009AF7B6 /* OusideBleDiscovery.m in Sources */ = {isa = PBXBuildFile; fileRef = 204462442DF07D18009AF7B6 /* OusideBleDiscovery.m */; }; + 204462782DF07D18009AF7B6 /* NSString+MJExtension.m in Sources */ = {isa = PBXBuildFile; fileRef = 2044622E2DF07D18009AF7B6 /* NSString+MJExtension.m */; }; + 204462792DF07D18009AF7B6 /* LTPHud.m in Sources */ = {isa = PBXBuildFile; fileRef = 2044625C2DF07D18009AF7B6 /* LTPHud.m */; }; + 2044627A2DF07D18009AF7B6 /* NSDate+HMTools.m in Sources */ = {isa = PBXBuildFile; fileRef = 204462502DF07D18009AF7B6 /* NSDate+HMTools.m */; }; + 2044627B2DF07D18009AF7B6 /* SleepTimeDrawObj.m in Sources */ = {isa = PBXBuildFile; fileRef = 204462412DF07D18009AF7B6 /* SleepTimeDrawObj.m */; }; + 2044627C2DF07D18009AF7B6 /* NSObject+Tool.m in Sources */ = {isa = PBXBuildFile; fileRef = 2044625E2DF07D18009AF7B6 /* NSObject+Tool.m */; }; + 2044627D2DF07D18009AF7B6 /* DTTimePeriodGroup.m in Sources */ = {isa = PBXBuildFile; fileRef = 204462162DF07D18009AF7B6 /* DTTimePeriodGroup.m */; }; + 2044627E2DF07D18009AF7B6 /* DTConstants.m in Sources */ = {isa = PBXBuildFile; fileRef = 2044620C2DF07D18009AF7B6 /* DTConstants.m */; }; + 2044627F2DF07D18009AF7B6 /* OTAImgObj.m in Sources */ = {isa = PBXBuildFile; fileRef = 204462372DF07D18009AF7B6 /* OTAImgObj.m */; }; + 204462802DF07D18009AF7B6 /* SRDeviceInfo+description.m in Sources */ = {isa = PBXBuildFile; fileRef = 204462522DF07D18009AF7B6 /* SRDeviceInfo+description.m */; }; + 204462812DF07D18009AF7B6 /* libRingSDK_2.0.2.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 2044625A2DF07D18009AF7B6 /* libRingSDK_2.0.2.a */; }; + 2044628A2DF07D95009AF7B6 /* NSString+Check.m in Sources */ = {isa = PBXBuildFile; fileRef = 204462892DF07D95009AF7B6 /* NSString+Check.m */; }; + 204462B02DF0935D009AF7B6 /* CoreBluetooth.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 204462AF2DF0935D009AF7B6 /* CoreBluetooth.framework */; }; + 204463442DF1EA80009AF7B6 /* LTSRingSDK+Desc.m in Sources */ = {isa = PBXBuildFile; fileRef = 204463432DF1EA80009AF7B6 /* LTSRingSDK+Desc.m */; }; 2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2152FB032600AC8F00CF470E /* iOSApp.swift */; }; - 41F867E78A3D26751CF26651 /* Pods_iosApp.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 50D4814DBF5E896805BB34AB /* Pods_iosApp.framework */; }; 7555FF83242A565900829871 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7555FF82242A565900829871 /* ContentView.swift */; }; /* End PBXBuildFile section */ +/* Begin PBXCopyFilesBuildPhase section */ + 204461EB2DF06025009AF7B6 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + 204461EA2DF06025009AF7B6 /* Pods_iosApp.framework in Embed Frameworks */, + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + /* Begin PBXFileReference section */ 058557BA273AAA24004C7B11 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 058557D8273AAEEB004C7B11 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; - 1C1DFA9EB164DC5B3278E19E /* Pods-iosApp.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iosApp.debug.xcconfig"; path = "Target Support Files/Pods-iosApp/Pods-iosApp.debug.xcconfig"; sourceTree = ""; }; + 204461EC2DF06034009AF7B6 /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; + 204461EE2DF06041009AF7B6 /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; }; + 204461F02DF06099009AF7B6 /* libRingSDK_2.0.2.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libRingSDK_2.0.2.a; path = iosApp/Libs/libRingSDK_2.0.2.a; sourceTree = ""; }; + 204461F12DF07C89009AF7B6 /* iosApp-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "iosApp-Bridging-Header.h"; sourceTree = ""; }; + 204461F42DF07D18009AF7B6 /* SRBLeService.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SRBLeService.h; sourceTree = ""; }; + 204461F52DF07D18009AF7B6 /* SRDeviceInfo.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SRDeviceInfo.h; sourceTree = ""; }; + 204461F72DF07D18009AF7B6 /* EcgParamObj.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = EcgParamObj.h; sourceTree = ""; }; + 204461F82DF07D18009AF7B6 /* SrEcgAlgorithmResults.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SrEcgAlgorithmResults.h; sourceTree = ""; }; + 204461F92DF07D18009AF7B6 /* SrEcgData.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SrEcgData.h; sourceTree = ""; }; + 204461FB2DF07D18009AF7B6 /* LTSRingSDK.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LTSRingSDK.h; sourceTree = ""; }; + 204461FC2DF07D18009AF7B6 /* SDKConstant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDKConstant.h; sourceTree = ""; }; + 204461FE2DF07D18009AF7B6 /* DBDevices.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DBDevices.h; sourceTree = ""; }; + 204461FF2DF07D18009AF7B6 /* DBHeartRate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DBHeartRate.h; sourceTree = ""; }; + 204462002DF07D18009AF7B6 /* DBHistoryDataSr03.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DBHistoryDataSr03.h; sourceTree = ""; }; + 204462012DF07D18009AF7B6 /* DBHrv.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DBHrv.h; sourceTree = ""; }; + 204462022DF07D18009AF7B6 /* DBOxygen.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DBOxygen.h; sourceTree = ""; }; + 204462032DF07D18009AF7B6 /* DBRespiratory.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DBRespiratory.h; sourceTree = ""; }; + 204462042DF07D18009AF7B6 /* DBSleepData.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DBSleepData.h; sourceTree = ""; }; + 204462052DF07D18009AF7B6 /* DBSteps.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DBSteps.h; sourceTree = ""; }; + 204462062DF07D18009AF7B6 /* DBTables.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DBTables.h; sourceTree = ""; }; + 204462072DF07D18009AF7B6 /* DBThermemoter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DBThermemoter.h; sourceTree = ""; }; + 204462082DF07D18009AF7B6 /* DBValueSuper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DBValueSuper.h; sourceTree = ""; }; + 2044620A2DF07D18009AF7B6 /* DateTools.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DateTools.h; sourceTree = ""; }; + 2044620B2DF07D18009AF7B6 /* DTConstants.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DTConstants.h; sourceTree = ""; }; + 2044620C2DF07D18009AF7B6 /* DTConstants.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DTConstants.m; sourceTree = ""; }; + 2044620D2DF07D18009AF7B6 /* DTError.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DTError.h; sourceTree = ""; }; + 2044620E2DF07D18009AF7B6 /* DTError.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DTError.m; sourceTree = ""; }; + 2044620F2DF07D18009AF7B6 /* DTTimePeriod.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DTTimePeriod.h; sourceTree = ""; }; + 204462102DF07D18009AF7B6 /* DTTimePeriod.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DTTimePeriod.m; sourceTree = ""; }; + 204462112DF07D18009AF7B6 /* DTTimePeriodChain.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DTTimePeriodChain.h; sourceTree = ""; }; + 204462122DF07D18009AF7B6 /* DTTimePeriodChain.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DTTimePeriodChain.m; sourceTree = ""; }; + 204462132DF07D18009AF7B6 /* DTTimePeriodCollection.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DTTimePeriodCollection.h; sourceTree = ""; }; + 204462142DF07D18009AF7B6 /* DTTimePeriodCollection.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DTTimePeriodCollection.m; sourceTree = ""; }; + 204462152DF07D18009AF7B6 /* DTTimePeriodGroup.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DTTimePeriodGroup.h; sourceTree = ""; }; + 204462162DF07D18009AF7B6 /* DTTimePeriodGroup.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DTTimePeriodGroup.m; sourceTree = ""; }; + 204462172DF07D18009AF7B6 /* NSDate+DateTools.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSDate+DateTools.h"; sourceTree = ""; }; + 204462182DF07D18009AF7B6 /* NSDate+DateTools.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSDate+DateTools.m"; sourceTree = ""; }; + 2044621A2DF07D18009AF7B6 /* MJExtension.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MJExtension.h; sourceTree = ""; }; + 2044621B2DF07D18009AF7B6 /* MJExtensionConst.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MJExtensionConst.h; sourceTree = ""; }; + 2044621C2DF07D18009AF7B6 /* MJExtensionConst.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MJExtensionConst.m; sourceTree = ""; }; + 2044621D2DF07D18009AF7B6 /* MJFoundation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MJFoundation.h; sourceTree = ""; }; + 2044621E2DF07D18009AF7B6 /* MJFoundation.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MJFoundation.m; sourceTree = ""; }; + 2044621F2DF07D18009AF7B6 /* MJProperty.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MJProperty.h; sourceTree = ""; }; + 204462202DF07D18009AF7B6 /* MJProperty.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MJProperty.m; sourceTree = ""; }; + 204462212DF07D18009AF7B6 /* MJPropertyKey.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MJPropertyKey.h; sourceTree = ""; }; + 204462222DF07D18009AF7B6 /* MJPropertyKey.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MJPropertyKey.m; sourceTree = ""; }; + 204462232DF07D18009AF7B6 /* MJPropertyType.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MJPropertyType.h; sourceTree = ""; }; + 204462242DF07D18009AF7B6 /* MJPropertyType.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = MJPropertyType.m; sourceTree = ""; }; + 204462252DF07D18009AF7B6 /* NSObject+MJClass.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSObject+MJClass.h"; sourceTree = ""; }; + 204462262DF07D18009AF7B6 /* NSObject+MJClass.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSObject+MJClass.m"; sourceTree = ""; }; + 204462272DF07D18009AF7B6 /* NSObject+MJCoding.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSObject+MJCoding.h"; sourceTree = ""; }; + 204462282DF07D18009AF7B6 /* NSObject+MJCoding.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSObject+MJCoding.m"; sourceTree = ""; }; + 204462292DF07D18009AF7B6 /* NSObject+MJKeyValue.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSObject+MJKeyValue.h"; sourceTree = ""; }; + 2044622A2DF07D18009AF7B6 /* NSObject+MJKeyValue.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSObject+MJKeyValue.m"; sourceTree = ""; }; + 2044622B2DF07D18009AF7B6 /* NSObject+MJProperty.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSObject+MJProperty.h"; sourceTree = ""; }; + 2044622C2DF07D18009AF7B6 /* NSObject+MJProperty.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSObject+MJProperty.m"; sourceTree = ""; }; + 2044622D2DF07D18009AF7B6 /* NSString+MJExtension.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSString+MJExtension.h"; sourceTree = ""; }; + 2044622E2DF07D18009AF7B6 /* NSString+MJExtension.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSString+MJExtension.m"; sourceTree = ""; }; + 204462302DF07D18009AF7B6 /* ActivityObj.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ActivityObj.h; sourceTree = ""; }; + 204462312DF07D18009AF7B6 /* ActivityObj.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ActivityObj.m; sourceTree = ""; }; + 204462332DF07D18009AF7B6 /* OTAHeader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OTAHeader.h; sourceTree = ""; }; + 204462342DF07D18009AF7B6 /* OTAHelper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OTAHelper.h; sourceTree = ""; }; + 204462352DF07D18009AF7B6 /* OTAHelper.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OTAHelper.m; sourceTree = ""; }; + 204462362DF07D18009AF7B6 /* OTAImgObj.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OTAImgObj.h; sourceTree = ""; }; + 204462372DF07D18009AF7B6 /* OTAImgObj.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OTAImgObj.m; sourceTree = ""; }; + 204462392DF07D18009AF7B6 /* ReadyDrawObj.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ReadyDrawObj.h; sourceTree = ""; }; + 2044623A2DF07D18009AF7B6 /* ReadyDrawObj.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ReadyDrawObj.m; sourceTree = ""; }; + 2044623C2DF07D18009AF7B6 /* SleepHrObj.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SleepHrObj.h; sourceTree = ""; }; + 2044623D2DF07D18009AF7B6 /* SleepHrObj.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SleepHrObj.m; sourceTree = ""; }; + 2044623E2DF07D18009AF7B6 /* SleepObj.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SleepObj.h; sourceTree = ""; }; + 2044623F2DF07D18009AF7B6 /* SleepObj.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SleepObj.m; sourceTree = ""; }; + 204462402DF07D18009AF7B6 /* SleepTimeDrawObj.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SleepTimeDrawObj.h; sourceTree = ""; }; + 204462412DF07D18009AF7B6 /* SleepTimeDrawObj.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SleepTimeDrawObj.m; sourceTree = ""; }; + 204462432DF07D18009AF7B6 /* OusideBleDiscovery.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = OusideBleDiscovery.h; sourceTree = ""; }; + 204462442DF07D18009AF7B6 /* OusideBleDiscovery.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OusideBleDiscovery.m; sourceTree = ""; }; + 204462462DF07D18009AF7B6 /* ConfigModel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ConfigModel.h; sourceTree = ""; }; + 204462472DF07D18009AF7B6 /* DeviceCenter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = DeviceCenter.h; sourceTree = ""; }; + 204462482DF07D18009AF7B6 /* DeviceCenter.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = DeviceCenter.m; sourceTree = ""; }; + 204462492DF07D18009AF7B6 /* HMUserdefaultUtil.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HMUserdefaultUtil.h; sourceTree = ""; }; + 2044624A2DF07D18009AF7B6 /* HMUserdefaultUtil.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = HMUserdefaultUtil.m; sourceTree = ""; }; + 2044624B2DF07D18009AF7B6 /* LocalizeKeys.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LocalizeKeys.h; sourceTree = ""; }; + 2044624C2DF07D18009AF7B6 /* LocalizeTool.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LocalizeTool.h; sourceTree = ""; }; + 2044624D2DF07D18009AF7B6 /* LocalizeTool.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LocalizeTool.m; sourceTree = ""; }; + 2044624E2DF07D18009AF7B6 /* NotificationNameHeader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = NotificationNameHeader.h; sourceTree = ""; }; + 2044624F2DF07D18009AF7B6 /* NSDate+HMTools.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSDate+HMTools.h"; sourceTree = ""; }; + 204462502DF07D18009AF7B6 /* NSDate+HMTools.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSDate+HMTools.m"; sourceTree = ""; }; + 204462512DF07D18009AF7B6 /* SRDeviceInfo+description.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SRDeviceInfo+description.h"; sourceTree = ""; }; + 204462522DF07D18009AF7B6 /* SRDeviceInfo+description.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "SRDeviceInfo+description.m"; sourceTree = ""; }; + 204462542DF07D18009AF7B6 /* SleepStageHeader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SleepStageHeader.h; sourceTree = ""; }; + 204462552DF07D18009AF7B6 /* StagingDataV2.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = StagingDataV2.h; sourceTree = ""; }; + 204462562DF07D18009AF7B6 /* StagingListObj.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = StagingListObj.h; sourceTree = ""; }; + 204462572DF07D18009AF7B6 /* StagingSubObj.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = StagingSubObj.h; sourceTree = ""; }; + 204462592DF07D18009AF7B6 /* Colors.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = Colors.h; sourceTree = ""; }; + 2044625A2DF07D18009AF7B6 /* libRingSDK_2.0.2.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libRingSDK_2.0.2.a; sourceTree = ""; }; + 2044625B2DF07D18009AF7B6 /* LTPHud.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LTPHud.h; sourceTree = ""; }; + 2044625C2DF07D18009AF7B6 /* LTPHud.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = LTPHud.m; sourceTree = ""; }; + 2044625D2DF07D18009AF7B6 /* NSObject+Tool.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSObject+Tool.h"; sourceTree = ""; }; + 2044625E2DF07D18009AF7B6 /* NSObject+Tool.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSObject+Tool.m"; sourceTree = ""; }; + 2044625F2DF07D18009AF7B6 /* TimeUtils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TimeUtils.h; sourceTree = ""; }; + 204462852DF07D77009AF7B6 /* HMTabbarController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = HMTabbarController.h; sourceTree = ""; }; + 204462892DF07D95009AF7B6 /* NSString+Check.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSString+Check.m"; sourceTree = ""; }; + 2044628B2DF07DCD009AF7B6 /* LTSRingSDK+Desc.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "LTSRingSDK+Desc.h"; sourceTree = ""; }; + 2044628E2DF07DF9009AF7B6 /* LoginVc.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LoginVc.h; sourceTree = ""; }; + 204462912DF07E1A009AF7B6 /* MainNav.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MainNav.h; sourceTree = ""; }; + 204462AC2DF082FA009AF7B6 /* NSString+Check.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSString+Check.h"; sourceTree = ""; }; + 204462AF2DF0935D009AF7B6 /* CoreBluetooth.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreBluetooth.framework; path = System/Library/Frameworks/CoreBluetooth.framework; sourceTree = SDKROOT; }; + 204463422DF1DC53009AF7B6 /* PrefixHeader.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PrefixHeader.pch; sourceTree = ""; }; + 204463432DF1EA80009AF7B6 /* LTSRingSDK+Desc.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "LTSRingSDK+Desc.m"; sourceTree = ""; }; 2152FB032600AC8F00CF470E /* iOSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSApp.swift; sourceTree = ""; }; - 50D4814DBF5E896805BB34AB /* Pods_iosApp.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_iosApp.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 2BB8C8CFB6051CAD0EEB82BE /* Pods-iosApp.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iosApp.release.xcconfig"; path = "Target Support Files/Pods-iosApp/Pods-iosApp.release.xcconfig"; sourceTree = ""; }; + 3F1F0F9655FE3CEE26EB7A52 /* Pods_iosApp.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_iosApp.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 7555FF7B242A565900829871 /* iosApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = iosApp.app; sourceTree = BUILT_PRODUCTS_DIR; }; 7555FF82242A565900829871 /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 7555FF8C242A565B00829871 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - ECED503BC33901942998D2B9 /* Pods-iosApp.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iosApp.release.xcconfig"; path = "Target Support Files/Pods-iosApp/Pods-iosApp.release.xcconfig"; sourceTree = ""; }; + 7D35E63CAA7E76EF357D18B8 /* Pods-iosApp.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iosApp.debug.xcconfig"; path = "Target Support Files/Pods-iosApp/Pods-iosApp.debug.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -31,7 +192,11 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 41F867E78A3D26751CF26651 /* Pods_iosApp.framework in Frameworks */, + 204461E92DF06025009AF7B6 /* Pods_iosApp.framework in Frameworks */, + 204462812DF07D18009AF7B6 /* libRingSDK_2.0.2.a in Frameworks */, + 204461EF2DF06041009AF7B6 /* Security.framework in Frameworks */, + 204462B02DF0935D009AF7B6 /* CoreBluetooth.framework in Frameworks */, + 204461ED2DF06034009AF7B6 /* Foundation.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -46,19 +211,224 @@ path = "Preview Content"; sourceTree = ""; }; - 34ACFE592B3A075F1F4D7F96 /* Frameworks */ = { + 204461F62DF07D18009AF7B6 /* Ble */ = { isa = PBXGroup; children = ( - 50D4814DBF5E896805BB34AB /* Pods_iosApp.framework */, + 204461F42DF07D18009AF7B6 /* SRBLeService.h */, + 204461F52DF07D18009AF7B6 /* SRDeviceInfo.h */, ); - name = Frameworks; + path = Ble; + sourceTree = ""; + }; + 204461FA2DF07D18009AF7B6 /* Ecg */ = { + isa = PBXGroup; + children = ( + 204461F72DF07D18009AF7B6 /* EcgParamObj.h */, + 204461F82DF07D18009AF7B6 /* SrEcgAlgorithmResults.h */, + 204461F92DF07D18009AF7B6 /* SrEcgData.h */, + ); + path = Ecg; + sourceTree = ""; + }; + 204461FD2DF07D18009AF7B6 /* BLESDK */ = { + isa = PBXGroup; + children = ( + 204461F62DF07D18009AF7B6 /* Ble */, + 204461FA2DF07D18009AF7B6 /* Ecg */, + 204461FB2DF07D18009AF7B6 /* LTSRingSDK.h */, + 204461FC2DF07D18009AF7B6 /* SDKConstant.h */, + ); + path = BLESDK; + sourceTree = ""; + }; + 204462092DF07D18009AF7B6 /* DataBase */ = { + isa = PBXGroup; + children = ( + 204461FE2DF07D18009AF7B6 /* DBDevices.h */, + 204461FF2DF07D18009AF7B6 /* DBHeartRate.h */, + 204462002DF07D18009AF7B6 /* DBHistoryDataSr03.h */, + 204462012DF07D18009AF7B6 /* DBHrv.h */, + 204462022DF07D18009AF7B6 /* DBOxygen.h */, + 204462032DF07D18009AF7B6 /* DBRespiratory.h */, + 204462042DF07D18009AF7B6 /* DBSleepData.h */, + 204462052DF07D18009AF7B6 /* DBSteps.h */, + 204462062DF07D18009AF7B6 /* DBTables.h */, + 204462072DF07D18009AF7B6 /* DBThermemoter.h */, + 204462082DF07D18009AF7B6 /* DBValueSuper.h */, + ); + path = DataBase; + sourceTree = ""; + }; + 204462192DF07D18009AF7B6 /* DateTools */ = { + isa = PBXGroup; + children = ( + 2044620A2DF07D18009AF7B6 /* DateTools.h */, + 2044620B2DF07D18009AF7B6 /* DTConstants.h */, + 2044620C2DF07D18009AF7B6 /* DTConstants.m */, + 2044620D2DF07D18009AF7B6 /* DTError.h */, + 2044620E2DF07D18009AF7B6 /* DTError.m */, + 2044620F2DF07D18009AF7B6 /* DTTimePeriod.h */, + 204462102DF07D18009AF7B6 /* DTTimePeriod.m */, + 204462112DF07D18009AF7B6 /* DTTimePeriodChain.h */, + 204462122DF07D18009AF7B6 /* DTTimePeriodChain.m */, + 204462132DF07D18009AF7B6 /* DTTimePeriodCollection.h */, + 204462142DF07D18009AF7B6 /* DTTimePeriodCollection.m */, + 204462152DF07D18009AF7B6 /* DTTimePeriodGroup.h */, + 204462162DF07D18009AF7B6 /* DTTimePeriodGroup.m */, + 204462172DF07D18009AF7B6 /* NSDate+DateTools.h */, + 204462182DF07D18009AF7B6 /* NSDate+DateTools.m */, + ); + path = DateTools; + sourceTree = ""; + }; + 2044622F2DF07D18009AF7B6 /* MJExtension */ = { + isa = PBXGroup; + children = ( + 2044621A2DF07D18009AF7B6 /* MJExtension.h */, + 2044621B2DF07D18009AF7B6 /* MJExtensionConst.h */, + 2044621C2DF07D18009AF7B6 /* MJExtensionConst.m */, + 2044621D2DF07D18009AF7B6 /* MJFoundation.h */, + 2044621E2DF07D18009AF7B6 /* MJFoundation.m */, + 2044621F2DF07D18009AF7B6 /* MJProperty.h */, + 204462202DF07D18009AF7B6 /* MJProperty.m */, + 204462212DF07D18009AF7B6 /* MJPropertyKey.h */, + 204462222DF07D18009AF7B6 /* MJPropertyKey.m */, + 204462232DF07D18009AF7B6 /* MJPropertyType.h */, + 204462242DF07D18009AF7B6 /* MJPropertyType.m */, + 204462252DF07D18009AF7B6 /* NSObject+MJClass.h */, + 204462262DF07D18009AF7B6 /* NSObject+MJClass.m */, + 204462272DF07D18009AF7B6 /* NSObject+MJCoding.h */, + 204462282DF07D18009AF7B6 /* NSObject+MJCoding.m */, + 204462292DF07D18009AF7B6 /* NSObject+MJKeyValue.h */, + 2044622A2DF07D18009AF7B6 /* NSObject+MJKeyValue.m */, + 2044622B2DF07D18009AF7B6 /* NSObject+MJProperty.h */, + 2044622C2DF07D18009AF7B6 /* NSObject+MJProperty.m */, + 2044622D2DF07D18009AF7B6 /* NSString+MJExtension.h */, + 2044622E2DF07D18009AF7B6 /* NSString+MJExtension.m */, + ); + path = MJExtension; + sourceTree = ""; + }; + 204462322DF07D18009AF7B6 /* AboutActivity */ = { + isa = PBXGroup; + children = ( + 204462302DF07D18009AF7B6 /* ActivityObj.h */, + 204462312DF07D18009AF7B6 /* ActivityObj.m */, + ); + path = AboutActivity; + sourceTree = ""; + }; + 204462382DF07D18009AF7B6 /* AboutOta */ = { + isa = PBXGroup; + children = ( + 204462332DF07D18009AF7B6 /* OTAHeader.h */, + 204462342DF07D18009AF7B6 /* OTAHelper.h */, + 204462352DF07D18009AF7B6 /* OTAHelper.m */, + 204462362DF07D18009AF7B6 /* OTAImgObj.h */, + 204462372DF07D18009AF7B6 /* OTAImgObj.m */, + ); + path = AboutOta; + sourceTree = ""; + }; + 2044623B2DF07D18009AF7B6 /* AboutReady */ = { + isa = PBXGroup; + children = ( + 204462392DF07D18009AF7B6 /* ReadyDrawObj.h */, + 2044623A2DF07D18009AF7B6 /* ReadyDrawObj.m */, + ); + path = AboutReady; + sourceTree = ""; + }; + 204462422DF07D18009AF7B6 /* AboutSleep */ = { + isa = PBXGroup; + children = ( + 2044623C2DF07D18009AF7B6 /* SleepHrObj.h */, + 2044623D2DF07D18009AF7B6 /* SleepHrObj.m */, + 2044623E2DF07D18009AF7B6 /* SleepObj.h */, + 2044623F2DF07D18009AF7B6 /* SleepObj.m */, + 204462402DF07D18009AF7B6 /* SleepTimeDrawObj.h */, + 204462412DF07D18009AF7B6 /* SleepTimeDrawObj.m */, + ); + path = AboutSleep; + sourceTree = ""; + }; + 204462452DF07D18009AF7B6 /* OusideBle */ = { + isa = PBXGroup; + children = ( + 204462432DF07D18009AF7B6 /* OusideBleDiscovery.h */, + 204462442DF07D18009AF7B6 /* OusideBleDiscovery.m */, + ); + path = OusideBle; + sourceTree = ""; + }; + 204462532DF07D18009AF7B6 /* Modules */ = { + isa = PBXGroup; + children = ( + 204462322DF07D18009AF7B6 /* AboutActivity */, + 204462382DF07D18009AF7B6 /* AboutOta */, + 2044623B2DF07D18009AF7B6 /* AboutReady */, + 204462422DF07D18009AF7B6 /* AboutSleep */, + 204462452DF07D18009AF7B6 /* OusideBle */, + 204462462DF07D18009AF7B6 /* ConfigModel.h */, + 204462472DF07D18009AF7B6 /* DeviceCenter.h */, + 204462482DF07D18009AF7B6 /* DeviceCenter.m */, + 204462492DF07D18009AF7B6 /* HMUserdefaultUtil.h */, + 2044624A2DF07D18009AF7B6 /* HMUserdefaultUtil.m */, + 2044624B2DF07D18009AF7B6 /* LocalizeKeys.h */, + 2044624C2DF07D18009AF7B6 /* LocalizeTool.h */, + 2044624D2DF07D18009AF7B6 /* LocalizeTool.m */, + 2044624E2DF07D18009AF7B6 /* NotificationNameHeader.h */, + 2044624F2DF07D18009AF7B6 /* NSDate+HMTools.h */, + 204462502DF07D18009AF7B6 /* NSDate+HMTools.m */, + 204462512DF07D18009AF7B6 /* SRDeviceInfo+description.h */, + 204462522DF07D18009AF7B6 /* SRDeviceInfo+description.m */, + ); + path = Modules; + sourceTree = ""; + }; + 204462582DF07D18009AF7B6 /* SleepStagingV2 */ = { + isa = PBXGroup; + children = ( + 204462542DF07D18009AF7B6 /* SleepStageHeader.h */, + 204462552DF07D18009AF7B6 /* StagingDataV2.h */, + 204462562DF07D18009AF7B6 /* StagingListObj.h */, + 204462572DF07D18009AF7B6 /* StagingSubObj.h */, + ); + path = SleepStagingV2; + sourceTree = ""; + }; + 204462602DF07D18009AF7B6 /* Libs */ = { + isa = PBXGroup; + children = ( + 204463432DF1EA80009AF7B6 /* LTSRingSDK+Desc.m */, + 204461FD2DF07D18009AF7B6 /* BLESDK */, + 204462092DF07D18009AF7B6 /* DataBase */, + 204462192DF07D18009AF7B6 /* DateTools */, + 2044622F2DF07D18009AF7B6 /* MJExtension */, + 204462532DF07D18009AF7B6 /* Modules */, + 204462582DF07D18009AF7B6 /* SleepStagingV2 */, + 204462592DF07D18009AF7B6 /* Colors.h */, + 2044625A2DF07D18009AF7B6 /* libRingSDK_2.0.2.a */, + 2044625B2DF07D18009AF7B6 /* LTPHud.h */, + 2044625C2DF07D18009AF7B6 /* LTPHud.m */, + 2044625D2DF07D18009AF7B6 /* NSObject+Tool.h */, + 2044625E2DF07D18009AF7B6 /* NSObject+Tool.m */, + 2044625F2DF07D18009AF7B6 /* TimeUtils.h */, + 204462852DF07D77009AF7B6 /* HMTabbarController.h */, + 204462892DF07D95009AF7B6 /* NSString+Check.m */, + 2044628B2DF07DCD009AF7B6 /* LTSRingSDK+Desc.h */, + 2044628E2DF07DF9009AF7B6 /* LoginVc.h */, + 204462912DF07E1A009AF7B6 /* MainNav.h */, + 204462AC2DF082FA009AF7B6 /* NSString+Check.h */, + ); + path = Libs; sourceTree = ""; }; 6659EF739E744394EC711C80 /* Pods */ = { isa = PBXGroup; children = ( - 1C1DFA9EB164DC5B3278E19E /* Pods-iosApp.debug.xcconfig */, - ECED503BC33901942998D2B9 /* Pods-iosApp.release.xcconfig */, + 7D35E63CAA7E76EF357D18B8 /* Pods-iosApp.debug.xcconfig */, + 2BB8C8CFB6051CAD0EEB82BE /* Pods-iosApp.release.xcconfig */, ); path = Pods; sourceTree = ""; @@ -69,7 +439,7 @@ 7555FF7D242A565900829871 /* iosApp */, 7555FF7C242A565900829871 /* Products */, 6659EF739E744394EC711C80 /* Pods */, - 34ACFE592B3A075F1F4D7F96 /* Frameworks */, + 7F492F9D678DB5C178EE9E8B /* Frameworks */, ); sourceTree = ""; }; @@ -84,15 +454,30 @@ 7555FF7D242A565900829871 /* iosApp */ = { isa = PBXGroup; children = ( + 204463422DF1DC53009AF7B6 /* PrefixHeader.pch */, 058557BA273AAA24004C7B11 /* Assets.xcassets */, 7555FF82242A565900829871 /* ContentView.swift */, 7555FF8C242A565B00829871 /* Info.plist */, 2152FB032600AC8F00CF470E /* iOSApp.swift */, 058557D7273AAEEB004C7B11 /* Preview Content */, + 204461F12DF07C89009AF7B6 /* iosApp-Bridging-Header.h */, + 204462602DF07D18009AF7B6 /* Libs */, ); path = iosApp; sourceTree = ""; }; + 7F492F9D678DB5C178EE9E8B /* Frameworks */ = { + isa = PBXGroup; + children = ( + 204462AF2DF0935D009AF7B6 /* CoreBluetooth.framework */, + 204461F02DF06099009AF7B6 /* libRingSDK_2.0.2.a */, + 204461EE2DF06041009AF7B6 /* Security.framework */, + 204461EC2DF06034009AF7B6 /* Foundation.framework */, + 3F1F0F9655FE3CEE26EB7A52 /* Pods_iosApp.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -100,12 +485,13 @@ isa = PBXNativeTarget; buildConfigurationList = 7555FFA5242A565B00829871 /* Build configuration list for PBXNativeTarget "iosApp" */; buildPhases = ( - BF04EABC7586D2B239E221C0 /* [CP] Check Pods Manifest.lock */, + 5FE676F1E9B776E5AB187D7D /* [CP] Check Pods Manifest.lock */, 7555FF77242A565900829871 /* Sources */, 7555FF79242A565900829871 /* Resources */, 708C81AE081D7C5D6DEC1EE0 /* Frameworks */, - 7CBF11B78E4F93CAB7A07BF6 /* [CP] Copy Pods Resources */, - E73A1ECB2BDAB9AAC398F036 /* [CP] Embed Pods Frameworks */, + 9126EA7ACCFC690C91067081 /* [CP] Embed Pods Frameworks */, + 204461EB2DF06025009AF7B6 /* Embed Frameworks */, + E2756170C1E36A35E5DCC589 /* [CP] Copy Pods Resources */, ); buildRules = ( ); @@ -128,6 +514,7 @@ TargetAttributes = { 7555FF7A242A565900829871 = { CreatedOnToolsVersion = 11.3.1; + LastSwiftMigration = 1630; }; }; }; @@ -162,24 +549,7 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 7CBF11B78E4F93CAB7A07BF6 /* [CP] Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-resources-${CONFIGURATION}-input-files.xcfilelist", - ); - name = "[CP] Copy Pods Resources"; - outputFileListPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-resources-${CONFIGURATION}-output-files.xcfilelist", - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-resources.sh\"\n"; - showEnvVarsInLog = 0; - }; - BF04EABC7586D2B239E221C0 /* [CP] Check Pods Manifest.lock */ = { + 5FE676F1E9B776E5AB187D7D /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -201,7 +571,7 @@ 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; }; - E73A1ECB2BDAB9AAC398F036 /* [CP] Embed Pods Frameworks */ = { + 9126EA7ACCFC690C91067081 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( @@ -218,6 +588,23 @@ shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; + E2756170C1E36A35E5DCC589 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-resources-${CONFIGURATION}-input-files.xcfilelist", + ); + name = "[CP] Copy Pods Resources"; + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-resources-${CONFIGURATION}-output-files.xcfilelist", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -226,6 +613,40 @@ buildActionMask = 2147483647; files = ( 2152FB042600AC8F00CF470E /* iOSApp.swift in Sources */, + 204462612DF07D18009AF7B6 /* ReadyDrawObj.m in Sources */, + 204462622DF07D18009AF7B6 /* DeviceCenter.m in Sources */, + 204462632DF07D18009AF7B6 /* ActivityObj.m in Sources */, + 204462642DF07D18009AF7B6 /* MJPropertyType.m in Sources */, + 204462652DF07D18009AF7B6 /* MJExtensionConst.m in Sources */, + 204462662DF07D18009AF7B6 /* NSObject+MJCoding.m in Sources */, + 204462672DF07D18009AF7B6 /* MJProperty.m in Sources */, + 204462682DF07D18009AF7B6 /* NSObject+MJClass.m in Sources */, + 204462692DF07D18009AF7B6 /* NSObject+MJProperty.m in Sources */, + 2044626A2DF07D18009AF7B6 /* SleepObj.m in Sources */, + 2044626B2DF07D18009AF7B6 /* MJPropertyKey.m in Sources */, + 2044626C2DF07D18009AF7B6 /* MJFoundation.m in Sources */, + 2044626D2DF07D18009AF7B6 /* NSDate+DateTools.m in Sources */, + 2044626E2DF07D18009AF7B6 /* NSObject+MJKeyValue.m in Sources */, + 2044626F2DF07D18009AF7B6 /* OTAHelper.m in Sources */, + 204462702DF07D18009AF7B6 /* DTTimePeriodChain.m in Sources */, + 204462712DF07D18009AF7B6 /* DTError.m in Sources */, + 204462722DF07D18009AF7B6 /* LocalizeTool.m in Sources */, + 204462732DF07D18009AF7B6 /* HMUserdefaultUtil.m in Sources */, + 204462742DF07D18009AF7B6 /* SleepHrObj.m in Sources */, + 2044628A2DF07D95009AF7B6 /* NSString+Check.m in Sources */, + 204462752DF07D18009AF7B6 /* DTTimePeriodCollection.m in Sources */, + 204462762DF07D18009AF7B6 /* DTTimePeriod.m in Sources */, + 204462772DF07D18009AF7B6 /* OusideBleDiscovery.m in Sources */, + 204462782DF07D18009AF7B6 /* NSString+MJExtension.m in Sources */, + 204462792DF07D18009AF7B6 /* LTPHud.m in Sources */, + 204463442DF1EA80009AF7B6 /* LTSRingSDK+Desc.m in Sources */, + 2044627A2DF07D18009AF7B6 /* NSDate+HMTools.m in Sources */, + 2044627B2DF07D18009AF7B6 /* SleepTimeDrawObj.m in Sources */, + 2044627C2DF07D18009AF7B6 /* NSObject+Tool.m in Sources */, + 2044627D2DF07D18009AF7B6 /* DTTimePeriodGroup.m in Sources */, + 2044627E2DF07D18009AF7B6 /* DTConstants.m in Sources */, + 2044627F2DF07D18009AF7B6 /* OTAImgObj.m in Sources */, + 204462802DF07D18009AF7B6 /* SRDeviceInfo+description.m in Sources */, 7555FF83242A565900829871 /* ContentView.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -284,7 +705,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 16.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -339,7 +760,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 16.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; SDKROOT = iphoneos; @@ -351,21 +772,43 @@ }; 7555FFA6242A565B00829871 /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 1C1DFA9EB164DC5B3278E19E /* Pods-iosApp.debug.xcconfig */; + baseConfigurationReference = 7D35E63CAA7E76EF357D18B8 /* Pods-iosApp.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\""; DEVELOPMENT_TEAM = NLHG3FFX4L; ENABLE_PREVIEWS = YES; + GCC_PREPROCESSOR_DEFINITIONS = ( + "$(inherited)", + "COCOAPODS=1", + "SQLITE_HAS_CODEC=1", + ); INFOPLIST_FILE = iosApp/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 16.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = moe.uni.app; + LIBRARY_SEARCH_PATHS = ( + /Users/anranyusheng/Documents/works/Ring/iosApp/iosApp/Libs, + "$(PROJECT_DIR)/iosApp/Libs", + ); + OTHER_CFLAGS = ( + "-DSQLITE_HAS_CODEC", + "-DSQLITE_TEMP_STORE=3", + "-DSQLCIPHER_CRYPTO_CC", + "-DNDEBUG", + ); + OTHER_LDFLAGS = ( + "-lRingSDK_2.0.2", + "$(inherited)", + ); + PRODUCT_BUNDLE_IDENTIFIER = moe.uni.ring; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "iosApp/iosApp-Bridging-Header.h"; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; @@ -373,9 +816,10 @@ }; 7555FFA7242A565B00829871 /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = ECED503BC33901942998D2B9 /* Pods-iosApp.release.xcconfig */; + baseConfigurationReference = 2BB8C8CFB6051CAD0EEB82BE /* Pods-iosApp.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\""; DEVELOPMENT_TEAM = NLHG3FFX4L; @@ -386,8 +830,23 @@ "$(inherited)", "@executable_path/Frameworks", ); - PRODUCT_BUNDLE_IDENTIFIER = moe.uni.app; + LIBRARY_SEARCH_PATHS = ( + /Users/anranyusheng/Documents/works/Ring/iosApp/iosApp/Libs, + "$(PROJECT_DIR)/iosApp/Libs", + ); + OTHER_CFLAGS = ( + "-DSQLITE_HAS_CODEC", + "-DSQLITE_TEMP_STORE=3", + "-DSQLCIPHER_CRYPTO_CC", + "-DNDEBUG", + ); + OTHER_LDFLAGS = ( + "-lRingSDK_2.0.2", + "$(inherited)", + ); + PRODUCT_BUNDLE_IDENTIFIER = moe.uni.ring; PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_OBJC_BRIDGING_HEADER = "iosApp/iosApp-Bridging-Header.h"; SWIFT_VERSION = 5.0; TARGETED_DEVICE_FAMILY = "1,2"; }; diff --git a/iosApp/iosApp/ContentView.swift b/iosApp/iosApp/ContentView.swift index b1f3f1c..48beafd 100644 --- a/iosApp/iosApp/ContentView.swift +++ b/iosApp/iosApp/ContentView.swift @@ -7,6 +7,7 @@ class TransparentStatusBarViewController: UIViewController { init() { self.composeViewController = MainViewControllerKt.MainViewController() + Platform_iosKt.doInitLogger() super.init(nibName: nil, bundle: nil) } diff --git a/iosApp/iosApp/Info.plist b/iosApp/iosApp/Info.plist index aaf5e18..56f0725 100644 --- a/iosApp/iosApp/Info.plist +++ b/iosApp/iosApp/Info.plist @@ -22,6 +22,10 @@ 1 LSRequiresIPhoneOS + NSBluetoothAlwaysUsageDescription + 此应用需要使用蓝牙来扫描和连接蓝牙设备 + NSBluetoothPeripheralUsageDescription + 此应用需要使用蓝牙来扫描和连接蓝牙设备 UIApplicationSceneManifest UIApplicationSupportsMultipleScenes diff --git a/iosApp/iosApp/Libs/HMTabbarController.h b/iosApp/iosApp/Libs/HMTabbarController.h new file mode 100755 index 0000000..20c7e4e --- /dev/null +++ b/iosApp/iosApp/Libs/HMTabbarController.h @@ -0,0 +1,33 @@ +// +// LNCTabbarController.h +// +// +// Created by lanzhongping on 2020/11/4. +// Copyright © 2020 linktop. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface HMTabbarController : UITabBarController + + +-(void)createTabs; + +/// 添加VC +/// @param vc 控制器 +/// @param title tabbaritem标题 +/// @param imgNm tabbaritem普通图片 +/// @param selecImgNm tabbaritem选中图片 +- (void)addSubControllers:(UIViewController *)vc + Title:(NSString * __nullable)title + ImageName:(NSString *)imgNm + SelectImagName:(NSString *)selecImgNm; + +@property(copy, nonatomic) void (^viewdidAppearBLK)(void); + + +@end + +NS_ASSUME_NONNULL_END diff --git a/iosApp/iosApp/Libs/LTPHud.m b/iosApp/iosApp/Libs/LTPHud.m index 9d0f1b9..9683c1b 100755 --- a/iosApp/iosApp/Libs/LTPHud.m +++ b/iosApp/iosApp/Libs/LTPHud.m @@ -7,7 +7,7 @@ // #import "LTPHud.h" -#import "AppDelegate.h" +//#import "AppDelegate.h" #import @@ -32,7 +32,7 @@ { self = [super init]; if (self) { - self.baseView = ((AppDelegate *)[UIApplication sharedApplication].delegate).window; +// self.baseView = ((AppDelegate *)[UIApplication sharedApplication].delegate).window; self.mbpHud = [[MBProgressHUD alloc]initWithView:self.baseView]; self.mbpHud.minShowTime = 1.0f; self.mbpHud.removeFromSuperViewOnHide = YES; diff --git a/iosApp/iosApp/Libs/LTSRingSDK+Desc.h b/iosApp/iosApp/Libs/LTSRingSDK+Desc.h new file mode 100644 index 0000000..88db9a5 --- /dev/null +++ b/iosApp/iosApp/Libs/LTSRingSDK+Desc.h @@ -0,0 +1,16 @@ +// +// LTSRingSDK+Desc.h +// CareRingApp +// +// Created by Linktop on 2023/8/1. +// + +#import "LTSRingSDK.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface LTSRingSDK (Desc) +-(NSString *)cmdErrorDesc:(EXCUTED_CMD)cmd; +@end + +NS_ASSUME_NONNULL_END diff --git a/iosApp/iosApp/Libs/LTSRingSDK+Desc.m b/iosApp/iosApp/Libs/LTSRingSDK+Desc.m new file mode 100644 index 0000000..19467b5 --- /dev/null +++ b/iosApp/iosApp/Libs/LTSRingSDK+Desc.m @@ -0,0 +1,65 @@ +// +// LTSRingSDK+Desc.m +// CareRingApp +// +// Created by Linktop on 2023/8/1. +// + +#import "LTSRingSDK+Desc.h" + +@implementation LTSRingSDK (Desc) + +-(NSString *)cmdErrorDesc:(EXCUTED_CMD)cmd +{ + NSString *dec = @""; + switch (cmd) { + case EXCUTED_CMD_SET_SPORT_MODE: + { + dec = @"sport mode switch";//@"运动模式开关"; + } + break; + case EXCUTED_CMD_SYNC_TIME: + { + dec = @"time synchronization";//@"时间同步"; + } + break; + case EXCUTED_CMD_GET_STEPS: + { + dec = @"get steps";//@"获取计步"; + } + break; + case EXCUTED_CMD_GET_TEMPERATURE: + { + dec = @"get body temperature";//@"获取体温"; + } + break; + case EXCUTED_CMD_HIS_DATA: + { + dec = @"Historical data reporting";//@"历史数据"; + } + break; + case EXCUTED_CMD_HIS_COUNT: + { + dec = @"Number of historical data";//@"历史数据个数"; + } + break; + + case EXCUTED_CMD_SPORT_MODE: + { + dec = @"sports mode";//@"运动模式"; + } + break; + case EXCUTED_CMD_CLEAR_HIS_DATA: + { + dec = @"Clear device history";//@"清空设备历史记录"; + + } + break; + default: + break; + } + + return dec; +} + +@end diff --git a/iosApp/iosApp/Libs/LoginVc.h b/iosApp/iosApp/Libs/LoginVc.h new file mode 100644 index 0000000..71aecba --- /dev/null +++ b/iosApp/iosApp/Libs/LoginVc.h @@ -0,0 +1,17 @@ +// +// LoginVc.h +// CareRingApp +// +// Created by Linktop on 2022/6/6. +// + +#import +NS_ASSUME_NONNULL_BEGIN + +@interface LoginVc : UIViewController + +@property(strong, nonatomic)NSString *cacheAccount; // 退出登录缓存使用 + +@end + +NS_ASSUME_NONNULL_END diff --git a/iosApp/iosApp/Libs/MainNav.h b/iosApp/iosApp/Libs/MainNav.h new file mode 100644 index 0000000..33ba6e3 --- /dev/null +++ b/iosApp/iosApp/Libs/MainNav.h @@ -0,0 +1,18 @@ +// +// MainNav.h +// +// +// Created by Linktop on 2021/4/20. +// Copyright © 2021 linktop. All rights reserved. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface MainNav : UINavigationController +-(instancetype)initWithRootViewController:(UIViewController *)rootViewController ShowNavBar:(BOOL)show; + +@end + +NS_ASSUME_NONNULL_END diff --git a/iosApp/iosApp/Libs/Modules/AboutActivity/ActivityObj.m b/iosApp/iosApp/Libs/Modules/AboutActivity/ActivityObj.m index 8e97770..9ab65ed 100644 --- a/iosApp/iosApp/Libs/Modules/AboutActivity/ActivityObj.m +++ b/iosApp/iosApp/Libs/Modules/AboutActivity/ActivityObj.m @@ -7,7 +7,7 @@ #import "ActivityObj.h" #import "ConfigModel.h" -#import "BarDrawView.h" +//#import "BarDrawView.h" //#import "tools.h" #import "../../DataBase/DBTables.h" #import "TimeUtils.h" @@ -211,9 +211,9 @@ STRONG_SELF NSMutableArray *objArray = [NSMutableArray arrayWithCapacity:24]; for (int i = 0; i < 24; i++) { - BarDrawObj * drwObj = [[BarDrawObj alloc]init]; - drwObj.hour = i; - [objArray addObject:drwObj]; +// BarDrawObj * drwObj = [[BarDrawObj alloc]init]; +// drwObj.hour = i; +// [objArray addObject:drwObj]; } __block NSNumber *allMax = results.firstObject.value; @@ -221,7 +221,7 @@ [results enumerateObjectsUsingBlock:^(DBHeartRate * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { NSInteger hour = [obj.time hour]; - [objArray[hour].valueArrayOfHour addObject:obj.value]; +// [objArray[hour].valueArrayOfHour addObject:obj.value]; if ([obj.value intValue] > allMax.intValue) { @@ -232,7 +232,7 @@ } }]; for (BarDrawObj *obj in objArray) { - [obj sortValueAsc:YES]; +// [obj sortValueAsc:YES]; } strongSelf.allMaxValue = allMax; diff --git a/iosApp/iosApp/Libs/Modules/AboutOta/OTAImgObj.m b/iosApp/iosApp/Libs/Modules/AboutOta/OTAImgObj.m index 92e05ba..b9b0063 100644 --- a/iosApp/iosApp/Libs/Modules/AboutOta/OTAImgObj.m +++ b/iosApp/iosApp/Libs/Modules/AboutOta/OTAImgObj.m @@ -77,7 +77,7 @@ NSString * const IMG_FLODER = @"devImgs"; NSURLSessionDownloadTask * downloadTask = [_session downloadTaskWithRequest:request completionHandler:^(NSURL * _Nullable location, NSURLResponse * _Nullable response, NSError * _Nullable error) { STRONG_SELF_NOT_CHECK - DebugNSLog(@"文件URL: %@", location); +// DebugNSLog(@"文件URL: %@", location); @@ -90,7 +90,7 @@ NSString * const IMG_FLODER = @"devImgs"; NSError * copyError; [fileMgr moveItemAtPath:location.path toPath:file error:©Error]; - DebugNSLog(@"拷贝文件URL: %@", file); +// DebugNSLog(@"拷贝文件URL: %@", file); strongSelf.sandBoxFileUrl = [NSURL fileURLWithPath:file]; if (strongSelf.downLoadCBK) { dispatch_async(dispatch_get_main_queue(), ^{ @@ -111,7 +111,7 @@ NSString * const IMG_FLODER = @"devImgs"; didReceiveResponse:(NSURLResponse *)response completionHandler:(void (^)(NSURLSessionResponseDisposition disposition))completionHandler { - DebugNSLog(@"本次请求大小 %lld", response.expectedContentLength); +// DebugNSLog(@"本次请求大小 %lld", response.expectedContentLength); completionHandler(NSURLSessionResponseAllow); @@ -120,7 +120,7 @@ NSString * const IMG_FLODER = @"devImgs"; //02 接收服务器返回的数据 (可能调用多次) -(void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data { - DebugNSLog(@"下载 size:%lu", (unsigned long)data.length); +// DebugNSLog(@"下载 size:%lu", (unsigned long)data.length); } @@ -128,7 +128,7 @@ NSString * const IMG_FLODER = @"devImgs"; //03 结束 -(void)URLSessionDidFinishEventsForBackgroundURLSession:(NSURLSession *)session { - DebugNSLog(@"下载结束!"); +// DebugNSLog(@"下载结束!"); } @end diff --git a/iosApp/iosApp/Libs/Modules/DeviceCenter.m b/iosApp/iosApp/Libs/Modules/DeviceCenter.m index 07050b3..b172821 100644 --- a/iosApp/iosApp/Libs/Modules/DeviceCenter.m +++ b/iosApp/iosApp/Libs/Modules/DeviceCenter.m @@ -8,13 +8,13 @@ #import "DeviceCenter.h" #import "ConfigModel.h" -#import "../DataBase/DBTables.h" +#import "DBTables.h" #import "NotificationNameHeader.h" #import "TimeUtils.h" #import "NSString+Check.h" -#import "AboutOta/OTAHelper.h" -#import "../Libs/OusideBle/OusideBleDiscovery.h" +#import "OTAHelper.h" +#import "OusideBleDiscovery.h" #import "LTSRingSDK+Desc.h" #import "SRDeviceInfo+description.h" @@ -59,6 +59,7 @@ NSString * const CP_NAME = @"BlackShark"; + (instancetype)instance { static DeviceCenter *_deviceCenter = nil; static dispatch_once_t onceToken; + NSLog(@"DeviceCenter instance"); dispatch_once(&onceToken, ^{ // 要使用self来调用 _deviceCenter = [[self alloc] init]; @@ -79,6 +80,7 @@ NSString * const CP_NAME = @"BlackShark"; self.sleepQueue = dispatch_queue_create("sleep_queue", DISPATCH_QUEUE_SERIAL); } + NSLog(@"DeviceCenter init!"); return self; } @@ -132,7 +134,7 @@ NSString * const CP_NAME = @"BlackShark"; WEAK_SELF [perphelArray enumerateObjectsUsingBlock:^(SRBLeService * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { STRONG_SELF - DebugNSLog(@"found devcie: %@", obj.macAddress); + NSLog(@"found devcie: %@", obj.macAddress); if ([obj.macAddress isEqual:strongSelf.bindDevice.macAddress]) { // 自动连接 [strongSelf connectDevice:obj]; @@ -175,7 +177,7 @@ NSString * const CP_NAME = @"BlackShark"; -(void)startBleScan { - DebugNSLog(@"ble CBManagerState: %ld", (long)self.sdk.bleCenterManagerState); + NSLog(@"ble CBManagerState: %ld", (long)self.sdk.bleCenterManagerState); if (self.isCustomBleManage) { [self.ousideBleManager startScanning]; } else { @@ -438,12 +440,12 @@ NSString * const CP_NAME = @"BlackShark"; case CBManagerStateUnsupported:// 不支持蓝牙 { //不支持 - DebugNSLog(@"StateChange = CBManagerStateUnsupported"); + NSLog(@"StateChange = CBManagerStateUnsupported"); } break; case CBManagerStatePoweredOff:// 未启动 { - DebugNSLog(@"StateChange = CBManagerStatePoweredOff"); + NSLog(@"StateChange = CBManagerStatePoweredOff"); } @@ -451,27 +453,27 @@ NSString * const CP_NAME = @"BlackShark"; case CBManagerStateUnauthorized: // 未授权 { /* Tell user the app is not allowed. */ - DebugNSLog(@"StateChange = CBManagerStateUnauthorized"); + NSLog(@"StateChange = CBManagerStateUnauthorized"); } break; case CBManagerStateUnknown: // 未知 { /* Bad news, let's wait for another event. */ - DebugNSLog(@"StateChange = CBManagerStateUnknown"); + NSLog(@"StateChange = CBManagerStateUnknown"); } break; case CBManagerStatePoweredOn:// 开启 { - DebugNSLog(@"StateChange = CBManagerStatePoweredOn"); + NSLog(@"StateChange = CBManagerStatePoweredOn"); isOn = YES; } break; case CBManagerStateResetting:// 重置中 { - DebugNSLog(@"StateChange = CBManagerStateResetting"); + NSLog(@"StateChange = CBManagerStateResetting"); break; } @@ -489,7 +491,7 @@ NSString * const CP_NAME = @"BlackShark"; - (void)srBleCmdExcute:(EXCUTED_CMD)cmd Succ:(BOOL)isSucc Reason:(CMD_EXECTE_ERROR_REASON)reason { - DebugNSLog(@"Command 0x%.2X - %@ ,Exec result %@ , Fail reason:%lu -- %@", + NSLog(@"Command 0x%.2X - %@ ,Exec result %@ , Fail reason:%lu -- %@", cmd, [self.sdk cmdErrorDesc:cmd] , isSucc ? @"succ" :@"fail", (unsigned long)reason, @@ -503,7 +505,7 @@ NSString * const CP_NAME = @"BlackShark"; - (void)srBleDeviceBatteryLevel:(NSUInteger)batteryLevel IsCharging:(BOOL)isCharging { _currentBatteryLevel = batteryLevel; _isCharging = isCharging; -// DebugNSLog(@"电量 %lu 充电: %d", (unsigned long)batteryLevel, isCharging); +// NSLog(@"电量 %lu 充电: %d", (unsigned long)batteryLevel, isCharging); if ([self.appDataDelegate respondsToSelector:@selector(srBleDeviceBatteryLevel:IsCharging:)]) { [self.appDataDelegate srBleDeviceBatteryLevel:batteryLevel IsCharging:isCharging]; } @@ -550,6 +552,8 @@ NSString * const CP_NAME = @"BlackShark"; -(void)srBleOEMAuthResult:(BOOL)authSucceddful { + NSLog(@"srBleOEMAuthResult"); + // 主动获取电池 [self.sdk functionGetDeviceBattery]; @@ -570,7 +574,7 @@ NSString * const CP_NAME = @"BlackShark"; -(void)srBleMeasureDuration:(NSInteger)seconds { self.currentDevice.hrMeasureDurations = seconds; - DebugNSLog(@"测量时长 %ld s", (long)seconds); + NSLog(@"测量时长 %ld s", (long)seconds); if ([self.appDataDelegate respondsToSelector:@selector(srBleMeasureDuration:)]){ [self.appDataDelegate srBleMeasureDuration:seconds]; } @@ -614,7 +618,7 @@ NSString * const CP_NAME = @"BlackShark"; { if (self.historySyncCbk) { self.historySyncCbk(isComplete, percent); -// DebugNSLog(@"lzp call historySyncCbk currentaccount: %ld complete:%d", (long)currentCount, isComplete); +// NSLog(@"lzp call historySyncCbk currentaccount: %ld complete:%d", (long)currentCount, isComplete); } @@ -684,7 +688,7 @@ NSString * const CP_NAME = @"BlackShark"; } - (void)srBleSN:(nonnull NSString *)sn { - DebugNSLog(@"sn:%@", sn); + NSLog(@"sn:%@", sn); if (self.bindDevice) { self.bindDevice.otherInfo.sn = sn; [self.bindDevice updateOtherInfo:^(BOOL succ) { @@ -721,7 +725,7 @@ NSString * const CP_NAME = @"BlackShark"; default: break; } - DebugNSLog(@"ble cmd sending error: %@ when calling %@", errorDesc,methodNm); + NSLog(@"ble cmd sending error: %@ when calling %@", errorDesc,methodNm); if ([self.appDataDelegate respondsToSelector:@selector(srBleFunctionErrorCallBack:MehthodName:)]) { [self.appDataDelegate srBleFunctionErrorCallBack:error MehthodName:methodNm]; @@ -812,7 +816,7 @@ NSString * const CP_NAME = @"BlackShark"; WEAK_SELF [[OTAHelper Instance] otaQueryUpgrade:OTA_HOST Cat:catDwn CBK:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error, NSDictionary * _Nullable resultDict) { STRONG_SELF - DebugNSLog(@"ota 请求 %@", resultDict); + NSLog(@"ota 请求 %@", resultDict); BOOL needUpdate = [[DeviceCenter instance] checkNeedUpdate:resultDict[@"ver"]]; if (needUpdate) { if (resultDict) { diff --git a/iosApp/iosApp/Libs/Modules/OusideBle/OusideBleDiscovery.m b/iosApp/iosApp/Libs/Modules/OusideBle/OusideBleDiscovery.m index a9e3456..12c6db0 100755 --- a/iosApp/iosApp/Libs/Modules/OusideBle/OusideBleDiscovery.m +++ b/iosApp/iosApp/Libs/Modules/OusideBle/OusideBleDiscovery.m @@ -12,102 +12,86 @@ @end -@implementation OusideBleDiscovery -{ +@implementation OusideBleDiscovery { CBUUID *_otaMainServiceUUID; // ota 主服务 } -+ (CBUUID*) sigUUIDToCBUUID:(uint16_t)UUID -{ - uint8_t b[2] = { (UUID >> 8) & 0xff, UUID & 0xff }; ++ (CBUUID *)sigUUIDToCBUUID:(uint16_t)UUID { + uint8_t b[2] = {(UUID >> 8) & 0xff, UUID & 0xff}; return [CBUUID UUIDWithData:[NSData dataWithBytes:b length:2]]; } -+ (uint16_t) sigUUIDFromCBUUID:(CBUUID*)UUID -{ - const uint8_t* b = UUID.data.bytes; ++ (uint16_t)sigUUIDFromCBUUID:(CBUUID *)UUID { + const uint8_t *b = UUID.data.bytes; return UUID.data.length == 2 ? (b[0] << 8) | b[1] : (uint16_t) 0; } - --(instancetype)init -{ +- (instancetype)init { self = [super init]; - if (self) - { + if (self) { _pendingInit = YES; self.centralManager = [[CBCentralManager alloc] initWithDelegate:self queue:dispatch_get_main_queue()]; _foundPeripherals = [[NSMutableArray alloc] init]; _otaMainServiceUUID = [[self class] sigUUIDToCBUUID:SR_SERVICE_UUID]; - - } + + } return self; } --(void)stconnectPeripheralTimeout:(id)sender { - +- (void)stconnectPeripheralTimeout:(id)sender { + } -- (void) dealloc -{ +- (void)dealloc { // We are a singleton and as such, dealloc shouldn't be called. // assert(NO); } - - -- (void) centralManager:(CBCentralManager *)central didRetrieveConnectedPeripherals:(NSArray *)peripherals -{ +- (void)centralManager:(CBCentralManager *)central didRetrieveConnectedPeripherals:(NSArray *)peripherals { } - -- (void) centralManager:(CBCentralManager *)central didRetrievePeripheral:(CBPeripheral *)peripheral -{ - [central connectPeripheral:peripheral options:nil]; +- (void)centralManager:(CBCentralManager *)central didRetrievePeripheral:(CBPeripheral *)peripheral { + [central connectPeripheral:peripheral options:nil]; // [_discoveryDelegate stdiscoveryDidRefresh]; } //delete the stored device uuid in list -- (void) centralManager:(CBCentralManager *)central didFailToRetrievePeripheralForUUID:(CFUUIDRef)UUID error:(NSError *)error -{ - /* Nuke from plist. */ +- (void)centralManager:(CBCentralManager *)central didFailToRetrievePeripheralForUUID:(CFUUIDRef)UUID error:(NSError *)error { + /* Nuke from plist. */ // [DeviceRecordManager removeSavedDevice:UUID]; - -} +} #pragma mark - #pragma mark Discovery /// 开始扫描 -- (void) startScanning -{ +- (void)startScanning { [self stopScanning]; [_foundPeripherals removeAllObjects]; - - NSArray *uuidArray = @[_otaMainServiceUUID]; - NSDictionary *options = @{ CBCentralManagerScanOptionAllowDuplicatesKey : @(NO) }; - - [_centralManager scanForPeripheralsWithServices:uuidArray options:options]; + + NSArray *uuidArray = @[_otaMainServiceUUID]; + NSDictionary *options = @{CBCentralManagerScanOptionAllowDuplicatesKey: @(NO)}; + NSLog(@"OusideBle start scan"); + [_centralManager scanForPeripheralsWithServices:uuidArray options:options]; } -- (void) stopScanning -{ +- (void)stopScanning { if (_centralManager != nil) { [_centralManager stopScan]; - + } - + } @@ -119,39 +103,37 @@ - (void)centralManager:(CBCentralManager *)central didDiscoverPeripheral:(CBPeripheral *)peripheral advertisementData:(NSDictionary *)advertisementData - RSSI:(NSNumber *)RSSI -{ + RSSI:(NSNumber *)RSSI { NSString *advertisementName = [advertisementData objectForKey:@"kCBAdvDataLocalName"]; NSArray *services = [advertisementData valueForKey:CBAdvertisementDataServiceUUIDsKey]; NSArray *servicesOvfl = [advertisementData valueForKey:CBAdvertisementDataOverflowServiceUUIDsKey]; - __block BOOL canAdd = [services containsObject:_otaMainServiceUUID] || [servicesOvfl containsObject:_otaMainServiceUUID]; - - if (canAdd) - { + __block BOOL canAdd = [services containsObject:_otaMainServiceUUID] || + [servicesOvfl containsObject:_otaMainServiceUUID]; + + if (canAdd) { NSString *macString = [self macAddressFromBleAvdData:advertisementData]; SRBLeService *service = [[SRBLeService alloc] initWithPeripheral:peripheral]; [service setAdvData:advertisementData]; service.rssi = RSSI; [self analysisAdvData:advertisementData]; - + if (service.macAddress != nil) { - - DebugNSLog(@"ouside scan add %@ %@", service.advDataLocalName, service.macAddress); + + NSLog(@"ouside scan add %@ %@", service.advDataLocalName, service.macAddress); [self addToFoundService:service AdvertisementData:advertisementData]; - + if ([_scanDelegate respondsToSelector:@selector(srScanDeviceDidRefresh:)]) { [_scanDelegate srScanDeviceDidRefresh:[NSArray arrayWithArray:_foundPeripherals]]; } } - + } } --(void)addToFoundService:(SRBLeService *)service AdvertisementData:(NSDictionary *)advertisementData -{ +- (void)addToFoundService:(SRBLeService *)service AdvertisementData:(NSDictionary *)advertisementData { BOOL canAdd = YES; // 防止重复加入 for (SRBLeService *s in _foundPeripherals) { @@ -159,37 +141,34 @@ canAdd = NO; break; } - + } - + if (canAdd) { [_foundPeripherals addObject:service]; } - + } --(void)sendData:(NSData *)data type:(CBCharacteristicWriteType)type -{ +- (void)sendData:(NSData *)data type:(CBCharacteristicWriteType)type { + - } #pragma mark - #pragma mark retrievePeripheral -- (void) retrievePeripheral:(NSString *)uuid -{ +- (void)retrievePeripheral:(NSString *)uuid { if (uuid != nil) { - CFUUIDRef uuidRef = CFUUIDCreateFromString(NULL, (__bridge CFStringRef)uuid); - NSArray *uuids = [NSArray arrayWithObject:(__bridge id)uuidRef]; + CFUUIDRef uuidRef = CFUUIDCreateFromString(NULL, (__bridge CFStringRef) uuid); + NSArray *uuids = [NSArray arrayWithObject:(__bridge id) uuidRef]; [_centralManager retrievePeripheralsWithIdentifiers:uuids]; } - + } -- (void)centralManager:(CBCentralManager *)central didRetrievePeripherals:(NSArray *)peripherals -{ +- (void)centralManager:(CBCentralManager *)central didRetrievePeripherals:(NSArray *)peripherals { // [self.scanDelegate stdidRetrievePeripherals:peripherals]; } @@ -198,13 +177,12 @@ /****************************************************************************/ /* Connection/Disconnection */ /****************************************************************************/ -- (void) connectPeripheral:(SRBLeService*)keyService -{ +- (void)connectPeripheral:(SRBLeService *)keyService { //停止扫描 [_centralManager stopScan]; - + if (keyService == _currentService) { - if ( _currentService.peripheral.state == CBPeripheralStateConnected) { + if (_currentService.peripheral.state == CBPeripheralStateConnected) { // 已连接 if ([self.scanDelegate respondsToSelector:@selector(srBleDidConnectPeripheral:)]) { [self.scanDelegate srBleDidConnectPeripheral:_currentService]; @@ -214,63 +192,59 @@ [_centralManager connectPeripheral:_currentService.peripheral options:nil]; return; } - + } else { - + if (_currentService.peripheral.state == CBPeripheralStateConnected) { [_centralManager cancelPeripheralConnection:_currentService.peripheral]; - DebugNSLog(@"主动断开 %s %d", __func__, __LINE__); - + NSLog(@"/*主*/动断开 %s %d", __func__, __LINE__); + } _currentService = keyService; - + if (keyService.peripheral.state != CBPeripheralStateConnected) { [_centralManager connectPeripheral:keyService.peripheral options:nil]; [self performSelector:@selector(stconnectPeripheralTimeout:) withObject:keyService afterDelay:900.0f]; } } - - + + } //-(void) -- (void)connectPeripheralTimeout:(id)obj -{ +- (void)connectPeripheralTimeout:(id)obj { SRBLeService *p = obj; if (!(p.peripheral.state == CBPeripheralStateConnected)) { // [_scanDelegate stconnectPeripheralTimeout:p]; } } -- (void) disconnectPeripheral:(SRBLeService*)keyService -{ +- (void)disconnectPeripheral:(SRBLeService *)keyService { if (_currentService == keyService) { } - - if (![_foundPeripherals containsObject:_currentService] && _currentService != nil) - { + + if (![_foundPeripherals containsObject:_currentService] && _currentService != nil) { [_foundPeripherals addObject:_currentService]; } if (keyService.peripheral) { [_centralManager cancelPeripheralConnection:keyService.peripheral]; - DebugNSLog(@"主动断开 %s %d", __func__, __LINE__); + NSLog(@"主动断开 %s %d", __func__, __LINE__); } _currentService = nil; } -- (void)cancelAllReconnect -{ +- (void)cancelAllReconnect { // for (LeKeyFobService *s in self.connectedServices) { - if (!(_currentService.peripheral.state == CBPeripheralStateConnected)) { - [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(stconnectPeripheralTimeout:) object:_currentService.peripheral]; - } + if (!(_currentService.peripheral.state == CBPeripheralStateConnected)) { + [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(stconnectPeripheralTimeout:) object:_currentService.peripheral]; + } // } for (CBPeripheral *p in self.foundPeripherals) { - if (!(p.state ==CBPeripheralStateConnected)) { + if (!(p.state == CBPeripheralStateConnected)) { [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(stconnectPeripheralTimeout:) object:p]; } } @@ -278,23 +252,22 @@ } -- (void) centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral -{ +- (void)centralManager:(CBCentralManager *)central didConnectPeripheral:(CBPeripheral *)peripheral { // LeKeyFobService *service = nil; // BOOL isExist = NO; - + [NSObject cancelPreviousPerformRequestsWithTarget:self selector:@selector(stconnectPeripheralTimeout:) object:peripheral]; - - + + if ([_currentService.peripheral.identifier.UUIDString isEqualToString:peripheral.identifier.UUIDString]) { - - if (_currentService.peripheral.state == CBPeripheralStateConnected){ - + + if (_currentService.peripheral.state == CBPeripheralStateConnected) { + if ([self.scanDelegate respondsToSelector:@selector(srBleDidConnectPeripheral:)]) { [self.scanDelegate srBleDidConnectPeripheral:_currentService]; return; } - + // [_currentService setPerpheralDelegate:_peripheralDelegate]; // [_currentService start]; // 开始业务 // @@ -302,59 +275,53 @@ // [_peripheralDelegate stkeyFobServiceDidChangeStatus:_currentService]; // [_peripheralDelegate stkeyFobServiceDidConnectPeripheral:_currentService]; // [_discoveryDelegate stdiscoveryDidRefresh]; - + } } } - -- (void) centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error -{ - DebugNSLog(@"Attempted connection to peripheral %@ failed: %@", [peripheral name], [error localizedDescription]); +- (void)centralManager:(CBCentralManager *)central didFailToConnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error { + NSLog(@"Attempted connection to peripheral %@ failed: %@", [peripheral name], + [error localizedDescription]); } - -- (void) centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error -{ +- (void)centralManager:(CBCentralManager *)central didDisconnectPeripheral:(CBPeripheral *)peripheral error:(NSError *)error { if ([_currentService peripheral] != peripheral) { return; } - DebugNSLog(@"异常断开 error:%@", error); + NSLog(@"异常断开 error:%@", error); //异常断开的动画 //3.通知代理 if ([self.scanDelegate respondsToSelector:@selector(srBleDidDisconnectPeripheral:)]) { [self.scanDelegate srBleDidDisconnectPeripheral:_currentService]; } - + } /// 清空所有发现和连接过的设备 -- (void) clearDevices -{ +- (void)clearDevices { [_foundPeripherals removeAllObjects]; - + if (_currentService.peripheral.state == CBPeripheralStateConnected) { [_centralManager cancelPeripheralConnection:_currentService.peripheral]; - DebugNSLog(@"主动断开 %s %d", __func__, __LINE__); + NSLog(@"主动断开 %s %d", __func__, __LINE__); _currentService = nil; } - + } -- (CBManagerState)deviceBleCenterState -{ - return [_centralManager state]; +- (CBManagerState)deviceBleCenterState { + return [_centralManager state]; } -- (void)centralManagerDidUpdateState:(CBCentralManager *)central -{ +- (void)centralManagerDidUpdateState:(CBCentralManager *)central { static CBManagerState previousState = -1; /* ios 10.0 CBManagerStateUnknown = 0, @@ -364,59 +331,52 @@ CBManagerStatePoweredOff, CBManagerStatePoweredOn, */ -// DebugNSLog(@"手机蓝牙状态: %ld", (long)[_centralManager state]); - switch ([_centralManager state]) { - case CBManagerStateUnsupported: - { + NSLog(@"手机蓝牙状态: %ld", (long) [_centralManager state]); + switch ([_centralManager state]) { + case CBManagerStateUnsupported: { //不支持 break; } - case CBManagerStatePoweredOff: - { + case CBManagerStatePoweredOff: { + [self clearDevices]; + + break; + } + + case CBManagerStateUnauthorized: { + /* Tell user the app is not allowed. */ + break; + } + + case CBManagerStateUnknown: { + /* Bad news, let's wait for another event. */ + break; + } + + case CBManagerStatePoweredOn: { + _pendingInit = NO; + + break; + } + + case CBManagerStateResetting: { [self clearDevices]; - break; - } - - case CBManagerStateUnauthorized: - { - /* Tell user the app is not allowed. */ - break; - } - - case CBManagerStateUnknown: - { - /* Bad news, let's wait for another event. */ - break; - } - - case CBManagerStatePoweredOn: - { - _pendingInit = NO; - - break; - } - - case CBManagerStateResetting: - { - [self clearDevices]; - - _pendingInit = YES; - break; - } - } - + _pendingInit = YES; + break; + } + } + previousState = [_centralManager state]; if ([_scanDelegate respondsToSelector:@selector(srBlePowerStateChange:)]) { [_scanDelegate srBlePowerStateChange:previousState]; } } --(void)analysisAdvData:(NSDictionary *)advertisementData -{ - NSData * manufacturerData = advertisementData[@"kCBAdvDataManufacturerData"]; - NSString * advDataLocalName = [advertisementData objectForKey:@"kCBAdvDataLocalName"]; - Byte * manufacturerBybtes = (Byte *)(manufacturerData.bytes); +- (void)analysisAdvData:(NSDictionary *)advertisementData { + NSData *manufacturerData = advertisementData[@"kCBAdvDataManufacturerData"]; + NSString *advDataLocalName = [advertisementData objectForKey:@"kCBAdvDataLocalName"]; + Byte *manufacturerBybtes = (Byte * )(manufacturerData.bytes); if (manufacturerBybtes[0] == 0XA5 && manufacturerBybtes[1] == 0X0D) { // not suitable @@ -426,15 +386,16 @@ uint8_t deviceSize = 0; if (manufacturerData.length >= 6) { // color and size - Byte *byte = (Byte *)[manufacturerData bytes]; - deviceColor = (NSUInteger)(byte[4]); // color: 0-black,1-silver, 2-gold, 3-rose gold + Byte *byte = (Byte * ) + [manufacturerData bytes]; + deviceColor = (NSUInteger) (byte[4]); // color: 0-black,1-silver, 2-gold, 3-rose gold deviceSize = byte[5] & 0xFF; // ring's size } BOOL isCharging = NO; uint8_t batteryLevel = 0; - if (manufacturerData.length >= 8) - { - Byte *byte = (Byte *)[manufacturerData bytes]; + if (manufacturerData.length >= 8) { + Byte *byte = (Byte * ) + [manufacturerData bytes]; uint8_t b6 = byte[6]; isCharging = (b6 >> 7) & 0X01; // Is device charging. YES = charging batteryLevel = b6 & 0X7F; // battery level,range in 0-100 @@ -442,31 +403,32 @@ NSInteger chipType = (byte[7] >> 4) & 0XFF; // main chip NSUInteger deviceGeneration = (byte[7]) & 0X0F; // generation } - + if (manufacturerData.length >= 8) { - DebugNSLog(@"advName:%@ color:%d,size:%d,isCharging:%d, batteryLevel:%d%% ", advDataLocalName, deviceColor, deviceSize, isCharging,batteryLevel); + NSLog(@"advName:%@ color:%d,size:%d,isCharging:%d, batteryLevel:%d%% ", advDataLocalName, deviceColor, deviceSize, isCharging,batteryLevel); return; } if (manufacturerData.length >= 6) { - DebugNSLog(@"advName:%@ color:%d,size:%d", advDataLocalName, deviceColor, deviceSize); + NSLog(@"advName:%@ color:%d,size:%d", advDataLocalName, deviceColor, deviceSize); } } --(NSString *)macAddressFromBleAvdData:(NSDictionary *)advertisementData -{ +- (NSString *)macAddressFromBleAvdData:(NSDictionary *)advertisementData { // mac 地址拼接 NSString *macString = nil; - NSData * manufacturerData = advertisementData[@"kCBAdvDataManufacturerData"]; + NSData *manufacturerData = advertisementData[@"kCBAdvDataManufacturerData"]; NSString *advertisementName = [advertisementData objectForKey:@"kCBAdvDataLocalName"]; if (manufacturerData.length >= 4) { NSData *macDataLowBit = [manufacturerData subdataWithRange:NSMakeRange(0, 4)]; - - Byte *byte = (Byte *)[macDataLowBit bytes]; - macString = [NSString stringWithFormat:@"%.2X:%.2X:%.2X:%.2X", byte[0], byte[1], byte[2], byte[3]]; - if (advertisementName.length>=4) { - NSString *macString_high = [advertisementName substringWithRange:NSMakeRange(advertisementName.length - 4, 4)]; + + Byte *byte = (Byte * ) + [macDataLowBit bytes]; + macString = [NSString stringWithFormat:@"%.2X:%.2X:%.2X:%.2X", byte[0], byte[1], byte[2], byte[3]]; + if (advertisementName.length >= 4) { + NSString *macString_high = [advertisementName substringWithRange:NSMakeRange( + advertisementName.length - 4, 4)]; NSMutableString *mutStr = [NSMutableString stringWithString:macString_high]; [mutStr insertString:@":" atIndex:2]; macString = [NSString stringWithFormat:@"%@:%@", macString, mutStr]; diff --git a/iosApp/iosApp/Libs/NAVTemplateViewController.m b/iosApp/iosApp/Libs/NAVTemplateViewController.m new file mode 100644 index 0000000..bbaeec1 --- /dev/null +++ b/iosApp/iosApp/Libs/NAVTemplateViewController.m @@ -0,0 +1,162 @@ +// +// NAVTemplateViewController.m +// +// +// Created by Linktop on 2021/4/15. +// Copyright © 2021 linktop. All rights reserved. +// + +#import "NAVTemplateViewController.h" +#import "ConfigModel.h" +#import "UILabel+LNCTitleStyle.h" +#import + +@interface NAVTemplateViewController () + +@property(copy, nonatomic)void(^backBlk)(void); + +@end + +@implementation NAVTemplateViewController + +- (void)viewDidLoad { + [super viewDidLoad]; + self.view.backgroundColor = [UIColor blackColor]; + // Do any additional setup after loading the view. + self.navigationController.interactivePopGestureRecognizer.delegate = self; + + + +} + +#pragma mark -- navbar style + +-(void)arrowback:(void(^ __nullable)(void))backBlk +{ + + UIImage *image = [UIImage imageNamed:@"back_white"]; + QMUIButton *backBtn = [[QMUIButton alloc]init]; + [backBtn setImage:image forState:UIControlStateNormal]; + backBtn.frame = CGRectMake(0, 0, 50, 44); + [backBtn setTitle:@" " forState:UIControlStateNormal]; + backBtn.imagePosition = QMUIButtonImagePositionLeft; + [backBtn addTarget:self action:@selector(backBtn:) forControlEvents:UIControlEventTouchUpInside]; + + self.backBlk = backBlk; + UIBarButtonItem *leftItem = [[UIBarButtonItem alloc]initWithCustomView:backBtn];; + + self.navigationItem.leftBarButtonItem = leftItem; +} + +-(void)cleanLeftBarButon +{ + + UIBarButtonItem *leftItem = [[UIBarButtonItem alloc]initWithCustomView:[[UIView alloc] initWithFrame:CGRectMake(0, 0, 40, 40)] ];; + + self.navigationItem.leftBarButtonItem = leftItem; +} + +-(void)customNavStyleNormal:(NSString *)centerTitle BackBlk:( void(^ __nullable)(void))backBlk +{ + //navbar 背景 + + UINavigationBar *navBar = self.navigationController.navigationBar; + if (!navBar) { + return; + } + navBar.tintColor = [UIColor whiteColor]; +// self.title = centerTitle; +// [self gradientBackGround]; //渐变色背景 + //title + UILabel *titleLabel = [[UILabel alloc] initWithFrame:CGRectMake(0, 0, 250, 44)]; + titleLabel.font = [UIFont fontWithName:@"ArialMT" size:19.0f]; + [titleLabel setTextColor:[UIColor whiteColor]]; + titleLabel.text = centerTitle; + titleLabel.textAlignment = NSTextAlignmentCenter; + + + self.navigationItem.titleView = titleLabel; + + //左按钮 + CGFloat bt_w = 50.0f; + UIButton * leftCustomBtn = [[UIButton alloc] initWithFrame:CGRectMake(0, 60, bt_w, 44)]; + [leftCustomBtn addTarget:self action:@selector(backBtn:) forControlEvents:UIControlEventTouchUpInside]; + UIBarButtonItem *leftItem = [[UIBarButtonItem alloc] initWithCustomView:leftCustomBtn]; + if (backBlk != nil) { + UIImage *btnArrowImg = [UIImage imageNamed:@"back_white"]; + [leftCustomBtn setImage:btnArrowImg forState:UIControlStateNormal]; + CGFloat imageV_w = btnArrowImg.size.width; + leftCustomBtn.imageEdgeInsets = UIEdgeInsetsMake(0, imageV_w - bt_w, 0, 0); + } + + self.navigationItem.leftBarButtonItem = leftItem; + + self.backBlk = backBlk; + +// self.navigationController.interactivePopGestureRecognizer.enabled = YES; +// self.navigationController.interactivePopGestureRecognizer.delegate = (id)self; +} + +- (void)customNavBarView:(NSString *)leftBtnTitle +{ + + //navbar 背景 + UINavigationBar *navBar = self.navigationController.navigationBar; + if (!navBar) { + return; + } +// navBar.tintColor = [UIColor whiteColor]; + +// [self gradientBackGround]; //渐变色背景 + + //左按钮 + /* + UIButton * leftCustomBtn = [[UIButton alloc] initWithFrame:CGRectMake(0, 60, 100, 44)]; + [leftCustomBtn useNavTitleFont]; + [leftCustomBtn setTitle:leftBtnTitle forState:UIControlStateNormal]; + leftCustomBtn.enabled = NO; + UIBarButtonItem *leftItem = [[UIBarButtonItem alloc] initWithCustomView:leftCustomBtn]; + + self.navigationItem.leftBarButtonItem = leftItem; + */ + + UILabel * leftCustomLbl = [[UILabel alloc] initWithFrame:CGRectMake(0, 60, 100, 44)]; + leftCustomLbl.text = leftBtnTitle; + leftCustomLbl.textAlignment = NSTextAlignmentLeft; + [leftCustomLbl userNavTitleFont:nil]; + UIBarButtonItem *leftItem = [[UIBarButtonItem alloc] initWithCustomView:leftCustomLbl]; + self.navigationItem.leftBarButtonItem = leftItem; + + + +} + +#pragma mark --navbar按键响应 +- (void)backBtn:(id)sender { + if (self.backBlk) { + self.backBlk(); + } else { + [self.navigationController popViewControllerAnimated:YES]; + } + +} + + +//- (void)gradientBackGround { +// self.navigationController.navigationBar.translucent = NO; +// self.navigationController.navigationBar.barTintColor = MAIN_BG_COLOR; +// +// if (@available(iOS 15.0, *)) { +// UINavigationBarAppearance *appearance = [UINavigationBarAppearance new]; +// [appearance configureWithOpaqueBackground]; +// appearance.backgroundColor = MAIN_BG_COLOR; +// self.navigationController.navigationBar.standardAppearance = appearance; +// self.navigationController.navigationBar.scrollEdgeAppearance = self.navigationController.navigationBar.standardAppearance; +// } +// +// +//// self.navigationController.navigationBar.backgroundColor = [UIColor blackColor]; +//} + + +@end diff --git a/iosApp/iosApp/Libs/NSString+Check.h b/iosApp/iosApp/Libs/NSString+Check.h new file mode 100644 index 0000000..907092d --- /dev/null +++ b/iosApp/iosApp/Libs/NSString+Check.h @@ -0,0 +1,23 @@ +// +// NSString+Check.h +// CareRingApp +// +// Created by Linktop on 2022/8/23. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface NSString (Check) + +/// 检查是否包含非法字符 YES = 包含 +-(BOOL)isContainSpecialCharacters; +// yes = 复合规则 +-(BOOL)isValiadEmail; + +//-(int)transVersionToInt; +-(BOOL)versionIsLowThan:(NSString *)remote; +@end + +NS_ASSUME_NONNULL_END diff --git a/iosApp/iosApp/Libs/NSString+Check.m b/iosApp/iosApp/Libs/NSString+Check.m new file mode 100644 index 0000000..2786065 --- /dev/null +++ b/iosApp/iosApp/Libs/NSString+Check.m @@ -0,0 +1,67 @@ +// +// NSString+Check.m +// CareRingApp +// +// Created by Linktop on 2022/8/23. +// + +#import "NSString+Check.h" + +@implementation NSString (Check) +//非法字符 +-(BOOL)isContainSpecialCharacters { + + NSCharacterSet *nameCharacters = [[NSCharacterSet characterSetWithCharactersInString:@"_abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"] invertedSet]; + NSRange userNameRange = [self rangeOfCharacterFromSet:nameCharacters]; + if (userNameRange.location != NSNotFound) { + NSLog(@"包含特殊字符"); + return YES; + } + return NO; +} + +-(BOOL)isValiadEmail +{ + + // ^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$ + NSString* emailRegu = @"^\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*$"; + NSPredicate *numberPre = [NSPredicate predicateWithFormat:@"SELF MATCHES %@",emailRegu]; + return [numberPre evaluateWithObject:self]; + +} + +-(int)transVersionToInt +{ + + NSArray *arr = [self componentsSeparatedByString:@"."]; + int res = 0; + NSMutableString *tempString = [NSMutableString new]; + for (int i = 0; i < arr.count; i++) { + [tempString appendFormat:@"%d", arr[i].intValue]; + + } + res = [tempString intValue]; + return res; + +} + +-(BOOL)versionIsLowThan:(NSString *)remote { + + NSArray *arrLocal = [self componentsSeparatedByString:@"."]; + NSArray *arrRemote = [remote componentsSeparatedByString:@"."]; + if (arrLocal.count != arrRemote.count) { + return NO; //格式不匹配 + } + BOOL isLow = NO; + for (int i = 0; i < arrLocal.count; i++) { + if ([arrLocal[i] intValue] < [arrRemote[i] intValue]) { + isLow = YES; + break; + } + + } + + return isLow; +} + +@end diff --git a/iosApp/iosApp/Libs/PrefixHeader.pch b/iosApp/iosApp/Libs/PrefixHeader.pch new file mode 100644 index 0000000..4454631 --- /dev/null +++ b/iosApp/iosApp/Libs/PrefixHeader.pch @@ -0,0 +1,23 @@ +// +// PrefixHeader.pch +// sr01sdkProject +// +// Created by Linktop on 2022/5/30. +// + +#ifndef PrefixHeader_pch +#define PrefixHeader_pch + +//#import "TestUtils.h" + +// Include any system framework and library headers here that should be included in all compilation units. +// You will also need to set the Prefix Header build setting of one or more of your targets to reference this file. + +#ifdef DEBUG +#define DebugNSLog(...) NSLog(__VA_ARGS__) +#else +#define DebugNSLog(...) + +#endif + +#endif /* PrefixHeader_pch */ diff --git a/iosApp/iosApp/PrefixHeader.pch b/iosApp/iosApp/PrefixHeader.pch new file mode 100644 index 0000000..4454631 --- /dev/null +++ b/iosApp/iosApp/PrefixHeader.pch @@ -0,0 +1,23 @@ +// +// PrefixHeader.pch +// sr01sdkProject +// +// Created by Linktop on 2022/5/30. +// + +#ifndef PrefixHeader_pch +#define PrefixHeader_pch + +//#import "TestUtils.h" + +// Include any system framework and library headers here that should be included in all compilation units. +// You will also need to set the Prefix Header build setting of one or more of your targets to reference this file. + +#ifdef DEBUG +#define DebugNSLog(...) NSLog(__VA_ARGS__) +#else +#define DebugNSLog(...) + +#endif + +#endif /* PrefixHeader_pch */ diff --git a/iosApp/iosApp/iosApp-Bridging-Header.h b/iosApp/iosApp/iosApp-Bridging-Header.h new file mode 100644 index 0000000..1b2cb5d --- /dev/null +++ b/iosApp/iosApp/iosApp-Bridging-Header.h @@ -0,0 +1,4 @@ +// +// Use this file to import your target's public headers that you would like to expose to Swift. +// + diff --git a/iosApp/iosApp/temp.m b/iosApp/iosApp/temp.m new file mode 100644 index 0000000..352b9e4 --- /dev/null +++ b/iosApp/iosApp/temp.m @@ -0,0 +1,9 @@ +// +// temp.m +// iosApp +// +// Created by 安然雨声 on 2025/6/4. +// Copyright © 2025 orgName. All rights reserved. +// + +#import diff --git a/shared/Ring.def b/shared/Ring.def index 28e9cf5..386c100 100644 --- a/shared/Ring.def +++ b/shared/Ring.def @@ -1 +1,3 @@ language = Objective-C +package = com.whitefish.ring.objc +linkerOpts = -L../iosApp/iosApp/Libs -lRingSDK_2.0.2 diff --git a/shared/build.gradle.kts b/shared/build.gradle.kts index f283d44..12440f9 100644 --- a/shared/build.gradle.kts +++ b/shared/build.gradle.kts @@ -6,6 +6,7 @@ plugins { alias(libs.plugins.androidLibrary) alias(libs.plugins.compose.compiler) alias(libs.plugins.composeMultiplatform) + id("com.google.devtools.ksp") } kotlin { @@ -22,12 +23,15 @@ kotlin { compilations.getByName("main"){ val cin by cinterops.creating{ definitionFile.set(project.file("Ring.def")) - packageName("com.whitefish.app.objc") + packageName("com.whitefish.ring.objc") val files = project.fileTree("../iosApp/iosApp/Libs").files.filter { it.extension == "h" } headers(files) val links = files.map { "-I${it.parent}" }.distinct() compilerOpts(links) compilerOpts.add("-I../iosApp/Pods/YYKit/YYKit/Base/Foundation") + compilerOpts.add("-I../iosApp/Pods/Masonry/Masonry") + compilerOpts.add("-I../iosApp/Pods/QMUIKit/QMUIKit") + println("cinterops build complete") } } } @@ -40,6 +44,10 @@ kotlin { framework { baseName = "shared" isStatic = true + freeCompilerArgs += listOf( + "-linker-option", "-L${project.projectDir.parent}/iosApp/iosApp/Libs", + "-linker-option", "-lRingSDK_2.0.2" + ) } } @@ -70,8 +78,21 @@ android { defaultConfig { minSdk = 29 } + lint{ + disable.add("NullSafeMutableLiveData") + } compileOptions { sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8 } -} \ No newline at end of file + dependencies { + implementation(libs.androidx.room.runtime) + ksp(libs.androidx.room.compiler) + implementation(libs.android.database.sqlcipher) + implementation(fileTree("libs")) + implementation ("com.google.accompanist:accompanist-permissions:0.37.3") + } +} +dependencies { + implementation(libs.androidx.activity.ktx) +} diff --git a/shared/libs/NexRingSDK_v1.4.0_release.aar b/shared/libs/NexRingSDK_v1.4.0_release.aar new file mode 100644 index 0000000..4582983 Binary files /dev/null and b/shared/libs/NexRingSDK_v1.4.0_release.aar differ diff --git a/shared/libs/OemAuth_v2.0.0_release.aar b/shared/libs/OemAuth_v2.0.0_release.aar new file mode 100644 index 0000000..c0a0b24 Binary files /dev/null and b/shared/libs/OemAuth_v2.0.0_release.aar differ diff --git a/shared/libs/SleepStagingNativeLib_v5_ring_release_v2.5.6.1.aar b/shared/libs/SleepStagingNativeLib_v5_ring_release_v2.5.6.1.aar new file mode 100644 index 0000000..5f1bc4d Binary files /dev/null and b/shared/libs/SleepStagingNativeLib_v5_ring_release_v2.5.6.1.aar differ diff --git a/shared/src/androidMain/kotlin/com/whitefish/ring/ActivityLifecycleCb.kt b/shared/src/androidMain/kotlin/com/whitefish/ring/ActivityLifecycleCb.kt new file mode 100644 index 0000000..91a573f --- /dev/null +++ b/shared/src/androidMain/kotlin/com/whitefish/ring/ActivityLifecycleCb.kt @@ -0,0 +1,58 @@ +package com.whitefish.ring + +import android.app.Activity +import android.app.Application +import android.os.Bundle +import android.util.Log + +class ActivityLifecycleCb : Application.ActivityLifecycleCallbacks { + + /** + * 如果是从后台打开APP的,此标志意味着可以从设备拉数据 + * + * + var readDataFromDevice: Boolean = false + */ + + private var flag = 0 + val activities = ArrayList() + + /** + * 判断APP是否在前台运行 + * */ + val isAppForeground: Boolean get() = flag > 0 + var backgroundFlag = true + + val currAct: Activity? + get() = if (activities.isNotEmpty()) activities[activities.size - 1] else null + + override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) { + activities.add(activity) + } + + override fun onActivityStarted(activity: Activity) { + flag++ + Log.i("ActivityLifecycleCb", "onActivityStarted - flag:$flag") + } + + override fun onActivityResumed(activity: Activity) { + } + + override fun onActivityPaused(activity: Activity) { + } + + override fun onActivityStopped(activity: Activity) { + flag-- + if (!isAppForeground) { + backgroundFlag = true + } + Log.i("ActivityLifecycleCb", "onActivityStopped - flag:$flag") + } + + override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) { + } + + override fun onActivityDestroyed(activity: Activity) { + activities.remove(activity) + } +} \ No newline at end of file diff --git a/shared/src/androidMain/kotlin/com/whitefish/ring/Application.kt b/shared/src/androidMain/kotlin/com/whitefish/ring/Application.kt new file mode 100644 index 0000000..15da0fe --- /dev/null +++ b/shared/src/androidMain/kotlin/com/whitefish/ring/Application.kt @@ -0,0 +1,31 @@ +package com.whitefish.ring + +import android.annotation.SuppressLint +import android.app.Application +import com.whitefish.app.bt.BleManager +import io.github.aakira.napier.DebugAntilog +import io.github.aakira.napier.Napier +import lib.linktop.nexring.api.NexRingManager + +class Application: Application() { + val bleManager by lazy { + NexRingManager.init(this) + BleManager(this) + } + + val mActivityLifecycleCb = ActivityLifecycleCb() + + companion object { + @SuppressLint("StaticFieldLeak") + var INSTANTS: com.whitefish.ring.Application? = null + private set + + } + + override fun onCreate() { + super.onCreate() + INSTANTS = this + Napier.base(DebugAntilog()) + registerActivityLifecycleCallbacks(mActivityLifecycleCb) + } +} \ No newline at end of file diff --git a/shared/src/androidMain/kotlin/com/whitefish/ring/DeviceManager.kt b/shared/src/androidMain/kotlin/com/whitefish/ring/DeviceManager.kt new file mode 100644 index 0000000..15d2bc4 --- /dev/null +++ b/shared/src/androidMain/kotlin/com/whitefish/ring/DeviceManager.kt @@ -0,0 +1,235 @@ +package com.whitefish.ring + +import android.annotation.SuppressLint +import android.app.AlertDialog +import android.bluetooth.BluetoothManager +import android.bluetooth.BluetoothProfile +import android.content.Context +import androidx.lifecycle.MutableLiveData +import com.whitefish.app.bt.BleDevice +import com.whitefish.ring.bt.OnBleConnectionListener +import com.whitefish.ring.bt.OnBleScanCallback +import com.whitefish.ring.bean.ui.Device +import com.whitefish.ring.device.IDeviceManager +import io.github.aakira.napier.Napier +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import lib.linktop.nexring.api.BATTERY_STATE_CHARGING +import lib.linktop.nexring.api.LOAD_DATA_EMPTY +import lib.linktop.nexring.api.LOAD_DATA_STATE_COMPLETED +import lib.linktop.nexring.api.LOAD_DATA_STATE_PROCESSING +import lib.linktop.nexring.api.LOAD_DATA_STATE_START +import lib.linktop.nexring.api.NexRingManager +import lib.linktop.nexring.api.OnSleepDataLoadListener +import lib.linktop.nexring.api.SleepData + + +class DeviceManager() : IDeviceManager(), OnBleConnectionListener, OnSleepDataLoadListener { + companion object{ + const val STATE_DEVICE_CHARGING = 1 + const val STATE_DEVICE_DISCHARGING = 0 + const val STATE_DEVICE_DISCONNECTED = -3 + const val STATE_DEVICE_CONNECTING = -2 + const val STATE_DEVICE_CONNECTED = -1 + } + private val context = Application.INSTANTS!! + private var isRegisterBattery = false + val batteryLevel = MutableLiveData(STATE_DEVICE_DISCONNECTED to 0) + private val sycProgress = MutableLiveData(0) + var isSyncingData: Boolean = false +// var homeViewModel: demo.linktop.nexring.ui.HomeViewModel? = null +// var workoutDetailViewModel: demo.linktop.nexring.ui.workout.WorkoutDetailViewModel? = null + + + init { + registerCb() + } + override fun onBleState(state: Int) { + bleStateListeners().forEach { + it.invoke(state) + } + when (state) { + BluetoothProfile.STATE_DISCONNECTED -> { + isRegisterBattery = false + batteryLevel.postValue(STATE_DEVICE_DISCONNECTED to 0) + } + + BluetoothProfile.STATE_CONNECTED -> { + batteryLevel.postValue(STATE_DEVICE_CONNECTED to 0) + } + } + } + + override fun onBleReady() { + bleReadyStateFlow.value = true + postDelay { + NexRingManager.get() + .deviceApi() + .getBatteryInfo { + if (it.state == BATTERY_STATE_CHARGING) { + batteryLevel.postValue(STATE_DEVICE_CHARGING to 0) + } else { + batteryLevel.postValue(STATE_DEVICE_DISCHARGING to it.level) + } + if (!isRegisterBattery) { + isRegisterBattery = true + postDelay { + NexRingManager.get() + .sleepApi() + .syncDataFromDev() + } + } + } + } + } + + override fun onSyncDataFromDevice(state: Int, progress: Int) { + Napier.i( + "onSyncDataFromDevice state: $state, progress: $progress" + ) + when (state) { + LOAD_DATA_EMPTY -> { + Napier.e("Empty data") + //TODO Callback when no data is received from the device. + } + + LOAD_DATA_STATE_START -> { + isSyncingData = true + sycProgress.postValue(progress) + } + + LOAD_DATA_STATE_PROCESSING -> sycProgress.postValue(progress) + LOAD_DATA_STATE_COMPLETED -> { + sycProgress.postValue(progress) + isSyncingData = false + //todo sync data complete + } + } + } + + override fun onSyncDataError(errorCode: Int) { + context.cmdErrorTip(errorCode) + } + + override fun onOutputNewSleepData(sleepData: ArrayList?) { + sleepData.also { + if (it.isNullOrEmpty()) { + Napier.i( + "onOutputNewSleepData NULL" + ) + } else { + Napier.i( + "onOutputNewSleepData size ${it.size}" + ) + it.forEachIndexed { index, data -> + Napier.i( + "onOutputNewSleepData $index sleep from ${data.startTs} to ${data.endTs}" + ) + } + } + } + } + + fun registerCb() { + context.bleManager.addOnBleConnectionListener(this) + NexRingManager.get().sleepApi().setOnSleepDataLoadListener(this) + } + + fun unregisterCb() { + NexRingManager.get().sleepApi().setOnSleepDataLoadListener(null) + context.bleManager.removeOnBleConnectionListener(this) + } + + + override fun connect(address: String) { + with(context.bleManager) { + when (bleState.value) { + BluetoothProfile.STATE_DISCONNECTED -> { + batteryLevel.postValue(STATE_DEVICE_CONNECTING to 0) + if (!connect(address)) { + startScan( + 20 * 1000L, + object : OnBleScanCallback { + override fun onScanning(result: BleDevice) { + if (result.device.address == address) { + connect(result.device) + } + } + + override fun onScanFinished() { + + } + }) + } + } + + BluetoothProfile.STATE_CONNECTED -> { + onBleState(bleState.value) + onBleReady() + } + } + } + } + + override fun bind() { + NexRingManager.get() + .deviceApi() + .getBindState { + if (it) { + //todo bind dialog +// AlertDialog.Builder(this@DeviceActivity) +// .setCancelable(false) +// .setTitle(R.string.dialog_title_restricted_mode) +// .setMessage(R.string.dialog_msg_restricted_mode) +// .setNegativeButton(android.R.string.cancel) { _, _ -> +// +// }.setPositiveButton(android.R.string.ok) { _, _ -> +// Logger.i("reset device") +// deviceAdapter.clear() +// NexRingManager.get() +// .deviceApi() +// .factoryReset() +// switchUI(true) +// postDelay { +// Thread.sleep(200) +// DeviceManager.INSTANCE.scan(this@DeviceActivity) +// } +// isConnecting = false +// }.create().show() + } else { + NexRingManager.get() + .deviceApi() + .bind { + //todo bind result + } + } + } + } + + override fun startScan() { + val bluetoothAdapter = + (context.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager).adapter + if (bluetoothAdapter.isEnabled) { + if (!context.bleManager.isScanning) { + context.bleManager.startScan(20 * 1000L, + object : OnBleScanCallback { + @SuppressLint("MissingPermission") + override fun onScanning(result: BleDevice) { + Napier.i("scanned device:${result}") + val newDevices = arrayListOf().apply { + addAll(_deviceList.value) + add(Device(result.device.name,result.device.address)) + } + _deviceList.value = newDevices + } + + override fun onScanFinished() { + } + }) + } + } + } + + override fun stopScan() { + } +} diff --git a/shared/src/androidMain/kotlin/com/whitefish/ring/HandlerHelper.kt b/shared/src/androidMain/kotlin/com/whitefish/ring/HandlerHelper.kt new file mode 100644 index 0000000..0a5d894 --- /dev/null +++ b/shared/src/androidMain/kotlin/com/whitefish/ring/HandlerHelper.kt @@ -0,0 +1,18 @@ +package com.whitefish.ring + +import android.os.Handler +import android.os.Looper + +private val uiHandler = Handler(Looper.getMainLooper()) + +fun postDelay(r: Runnable, delay: Long) = uiHandler.postDelayed(r, delay) + +fun postDelay(r: Runnable) = postDelay(r, 100L) + +fun post(r: Runnable) = uiHandler.post(r) + +fun Runnable.handlerPost() = post(this) + +fun Runnable.handlerPostDelay(delay: Long) = postDelay(this, delay) + +fun Runnable.handlerRemove() = uiHandler.removeCallbacks(this) \ No newline at end of file diff --git a/shared/src/androidMain/kotlin/com/whitefish/ring/PermissionManager.kt b/shared/src/androidMain/kotlin/com/whitefish/ring/PermissionManager.kt new file mode 100644 index 0000000..a832abc --- /dev/null +++ b/shared/src/androidMain/kotlin/com/whitefish/ring/PermissionManager.kt @@ -0,0 +1,112 @@ +package com.whitefish.ring + +import android.Manifest +import android.annotation.SuppressLint +import android.app.AlertDialog +import android.bluetooth.BluetoothAdapter +import android.bluetooth.BluetoothManager +import android.content.ActivityNotFoundException +import android.content.Context +import android.content.Context.BLUETOOTH_SERVICE +import android.content.Intent +import android.content.pm.PackageManager +import android.location.LocationManager +import android.os.Build +import android.provider.Settings +import androidx.activity.result.ActivityResultLauncher +import androidx.core.app.ActivityCompat +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +object PermissionManager { + var permissionChecker: ActivityResultLauncher>? = null + + @SuppressLint("MissingPermission") + fun checkPermission(context: Context) { + context.apply { + val dinedPermissions = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + checkDeniedPermissions( + this, + Manifest.permission.BLUETOOTH_SCAN, + Manifest.permission.BLUETOOTH_CONNECT + ) + } else { + checkDeniedPermissions( + this, + Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.ACCESS_COARSE_LOCATION + ) + } + if (dinedPermissions != null) { + permissionChecker?.launch(dinedPermissions) + return + }else{ + CoroutineScope(Dispatchers.IO).launch { + obtainDeviceManager().blePowerState.emit(true) + } + } + if (!locationServiceAllowed()) { + AlertDialog.Builder(this) + .setMessage(com.whitefish.ring.R.string.dialog_msg_turn_on_location_service) + .setCancelable(false) + .setPositiveButton(android.R.string.ok) { _, _ -> + goEnableLocationServicePage() + } + .create().show() + return + } + val bluetoothAdapter = + (getSystemService(BLUETOOTH_SERVICE) as BluetoothManager).adapter + if (!bluetoothAdapter.isEnabled) { + startActivity(Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE)) + } + } + } + + private fun Context.locationServiceAllowed(): Boolean { + return if (Build.VERSION.SDK_INT in Build.VERSION_CODES.M..Build.VERSION_CODES.R) { + val manager = getSystemService(Context.LOCATION_SERVICE) as LocationManager + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) manager.isLocationEnabled + else manager.isProviderEnabled(LocationManager.GPS_PROVIDER) + } else { + //Other versions do not need to turn on location services, + //so it can be considered that location services are turned on. + true + } + } + + private fun checkDeniedPermissions( + context: Context, + vararg permissions: String, + ): Array? { + val dinedPermissions: MutableList = ArrayList() + for (permission in permissions) { + if (ActivityCompat.checkSelfPermission(context, permission) + != PackageManager.PERMISSION_GRANTED + ) { + dinedPermissions.add(permission) + } + } + return if (dinedPermissions.isEmpty()) null else dinedPermissions.toTypedArray() + } + + fun Context.goEnableLocationServicePage() { + val intent = Intent() + .setAction(Settings.ACTION_LOCATION_SOURCE_SETTINGS) + .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + try { + startActivity(intent) + } catch (ex: ActivityNotFoundException) { + // The Android SDK doc says that the location settings activity + // may not be found. In that case show the general settings. + // General settings activity + intent.action = Settings.ACTION_SETTINGS + try { + startActivity(intent) + } catch (e: Exception) { + toast("Can not find the LOCATION setting page.") + } + } + } +} \ No newline at end of file diff --git a/shared/src/androidMain/kotlin/com/whitefish/ring/Platform.android.kt b/shared/src/androidMain/kotlin/com/whitefish/ring/Platform.android.kt index 11eac6f..eacc34c 100644 --- a/shared/src/androidMain/kotlin/com/whitefish/ring/Platform.android.kt +++ b/shared/src/androidMain/kotlin/com/whitefish/ring/Platform.android.kt @@ -1,11 +1,17 @@ package com.whitefish.ring +import android.Manifest +import android.content.Context +import android.content.pm.PackageManager import android.os.Build import androidx.compose.runtime.Composable import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.Dp -import android.content.Context +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat +import com.whitefish.ring.device.IDeviceManager + class AndroidPlatform : Platform { override val name: String = "Android ${Build.VERSION.SDK_INT}" @@ -54,3 +60,8 @@ private fun getNavigationBarHeightPx(context: Context): Int { } return navigationBarHeight } + +private val DeviceInstance = DeviceManager() +actual fun obtainDeviceManager(): IDeviceManager { + return DeviceInstance +} diff --git a/shared/src/androidMain/kotlin/com/whitefish/ring/Utils.kt b/shared/src/androidMain/kotlin/com/whitefish/ring/Utils.kt new file mode 100644 index 0000000..dec9586 --- /dev/null +++ b/shared/src/androidMain/kotlin/com/whitefish/ring/Utils.kt @@ -0,0 +1,49 @@ +package com.whitefish.ring + +import android.Manifest +import android.annotation.SuppressLint +import android.app.AlertDialog +import android.bluetooth.BluetoothAdapter +import android.bluetooth.BluetoothManager +import android.content.ActivityNotFoundException +import android.content.Context +import android.content.Context.BLUETOOTH_SERVICE +import android.content.Intent +import android.content.pm.PackageManager +import android.location.LocationManager +import android.os.Build +import android.provider.Settings +import android.widget.Toast +import androidx.activity.result.ActivityResultLauncher +import androidx.activity.result.contract.ActivityResultContracts +import androidx.annotation.StringRes +import androidx.core.app.ActivityCompat +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +fun Context.cmdErrorTip(code: Int) { + when (code) { + 0 -> toast(R.string.cmd_execute_success) + 1 -> toast(R.string.cmd_execute_failed_1) + 2 -> toast(R.string.cmd_execute_failed_2) + 3 -> toast(R.string.cmd_execute_failed_3) + 4 -> toast(R.string.cmd_execute_failed_4) + 5 -> toast(R.string.cmd_execute_failed_5) + 6 -> toast(R.string.cmd_execute_failed_6) + } +} + +var toast: Toast? = null + +fun Context.toast(tip: String) { + toast?.cancel() + toast = Toast.makeText(this, tip, Toast.LENGTH_SHORT) + .apply { show() } +} + +fun Context.toast(@StringRes tip: Int) { + toast?.cancel() + toast = Toast.makeText(this, tip, Toast.LENGTH_SHORT) + .apply { show() } +} \ No newline at end of file diff --git a/shared/src/androidMain/kotlin/com/whitefish/ring/bt/BleDevice.kt b/shared/src/androidMain/kotlin/com/whitefish/ring/bt/BleDevice.kt new file mode 100644 index 0000000..9d18098 --- /dev/null +++ b/shared/src/androidMain/kotlin/com/whitefish/ring/bt/BleDevice.kt @@ -0,0 +1,15 @@ +package com.whitefish.app.bt + +import android.bluetooth.BluetoothDevice + +data class BleDevice( + val device: BluetoothDevice, + val color: Int, + val size: Int, + val batteryState: Int? = null, + val batteryLevel: Int? = null, + /*val chipMode: Int = 0,*/ + val generation: Int? = null, + val sn: String? = null, + var rssi: Int, +) \ No newline at end of file diff --git a/shared/src/androidMain/kotlin/com/whitefish/ring/bt/BleManager.kt b/shared/src/androidMain/kotlin/com/whitefish/ring/bt/BleManager.kt new file mode 100644 index 0000000..536f62d --- /dev/null +++ b/shared/src/androidMain/kotlin/com/whitefish/ring/bt/BleManager.kt @@ -0,0 +1,423 @@ +package com.whitefish.app.bt + +import android.annotation.SuppressLint +import android.app.AlertDialog +import android.bluetooth.BluetoothDevice +import android.bluetooth.BluetoothGatt +import android.bluetooth.BluetoothGattDescriptor +import android.bluetooth.BluetoothManager +import android.bluetooth.BluetoothProfile +import android.bluetooth.le.ScanCallback +import android.bluetooth.le.ScanResult +import android.bluetooth.le.ScanSettings +import android.content.Context +import android.content.pm.PackageManager +import android.os.Build +import com.whitefish.ring.Application +import com.whitefish.ring.R +import com.whitefish.ring.bt.OnBleConnectionListener +import com.whitefish.ring.bt.OnBleScanCallback +import com.whitefish.ring.handlerRemove +import com.whitefish.ring.post +import com.whitefish.ring.postDelay +import io.github.aakira.napier.Napier +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import lib.linktop.nexring.api.NexRingBluetoothGattCallback +import lib.linktop.nexring.api.NexRingManager +import lib.linktop.nexring.api.OEM_AUTHENTICATION_FAILED_FOR_CHECK_R2 +import lib.linktop.nexring.api.OEM_AUTHENTICATION_FAILED_FOR_DECRYPT +import lib.linktop.nexring.api.OEM_AUTHENTICATION_FAILED_FOR_SN_NULL +import lib.linktop.nexring.api.OEM_AUTHENTICATION_START +import lib.linktop.nexring.api.OEM_AUTHENTICATION_SUCCESS +import lib.linktop.nexring.api.matchFromAdvertisementData +import lib.linktop.nexring.api.parseScanRecord + + +private const val OEM_STEP_CHECK_OEM_AUTHENTICATION_STATUS = 0 +private const val OEM_STEP_AUTHENTICATE_OEM = 1 +private const val OEM_STEP_TIMESTAMP_SYNC = 2 +private const val OEM_STEP_PROCESS_COMPLETED = 3 + +class BleManager(val app: Application) { + private val tag = "BleManager" + private val mBluetoothAdapter = + (app.getSystemService(Context.BLUETOOTH_SERVICE) as BluetoothManager).adapter + + private val mOnBleConnectionListeners: MutableList = ArrayList() + + private var mOnBleScanCallback: OnBleScanCallback? = null + var bleGatt: BluetoothGatt? = null + private val scanDevMacList: MutableList = ArrayList() + var isScanning = false + + private val mScanCallback = object : ScanCallback() { + + @SuppressLint("MissingPermission") + override fun onScanResult(callbackType: Int, result: ScanResult) { + super.onScanResult(callbackType, result) +// loge( +// "JKL", +// "address ${result.device.address}, scanRecord.bytes ${result.scanRecord?.bytes.toByteArrayString()}" +// ) + synchronized(scanDevMacList) { + val scanRecord = result.scanRecord + if (scanRecord != null) { + val bytes = scanRecord.bytes + if (bytes.matchFromAdvertisementData()) { + val address = result.device.address + if (!scanDevMacList.contains(address).apply { + Napier.i{"scanDevMacList contains address($address) = ${!this}"} + }) { + val bleDevice = bytes.parseScanRecord().run { + BleDevice( + result.device, color, size, + batteryState, batteryLevel, + /*chipMode,*/ generation, sn, + result.rssi + ) + } + scanDevMacList.add(address) + mOnBleScanCallback?.apply { + + post { + onScanning(bleDevice) + } + } + } + } + } + } + } + } + + private val scanStopRunnable = Runnable { + cancelScan() + } + + var bleState = MutableStateFlow(0) + var connectedDevice: BluetoothDevice? = null + + private val _oemStepComplete = MutableStateFlow(false) + val oemStepComplete = _oemStepComplete.asStateFlow() + + private val mGattCallback = object : NexRingBluetoothGattCallback(NexRingManager.get()) { + + @SuppressLint("MissingPermission") + override fun onConnectionStateChange( + gatt: BluetoothGatt, status: Int, newState: Int, + ) { + super.onConnectionStateChange(gatt, status, newState) + Napier.i ( + "onConnectionStateChange->status:$status, newState:$newState" + ) + when (newState) { + BluetoothProfile.STATE_DISCONNECTED -> { + NexRingManager.get().apply { + setBleGatt(null) + unregisterRingService() + } + connectedDevice = null + gatt.close() + bleState.value = BluetoothProfile.STATE_DISCONNECTED + postBleState() + _oemStepComplete.value = false + } + + BluetoothProfile.STATE_CONNECTING -> { + bleState.value = BluetoothProfile.STATE_CONNECTING + postBleState() + } + + BluetoothProfile.STATE_CONNECTED -> { + bleState.value = BluetoothProfile.STATE_CONNECTED + connectedDevice = gatt.device + postBleState() + // The default MTU for ATT in the core spec is 23 bytes, with 1 byte for ATT's Opcode, 2 bytes for ATT's Handle, and 20 bytes for GATT. + // So if you want to set 40, you should request the MTU to be set to 43. + gatt.requestMtu(40 + 3) + } + } + } + + @SuppressLint("MissingPermission") + override fun onMtuChanged(gatt: BluetoothGatt, mtu: Int, status: Int) { + super.onMtuChanged(gatt, mtu, status) + when (status) { + BluetoothGatt.GATT_SUCCESS -> { + Napier.i{ "onMtuChanged success."} + gatt.discoverServices() + } + + BluetoothGatt.GATT_FAILURE -> { + Napier.i( "onMtuChanged failure.") + } + + else -> Napier.i("onMtuChanged unknown status $status.") + } + } + + @SuppressLint("MissingPermission") + override fun onServicesDiscovered(gatt: BluetoothGatt, status: Int) { + super.onServicesDiscovered(gatt, status) + Napier.i( "onServicesDiscovered(), status:${status}") + // Refresh device cache. This is the safest place to initiate the procedure. + if (status == BluetoothGatt.GATT_SUCCESS) { + NexRingManager.get().setBleGatt(gatt) + Napier.i("onServicesDiscovered(), registerHealthData") + postDelay { + NexRingManager.get().registerRingService() + } + } + } + + override fun onDescriptorWrite( + gatt: BluetoothGatt, descriptor: BluetoothGattDescriptor, status: Int, + ) { + super.onDescriptorWrite(gatt, descriptor, status) + if (status == BluetoothGatt.GATT_SUCCESS && + NexRingManager.get().isRingServiceRegistered() + ) { +// post { +// //you need to synchronize the timestamp with the device first after +// //the the service registration is successful. +// NexRingManager.get() +// .settingsApi() +// .timestampSync(System.currentTimeMillis()) { +// synchronized(mOnBleConnectionListeners) { +// mOnBleConnectionListeners.forEach { +// it.onBleReady() +// } +// } +// } +// } + OemAuthenticationProcess().start() + } + } + } + + @SuppressLint("MissingPermission", "ObsoleteSdkInt") + private fun connectInterval(device: BluetoothDevice) { + Napier.i("connect gatt to ${device.address}") + bleGatt = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { +// device.connectGatt(context, false, gattCallback) + device.connectGatt(app, false, mGattCallback, BluetoothDevice.TRANSPORT_LE) + } else { + device.connectGatt(app, false, mGattCallback) + }.apply { connect() } + } + + fun isSupportBle(): Boolean = +// Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2 && + app.applicationContext.packageManager.hasSystemFeature(PackageManager.FEATURE_BLUETOOTH_LE) + + + @SuppressLint("MissingPermission") + fun startScan(timeoutMillis: Long, callback: OnBleScanCallback) { + isScanning = true + mOnBleScanCallback = callback + scanDevMacList.clear() + val scanSettings = ScanSettings.Builder() + .setScanMode(ScanSettings.SCAN_MODE_LOW_LATENCY) + .build() + mBluetoothAdapter.bluetoothLeScanner.startScan(null, scanSettings, mScanCallback) + postDelay(scanStopRunnable, timeoutMillis) + } + + @SuppressLint("MissingPermission") + fun cancelScan() { + if (isScanning) { + isScanning = false + mBluetoothAdapter.bluetoothLeScanner.stopScan(mScanCallback) + post { + mOnBleScanCallback?.onScanFinished() + mOnBleScanCallback = null + scanStopRunnable.handlerRemove() + } + } + scanDevMacList.clear() + } + + @SuppressLint("MissingPermission") + fun connect(address: String): Boolean { + val remoteDevice = mBluetoothAdapter.getRemoteDevice(address) + Napier.i( "connect to remoteDevice by address, ${remoteDevice.name}") + return if (!remoteDevice.name.isNullOrEmpty()) { + connect(remoteDevice) + true + } else { + Napier.i("reject, because it cannot connect success.") + false + } + } + + fun connect(device: BluetoothDevice) { + val delayConnect = isScanning + cancelScan() + if (delayConnect) { + Napier.i( "connect to ${device.address}, delay 200L") + postDelay({ + Napier.i("delay finish, connect to ${device.address}") + connectInterval(device) + }, 200L) + } else { + Napier.i("connect to ${device.address} right now.") + connectInterval(device) + } + } + + @SuppressLint("MissingPermission") + fun disconnect() { + bleGatt?.disconnect() + bleGatt = null + } + + + fun addOnBleConnectionListener(listener: OnBleConnectionListener) { + synchronized(mOnBleConnectionListeners) { + mOnBleConnectionListeners.add(listener) + } + } + + fun removeOnBleConnectionListener(listener: OnBleConnectionListener) { + synchronized(mOnBleConnectionListeners) { + mOnBleConnectionListeners.remove(listener) + } + } + + fun postBleState() { + post { + synchronized(mOnBleConnectionListeners) { + mOnBleConnectionListeners.forEach { + it.onBleState(bleState.value) + } + } + } + } + + inner class OemAuthenticationProcess : Thread() { + + private val innerTag = "OemAuthenticationProcess" + private val locked = Object() + private var step = OEM_STEP_CHECK_OEM_AUTHENTICATION_STATUS + + override fun run() { + while (step < OEM_STEP_PROCESS_COMPLETED) { + sleep(200L) + synchronized(locked) { + when (step) { + OEM_STEP_CHECK_OEM_AUTHENTICATION_STATUS -> { + Napier.i( "OEM_STEP_CHECK_OEM_AUTHENTICATION_STATUS") + NexRingManager.get().securityApi().checkOemAuthenticationStatus { + step = if (it) OEM_STEP_AUTHENTICATE_OEM else OEM_STEP_TIMESTAMP_SYNC + synchronized(locked) { + locked.notify() + } + } + } + + OEM_STEP_AUTHENTICATE_OEM -> { + Napier.i( "OEM_STEP_AUTHENTICATE_OEM") + NexRingManager.get().securityApi().authenticateOem { result -> + when (result) { + OEM_AUTHENTICATION_FAILED_FOR_CHECK_R2 -> { + Napier.i( "OEM_AUTHENTICATION_FAILED_FOR_CHECK_R2") + step = OEM_STEP_PROCESS_COMPLETED + result.showOemAuthFailDialog() + synchronized(locked) { + locked.notify() + } + } + + OEM_AUTHENTICATION_FAILED_FOR_DECRYPT -> { + Napier.i( "OEM_AUTHENTICATION_FAILED_FOR_DECRYPT") + step = OEM_STEP_PROCESS_COMPLETED + result.showOemAuthFailDialog() + synchronized(locked) { + locked.notify() + } + } + + OEM_AUTHENTICATION_FAILED_FOR_SN_NULL -> { + Napier.i("OEM_AUTHENTICATION_FAILED_FOR_SN_NULL") + step = OEM_STEP_PROCESS_COMPLETED + result.showOemAuthFailDialog() + synchronized(locked) { + locked.notify() + } + } + + OEM_AUTHENTICATION_START -> { + Napier.i( "OEM_AUTHENTICATION_START") + } + + OEM_AUTHENTICATION_SUCCESS -> { + Napier.i( "OEM_AUTHENTICATION_SUCCESS") + step = OEM_STEP_TIMESTAMP_SYNC + synchronized(locked) { + locked.notify() + } + } + } + } + } + + OEM_STEP_TIMESTAMP_SYNC -> { + Napier.i( "OEM_STEP_TIMESTAMP_SYNC") + NexRingManager.get() + .settingsApi() + .timestampSync(System.currentTimeMillis()) { + Napier.i( "OEM_STEP_TIMESTAMP_SYNC result $it") + synchronized(mOnBleConnectionListeners) { + post { + mOnBleConnectionListeners.forEach { listener -> + listener.onBleReady() + } + } + } + step = OEM_STEP_PROCESS_COMPLETED + synchronized(locked) { + locked.notify() + } + } + } + } + locked.wait() + } + } + _oemStepComplete.value = true + Napier.i("OEM_STEP_PROCESS_COMPLETED") + } + } + + private fun Int.showOemAuthFailDialog() { + app.mActivityLifecycleCb.currAct.apply { + if (this != null) { + val message = when (this@showOemAuthFailDialog) { + OEM_AUTHENTICATION_FAILED_FOR_SN_NULL -> { + getString(R.string.dialog_msg_oem_auth_failed_cause_by_sn_null) + } + + OEM_AUTHENTICATION_FAILED_FOR_DECRYPT -> { + getString(R.string.dialog_msg_oem_auth_failed_cause_by_r1_to_r2) + } + + OEM_AUTHENTICATION_FAILED_FOR_CHECK_R2 -> { + getString(R.string.dialog_msg_oem_auth_failed_cause_by_check_r2) + } + + else -> "Unknown error." + } + runOnUiThread { + AlertDialog.Builder(this) + .setCancelable(false) + .setTitle(R.string.dialog_title_oem_auth_failed) + .setMessage(message) + .setPositiveButton(R.string.btn_label_disconnected) { _, _ -> + disconnect() + }.create().show() + } + } else disconnect() + } + } +} \ No newline at end of file diff --git a/shared/src/androidMain/kotlin/com/whitefish/ring/bt/OnBleConnectionListener.kt b/shared/src/androidMain/kotlin/com/whitefish/ring/bt/OnBleConnectionListener.kt new file mode 100644 index 0000000..995afd9 --- /dev/null +++ b/shared/src/androidMain/kotlin/com/whitefish/ring/bt/OnBleConnectionListener.kt @@ -0,0 +1,8 @@ +package com.whitefish.ring.bt + +interface OnBleConnectionListener { + + fun onBleState(state: Int) + + fun onBleReady() +} \ No newline at end of file diff --git a/shared/src/androidMain/kotlin/com/whitefish/ring/bt/OnBleScanCallback.kt b/shared/src/androidMain/kotlin/com/whitefish/ring/bt/OnBleScanCallback.kt new file mode 100644 index 0000000..936d7c5 --- /dev/null +++ b/shared/src/androidMain/kotlin/com/whitefish/ring/bt/OnBleScanCallback.kt @@ -0,0 +1,10 @@ +package com.whitefish.ring.bt + +import com.whitefish.app.bt.BleDevice + +interface OnBleScanCallback { + + fun onScanning(result: BleDevice) + + fun onScanFinished() +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/whitefish/ring/App.kt b/shared/src/commonMain/kotlin/com/whitefish/ring/App.kt index 3343ab1..1cb5414 100644 --- a/shared/src/commonMain/kotlin/com/whitefish/ring/App.kt +++ b/shared/src/commonMain/kotlin/com/whitefish/ring/App.kt @@ -3,6 +3,8 @@ package com.whitefish.ring import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.* import androidx.compose.ui.Modifier +import com.whitefish.ring.ui.guide.DeviceScreen +import com.whitefish.ring.ui.guide.GuideNavigationScreen import com.whitefish.ring.ui.home.HomeScreen import org.jetbrains.compose.ui.tooling.preview.Preview @@ -10,8 +12,12 @@ import org.jetbrains.compose.ui.tooling.preview.Preview @Preview fun App() { MaterialTheme { - HomeScreen( - modifier = Modifier - ) +// HomeScreen( +// modifier = Modifier +// ) +// DeviceScreen{ +// +// } + GuideNavigationScreen() } } \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/whitefish/ring/Platform.kt b/shared/src/commonMain/kotlin/com/whitefish/ring/Platform.kt index 79e7fe6..1e44571 100644 --- a/shared/src/commonMain/kotlin/com/whitefish/ring/Platform.kt +++ b/shared/src/commonMain/kotlin/com/whitefish/ring/Platform.kt @@ -2,6 +2,7 @@ package com.whitefish.ring import androidx.compose.runtime.Composable import androidx.compose.ui.unit.Dp +import com.whitefish.ring.device.IDeviceManager interface Platform { val name: String @@ -13,4 +14,6 @@ expect fun getPlatform(): Platform expect fun getStatusBarHeight(): Dp @Composable -expect fun getNavigationBarHeight(): Dp \ No newline at end of file +expect fun getNavigationBarHeight(): Dp + +expect fun obtainDeviceManager(): IDeviceManager \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/whitefish/ring/bean/ui/Device.kt b/shared/src/commonMain/kotlin/com/whitefish/ring/bean/ui/Device.kt new file mode 100644 index 0000000..d5c3beb --- /dev/null +++ b/shared/src/commonMain/kotlin/com/whitefish/ring/bean/ui/Device.kt @@ -0,0 +1,3 @@ +package com.whitefish.ring.bean.ui + +data class Device (val name: String,val mac: String) diff --git a/shared/src/commonMain/kotlin/com/whitefish/ring/device/IDeviceManager.kt b/shared/src/commonMain/kotlin/com/whitefish/ring/device/IDeviceManager.kt new file mode 100644 index 0000000..7fd2eec --- /dev/null +++ b/shared/src/commonMain/kotlin/com/whitefish/ring/device/IDeviceManager.kt @@ -0,0 +1,30 @@ +package com.whitefish.ring.device + +import com.whitefish.ring.bean.ui.Device +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.flow.asStateFlow + +abstract class IDeviceManager { + protected val _deviceList = MutableStateFlow>(emptyList()) + val deviceList = _deviceList.asStateFlow() + protected val _bleState = MutableStateFlow(-1) + val bleState = _bleState.asStateFlow() + val bleReadyStateFlow = MutableStateFlow(false) + val blePowerState = MutableStateFlow(false) // ios的蓝牙是懒加载的,安卓则无此特性 + + private val bleStateListeners = arrayListOf<(Int) -> Unit>() + fun bleStateListeners() = bleStateListeners + + + fun setOnBleStateChange(event: (Int) -> Unit) { + bleStateListeners.add(event) + } + + + abstract fun startScan() + abstract fun stopScan() + abstract fun connect(mac: String) + abstract fun bind() +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/whitefish/ring/ui/guide/ConnectionGuideScreen.kt b/shared/src/commonMain/kotlin/com/whitefish/ring/ui/guide/ConnectionGuideScreen.kt new file mode 100644 index 0000000..959e681 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/whitefish/ring/ui/guide/ConnectionGuideScreen.kt @@ -0,0 +1,144 @@ +package com.whitefish.ring.ui.guide + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import org.jetbrains.compose.ui.tooling.preview.Preview + +@Composable +fun ConnectionGuideScreen( + onNextClick: () -> Unit = {} +) { + Box( + modifier = Modifier + .fillMaxSize() + .background(Color(0xFFF5F5F5)) + ) { + Column( + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 24.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Spacer(modifier = Modifier.height(80.dp)) + + // 标题 + Text( + text = "连接您的Acti戒指", + fontSize = 24.sp, + fontWeight = FontWeight.Medium, + color = Color(0xFF333333), + textAlign = TextAlign.Center, + modifier = Modifier.fillMaxWidth() + ) + + Spacer(modifier = Modifier.height(40.dp)) + + // 说明文字 + Text( + text = "将您的戒指连接到充电器,并继续下一步。请确保您的手机已启用蓝牙功能。", + fontSize = 16.sp, + color = Color(0xFF666666), + textAlign = TextAlign.Center, + lineHeight = 24.sp, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp) + ) + + Spacer(modifier = Modifier.height(60.dp)) + + // 戒指和充电器图片占位符 + Box( + modifier = Modifier + .size(280.dp) + .background( + Color.White, + RoundedCornerShape(20.dp) + ), + contentAlignment = Alignment.Center + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + // 戒指图标 + Text( + text = "💍", + fontSize = 80.sp + ) + + Spacer(modifier = Modifier.height(20.dp)) + + // 连接线 + Box( + modifier = Modifier + .width(60.dp) + .height(4.dp) + .background( + Color(0xFF007AFF), + RoundedCornerShape(2.dp) + ) + ) + + Spacer(modifier = Modifier.height(20.dp)) + + // 充电器图标 + Box( + modifier = Modifier + .size(80.dp) + .background( + Color(0xFF333333), + RoundedCornerShape(40.dp) + ), + contentAlignment = Alignment.Center + ) { + Text( + text = "⚡", + fontSize = 40.sp, + color = Color.White + ) + } + } + } + + Spacer(modifier = Modifier.weight(1f)) + + // 下一步按钮 + Button( + onClick = onNextClick, + modifier = Modifier + .fillMaxWidth() + .height(56.dp), + shape = RoundedCornerShape(28.dp), + colors = ButtonDefaults.buttonColors( + containerColor = Color(0xFF007AFF), + contentColor = Color.White + ) + ) { + Text( + text = "下一步", + fontSize = 18.sp, + fontWeight = FontWeight.Medium + ) + } + + Spacer(modifier = Modifier.height(40.dp)) + } + } +} + +@Composable +@Preview +fun ConnectionGuideScreenPreview() { + ConnectionGuideScreen() +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/whitefish/ring/ui/guide/DeviceScreen.kt b/shared/src/commonMain/kotlin/com/whitefish/ring/ui/guide/DeviceScreen.kt new file mode 100644 index 0000000..e3ad73a --- /dev/null +++ b/shared/src/commonMain/kotlin/com/whitefish/ring/ui/guide/DeviceScreen.kt @@ -0,0 +1,150 @@ +package com.whitefish.ring.ui.guide + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.items +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import androidx.lifecycle.viewmodel.compose.viewModel +import com.whitefish.ring.bean.ui.Device +import com.whitefish.ring.device.IDeviceManager +import org.jetbrains.compose.ui.tooling.preview.Preview + +@Composable +fun DeviceScreen(onBind:() -> Unit){ + val viewModel: DeviceViewModel = viewModel { DeviceViewModel() } + val uiState by viewModel.uiState.collectAsState() + val bindState by viewModel.manager.bleReadyStateFlow.collectAsState() + + if (bindState){ + onBind.invoke() + } + + Box( + modifier = Modifier + .fillMaxSize() + .background(Color(0xFFF5F5F5)) + ) { + Column( + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 20.dp) + ) { + // 标题 + Text( + text = "附近设备", + fontSize = 24.sp, + fontWeight = FontWeight.Medium, + color = Color(0xFF333333), + modifier = Modifier + .fillMaxWidth() + .padding(top = 60.dp, bottom = 40.dp), + textAlign = TextAlign.Center + ) + + // 设备列表 + LazyColumn( + verticalArrangement = Arrangement.spacedBy(16.dp), + modifier = Modifier.weight(1f) + ) { + items(uiState.deviceList) { device -> + DeviceItem(device = device){ + viewModel.connect(it.mac) + } + } + } + } + + // 底部提示 + Text( + text = "连接失败?", + fontSize = 16.sp, + color = Color(0xFF666666), + modifier = Modifier + .align(Alignment.BottomCenter) + .padding(bottom = 21.dp) + ) + } +} + +@Composable +private fun DeviceItem(device: Device,onClick:(Device)-> Unit) { + Card( + modifier = Modifier + .fillMaxWidth() + .height(80.dp), + onClick = { + onClick.invoke(device) + }, + shape = RoundedCornerShape(16.dp), + colors = CardDefaults.cardColors( + containerColor = Color.White + ), + elevation = CardDefaults.cardElevation( + defaultElevation = 2.dp + ) + ) { + Row( + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 20.dp, vertical = 16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + // 设备图标占位符 + Box( + modifier = Modifier + .size(48.dp) + .clip(RoundedCornerShape(24.dp)) + .background(Color(0xFFE5E5E5)), + contentAlignment = Alignment.Center + ) { + // 这里可以放置实际的设备图标 + Text( + text = "💍", + fontSize = 24.sp + ) + } + + Spacer(modifier = Modifier.width(16.dp)) + + // 设备信息 + Column( + modifier = Modifier.weight(1f) + ) { + Text( + text = device.name, + fontSize = 18.sp, + fontWeight = FontWeight.Medium, + color = Color(0xFF333333) + ) + + Spacer(modifier = Modifier.height(4.dp)) + + Text( + text = "设备号:${device.mac}", + fontSize = 14.sp, + color = Color(0xFF999999) + ) + } + } + } +} + +@Composable +@Preview +fun Device(){ + DeviceScreen{ + + } +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/whitefish/ring/ui/guide/DeviceViewModel.kt b/shared/src/commonMain/kotlin/com/whitefish/ring/ui/guide/DeviceViewModel.kt new file mode 100644 index 0000000..90c16dc --- /dev/null +++ b/shared/src/commonMain/kotlin/com/whitefish/ring/ui/guide/DeviceViewModel.kt @@ -0,0 +1,64 @@ +package com.whitefish.ring.ui.guide + +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.whitefish.ring.bean.ui.Device +import com.whitefish.ring.device.IDeviceManager +import com.whitefish.ring.obtainDeviceManager +import io.github.aakira.napier.Napier +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch + +class DeviceViewModel: ViewModel() { +// var currentStep by remember { mutableStateOf(GuideStep.WELCOME) } + + class UiState( + val deviceList: List = emptyList() + ) + + val manager = obtainDeviceManager() + private val _uiState = MutableStateFlow(UiState()) + val uiState = _uiState.asStateFlow() + + init { + Napier.i { "DeviceViewModel initializing..." } + + viewModelScope.launch { + + launch { + manager.deviceList.collectLatest { + Napier.i { "new device:${it}" } + _uiState.value = UiState(it) + } + } + + launch { + manager.blePowerState.collectLatest { + if (it){ + manager.startScan() + } + } + } + + launch { + manager.bleReadyStateFlow.collectLatest { + Napier.i { "ble ready:${it}" } + if (it){ + manager.bind() + } + } + } + } + } + + fun connect(mac: String){ + manager.connect(mac) + } + +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/whitefish/ring/ui/guide/DominantHandScreen.kt b/shared/src/commonMain/kotlin/com/whitefish/ring/ui/guide/DominantHandScreen.kt new file mode 100644 index 0000000..384b325 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/whitefish/ring/ui/guide/DominantHandScreen.kt @@ -0,0 +1,190 @@ +package com.whitefish.ring.ui.guide + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import org.jetbrains.compose.ui.tooling.preview.Preview + +@Composable +fun DominantHandScreen( + onNextClick: () -> Unit = {}, + onHandSelected: (Hand) -> Unit = {} +) { + var selectedHand by remember { mutableStateOf(null) } + + Box( + modifier = Modifier + .fillMaxSize() + .background(Color(0xFFF5F5F5)) + ) { + Column( + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 24.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Spacer(modifier = Modifier.height(80.dp)) + + // 标题 + Text( + text = "惯用手", + fontSize = 24.sp, + fontWeight = FontWeight.Medium, + color = Color(0xFF333333), + textAlign = TextAlign.Center, + modifier = Modifier.fillMaxWidth() + ) + + Spacer(modifier = Modifier.height(16.dp)) + + // 副标题 + Text( + text = "请选择您的惯用手", + fontSize = 16.sp, + color = Color(0xFF666666), + textAlign = TextAlign.Center, + modifier = Modifier.fillMaxWidth() + ) + + Spacer(modifier = Modifier.height(80.dp)) + + // 选择区域 + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.spacedBy(24.dp) + ) { + // 左手选项 + HandOptionCard( + hand = Hand.LEFT, + isSelected = selectedHand == Hand.LEFT, + onClick = { + selectedHand = Hand.LEFT + onHandSelected(Hand.LEFT) + }, + modifier = Modifier.weight(1f) + ) + + // 右手选项 + HandOptionCard( + hand = Hand.RIGHT, + isSelected = selectedHand == Hand.RIGHT, + onClick = { + selectedHand = Hand.RIGHT + onHandSelected(Hand.RIGHT) + }, + modifier = Modifier.weight(1f) + ) + } + + Spacer(modifier = Modifier.weight(1f)) + + // 下一步按钮 + Button( + onClick = onNextClick, + enabled = selectedHand != null, + modifier = Modifier + .fillMaxWidth() + .height(56.dp), + shape = RoundedCornerShape(28.dp), + colors = ButtonDefaults.buttonColors( + containerColor = if (selectedHand != null) Color(0xFF007AFF) else Color(0xFFCCCCCC), + contentColor = Color.White + ) + ) { + Text( + text = "下一步", + fontSize = 18.sp, + fontWeight = FontWeight.Medium + ) + } + + Spacer(modifier = Modifier.height(40.dp)) + } + } +} + +@Composable +private fun HandOptionCard( + hand: Hand, + isSelected: Boolean, + onClick: () -> Unit, + modifier: Modifier = Modifier +) { + Card( + modifier = modifier + .height(200.dp) + .clickable { onClick() }, + shape = RoundedCornerShape(20.dp), + colors = CardDefaults.cardColors( + containerColor = if (isSelected) Color(0xFFE3F2FD) else Color.White + ), + elevation = CardDefaults.cardElevation( + defaultElevation = if (isSelected) 8.dp else 4.dp + ) + ) { + Box( + modifier = Modifier + .fillMaxSize() + .then( + if (isSelected) { + Modifier.border( + width = 2.dp, + color = Color(0xFF007AFF), + shape = RoundedCornerShape(20.dp) + ) + } else { + Modifier + } + ), + contentAlignment = Alignment.Center + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + // 手部图标占位符 + Box( + modifier = Modifier + .size(100.dp) + .background( + if (isSelected) Color(0xFF007AFF) else Color(0xFFE5E5E5), + RoundedCornerShape(50.dp) + ), + contentAlignment = Alignment.Center + ) { + Text( + text = if (hand == Hand.LEFT) "✋" else "🤚", + fontSize = 48.sp, + color = if (isSelected) Color.White else Color(0xFF666666) + ) + } + + Spacer(modifier = Modifier.height(20.dp)) + + Text( + text = if (hand == Hand.LEFT) "左手" else "右手", + fontSize = 18.sp, + fontWeight = FontWeight.Medium, + color = if (isSelected) Color(0xFF007AFF) else Color(0xFF333333) + ) + } + } + } +} + +@Composable +@Preview +fun DominantHandScreenPreview() { + DominantHandScreen() +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/whitefish/ring/ui/guide/GuideNavigationScreen.kt b/shared/src/commonMain/kotlin/com/whitefish/ring/ui/guide/GuideNavigationScreen.kt new file mode 100644 index 0000000..160a656 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/whitefish/ring/ui/guide/GuideNavigationScreen.kt @@ -0,0 +1,100 @@ +package com.whitefish.ring.ui.guide + +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier + +enum class GuideStep { + WELCOME, + REGISTER, + CONNECTION_GUIDE, + DEVICE_LIST, + PERSONAL_INFO, + WEARING_FINGER, + DOMINANT_HAND, + +} + +@Composable +fun GuideNavigationScreen( + modifier: Modifier = Modifier, + onGuideComplete: () -> Unit = {}, + +) { + var currentStep by remember { mutableStateOf(GuideStep.WELCOME) } + + when (currentStep) { + GuideStep.WELCOME -> { + WelcomeScreen( + onStartClick = { + currentStep = GuideStep.REGISTER + } + ) + } + + GuideStep.REGISTER -> { + RegisterScreen( + onLoginClick = { phoneNumber, verificationCode -> + // 这里可以添加登录验证逻辑 + if (phoneNumber.isNotEmpty() && verificationCode.isNotEmpty()) { + currentStep = GuideStep.CONNECTION_GUIDE + } + } + ) + } + + GuideStep.CONNECTION_GUIDE -> { + ConnectionGuideScreen( + onNextClick = { + currentStep = GuideStep.DEVICE_LIST + } + ) + } + + + GuideStep.DEVICE_LIST -> { + DeviceScreen{ + currentStep = GuideStep.PERSONAL_INFO + } + } + + GuideStep.PERSONAL_INFO -> { + PersonalInfoScreen( + onNextClick = { + currentStep = GuideStep.WEARING_FINGER + }, + onGenderClick = { + // 这里可以打开性别选择对话框或跳转到专门的性别选择页面 + }, + onBirthdayClick = { + // 这里可以打开日期选择器 + }, + onHeightClick = { + // 这里可以打开身高选择器 + } + ) + } + + GuideStep.WEARING_FINGER -> { + WearingFingerScreen( + onNextClick = { + currentStep = GuideStep.DOMINANT_HAND + }, + onFingerSelected = { position -> + // 保存选择的佩戴位置 + } + ) + } + + GuideStep.DOMINANT_HAND -> { + DominantHandScreen( + onNextClick = { + // 完成所有引导步骤 + onGuideComplete() + }, + onHandSelected = { hand -> + // 保存选择的惯用手 + } + ) + } + } +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/whitefish/ring/ui/guide/GuideScreensPreviews.kt b/shared/src/commonMain/kotlin/com/whitefish/ring/ui/guide/GuideScreensPreviews.kt new file mode 100644 index 0000000..9fb1042 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/whitefish/ring/ui/guide/GuideScreensPreviews.kt @@ -0,0 +1,107 @@ +package com.whitefish.ring.ui.guide + +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import org.jetbrains.compose.ui.tooling.preview.Preview + +@Composable +@Preview +fun AllGuideScreensPreview() { + LazyColumn( + modifier = Modifier.fillMaxSize(), + verticalArrangement = Arrangement.spacedBy(16.dp), + contentPadding = PaddingValues(16.dp) + ) { + item { + Card( + modifier = Modifier + .fillMaxWidth() + .height(600.dp) + ) { + WelcomeScreen() + } + } + + item { + Card( + modifier = Modifier + .fillMaxWidth() + .height(600.dp) + ) { + RegisterScreen() + } + } + + item { + Card( + modifier = Modifier + .fillMaxWidth() + .height(600.dp) + ) { + ConnectionGuideScreen() + } + } + + item { + Card( + modifier = Modifier + .fillMaxWidth() + .height(600.dp) + ) { + SearchTip() + } + } + + item { + Card( + modifier = Modifier + .fillMaxWidth() + .height(600.dp) + ) { + DeviceScreen{ + + } + } + } + + item { + Card( + modifier = Modifier + .fillMaxWidth() + .height(600.dp) + ) { + PersonalInfoScreen() + } + } + + item { + Card( + modifier = Modifier + .fillMaxWidth() + .height(600.dp) + ) { + WearingFingerScreen() + } + } + + item { + Card( + modifier = Modifier + .fillMaxWidth() + .height(600.dp) + ) { + DominantHandScreen() + } + } + } +} + +@Composable +@Preview +fun GuideNavigationPreview() { + GuideNavigationScreen() +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/whitefish/ring/ui/guide/PersonalInfoScreen.kt b/shared/src/commonMain/kotlin/com/whitefish/ring/ui/guide/PersonalInfoScreen.kt new file mode 100644 index 0000000..2f8f79f --- /dev/null +++ b/shared/src/commonMain/kotlin/com/whitefish/ring/ui/guide/PersonalInfoScreen.kt @@ -0,0 +1,174 @@ +package com.whitefish.ring.ui.guide + +import androidx.compose.foundation.background +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import org.jetbrains.compose.ui.tooling.preview.Preview + +data class PersonalInfo( + val gender: String = "", + val birthday: String = "", + val height: String = "" +) + +@Composable +fun PersonalInfoScreen( + onNextClick: () -> Unit = {}, + onGenderClick: () -> Unit = {}, + onBirthdayClick: () -> Unit = {}, + onHeightClick: () -> Unit = {} +) { + var personalInfo by remember { mutableStateOf(PersonalInfo()) } + + Box( + modifier = Modifier + .fillMaxSize() + .background(Color(0xFFF5F5F5)) + ) { + Column( + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 24.dp) + ) { + Spacer(modifier = Modifier.height(80.dp)) + + // 标题 + Text( + text = "个人信息完善", + fontSize = 24.sp, + fontWeight = FontWeight.Medium, + color = Color(0xFF333333), + textAlign = TextAlign.Center, + modifier = Modifier.fillMaxWidth() + ) + + Spacer(modifier = Modifier.height(16.dp)) + + // 副标题 + Text( + text = "完善您的个人信息以便更好的服务", + fontSize = 16.sp, + color = Color(0xFF666666), + textAlign = TextAlign.Center, + modifier = Modifier.fillMaxWidth() + ) + + Spacer(modifier = Modifier.height(60.dp)) + + // 性别选项 + PersonalInfoItem( + title = "性别", + value = personalInfo.gender.ifEmpty { "" }, + onClick = onGenderClick + ) + + Spacer(modifier = Modifier.height(24.dp)) + + // 生日选项 + PersonalInfoItem( + title = "生日", + value = personalInfo.birthday.ifEmpty { "" }, + onClick = onBirthdayClick + ) + + Spacer(modifier = Modifier.height(24.dp)) + + // 身高选项 + PersonalInfoItem( + title = "身高", + value = personalInfo.height.ifEmpty { "" }, + onClick = onHeightClick + ) + + Spacer(modifier = Modifier.weight(1f)) + + // 下一步按钮 + Button( + onClick = onNextClick, + modifier = Modifier + .fillMaxWidth() + .height(56.dp), + shape = RoundedCornerShape(28.dp), + colors = ButtonDefaults.buttonColors( + containerColor = Color(0xFF007AFF), + contentColor = Color.White + ) + ) { + Text( + text = "下一步", + fontSize = 18.sp, + fontWeight = FontWeight.Medium + ) + } + + Spacer(modifier = Modifier.height(40.dp)) + } + } +} + +@Composable +private fun PersonalInfoItem( + title: String, + value: String, + onClick: () -> Unit +) { + Card( + modifier = Modifier + .fillMaxWidth() + .clickable { onClick() }, + shape = RoundedCornerShape(16.dp), + colors = CardDefaults.cardColors( + containerColor = Color.White + ), + elevation = CardDefaults.cardElevation( + defaultElevation = 2.dp + ) + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 20.dp, vertical = 20.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Text( + text = title, + fontSize = 18.sp, + fontWeight = FontWeight.Medium, + color = Color(0xFF333333), + modifier = Modifier.weight(1f) + ) + + if (value.isNotEmpty()) { + Text( + text = value, + fontSize = 16.sp, + color = Color(0xFF666666), + modifier = Modifier.padding(end = 8.dp) + ) + } + +// Icon( +// imageVector = Icons.Default.KeyboardArrowRight, +// contentDescription = "展开", +// tint = Color(0xFF999999), +// modifier = Modifier.size(24.dp) +// ) + } + } +} + +@Composable +@Preview +fun PersonalInfoScreenPreview() { + PersonalInfoScreen() +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/whitefish/ring/ui/guide/RegisterScreen.kt b/shared/src/commonMain/kotlin/com/whitefish/ring/ui/guide/RegisterScreen.kt new file mode 100644 index 0000000..e3b7404 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/whitefish/ring/ui/guide/RegisterScreen.kt @@ -0,0 +1,214 @@ +package com.whitefish.ring.ui.guide + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.text.KeyboardOptions +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.input.KeyboardType +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import org.jetbrains.compose.ui.tooling.preview.Preview + +@Composable +fun RegisterScreen( + onLoginClick: (phoneNumber: String, verificationCode: String) -> Unit = { _, _ -> } +) { + var phoneNumber by remember { mutableStateOf("") } + var verificationCode by remember { mutableStateOf("") } + var isCodeSent by remember { mutableStateOf(false) } + + Box( + modifier = Modifier + .fillMaxSize() + .background(Color(0xFFF5F5F5)) + ) { + Column( + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 24.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Spacer(modifier = Modifier.height(120.dp)) + + // 戒指图片占位符 + Box( + modifier = Modifier + .size(160.dp) + .background( + Color(0xFFE5E5E5), + RoundedCornerShape(80.dp) + ), + contentAlignment = Alignment.Center + ) { + Text( + text = "💍", + fontSize = 64.sp + ) + } + + Spacer(modifier = Modifier.height(40.dp)) + + // 欢迎标题 + Column( + horizontalAlignment = Alignment.CenterHorizontally + ) { + Text( + text = "Hi,", + fontSize = 28.sp, + fontWeight = FontWeight.Medium, + color = Color(0xFF333333), + textAlign = TextAlign.Center + ) + + Text( + text = "欢迎来到Acti", + fontSize = 28.sp, + fontWeight = FontWeight.Medium, + color = Color(0xFF333333), + textAlign = TextAlign.Center + ) + } + + Spacer(modifier = Modifier.height(60.dp)) + + // 手机号输入框 + Column( + modifier = Modifier.fillMaxWidth() + ) { + Text( + text = "手机号", + fontSize = 16.sp, + fontWeight = FontWeight.Medium, + color = Color(0xFF333333), + modifier = Modifier.padding(bottom = 8.dp) + ) + + OutlinedTextField( + value = phoneNumber, + onValueChange = { phoneNumber = it }, + placeholder = { + Text( + text = "请输入您的手机号", + color = Color(0xFF999999) + ) + }, + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(12.dp), + colors = OutlinedTextFieldDefaults.colors( + focusedBorderColor = Color(0xFF007AFF), + unfocusedBorderColor = Color(0xFFE5E5E5), + focusedContainerColor = Color.White, + unfocusedContainerColor = Color.White + ), + singleLine = true + ) + } + + Spacer(modifier = Modifier.height(24.dp)) + + // 验证码输入框 + Column( + modifier = Modifier.fillMaxWidth() + ) { + Text( + text = "验证码", + fontSize = 16.sp, + fontWeight = FontWeight.Medium, + color = Color(0xFF333333), + modifier = Modifier.padding(bottom = 8.dp) + ) + + Row( + modifier = Modifier.fillMaxWidth(), + verticalAlignment = Alignment.CenterVertically + ) { + OutlinedTextField( + value = verificationCode, + onValueChange = { verificationCode = it }, + placeholder = { + Text( + text = "请输入验证码", + color = Color(0xFF999999) + ) + }, + modifier = Modifier.weight(1f), + shape = RoundedCornerShape(12.dp), + colors = OutlinedTextFieldDefaults.colors( + focusedBorderColor = Color(0xFF007AFF), + unfocusedBorderColor = Color(0xFFE5E5E5), + focusedContainerColor = Color.White, + unfocusedContainerColor = Color.White + ), + singleLine = true + ) + + Spacer(modifier = Modifier.width(12.dp)) + + TextButton( + onClick = { + if (phoneNumber.isNotEmpty()) { + isCodeSent = true + } + }, + enabled = phoneNumber.isNotEmpty() + ) { + Text( + text = if (isCodeSent) "重新发送验证码" else "获取验证码", + fontSize = 14.sp, + color = if (phoneNumber.isNotEmpty()) Color(0xFF007AFF) else Color(0xFF999999) + ) + } + } + } + + Spacer(modifier = Modifier.height(16.dp)) + + // 提示文字 + Text( + text = "未注册的手机号码会自动创建新账号", + fontSize = 12.sp, + color = Color(0xFF999999), + textAlign = TextAlign.Center, + modifier = Modifier.fillMaxWidth() + ) + + Spacer(modifier = Modifier.height(40.dp)) + + // 登录按钮 + Button( + onClick = { onLoginClick(phoneNumber, verificationCode) }, + enabled = phoneNumber.isNotEmpty() && verificationCode.isNotEmpty(), + modifier = Modifier + .fillMaxWidth() + .height(56.dp), + shape = RoundedCornerShape(28.dp), + colors = ButtonDefaults.buttonColors( + containerColor = if (phoneNumber.isNotEmpty() && verificationCode.isNotEmpty()) + Color(0xFF007AFF) else Color(0xFFCCCCCC), + contentColor = Color.White + ) + ) { + Text( + text = "登录", + fontSize = 18.sp, + fontWeight = FontWeight.Medium + ) + } + + Spacer(modifier = Modifier.weight(1f)) + } + } +} + +@Composable +@Preview +fun RegisterScreenPreview() { + RegisterScreen() +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/whitefish/ring/ui/guide/SearchingScreen.kt b/shared/src/commonMain/kotlin/com/whitefish/ring/ui/guide/SearchingScreen.kt new file mode 100644 index 0000000..43b4c7e --- /dev/null +++ b/shared/src/commonMain/kotlin/com/whitefish/ring/ui/guide/SearchingScreen.kt @@ -0,0 +1,166 @@ +package com.whitefish.ring.ui.guide + +import androidx.compose.animation.core.* +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.rotate +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.text.style.TextDecoration +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import org.jetbrains.compose.ui.tooling.preview.Preview + +@Composable +fun SearchTip( + onDeviceNotFoundClick: () -> Unit = {}, + onDeviceFound: () -> Unit = {} +) { + // 旋转动画 + val infiniteTransition = rememberInfiniteTransition() + val rotation by infiniteTransition.animateFloat( + initialValue = 0f, + targetValue = 360f, + animationSpec = infiniteRepeatable( + animation = tween(2000, easing = LinearEasing), + repeatMode = RepeatMode.Restart + ) + ) + + // 模拟搜索过程,5秒后跳转到设备列表 + LaunchedEffect(Unit) { + kotlinx.coroutines.delay(5000) + onDeviceFound() + } + + Box( + modifier = Modifier + .fillMaxSize() + .background(Color(0xFFF5F5F5)) + ) { + Column( + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 24.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Spacer(modifier = Modifier.height(120.dp)) + + // 标题 + Text( + text = "正在搜索设备...", + fontSize = 24.sp, + fontWeight = FontWeight.Medium, + color = Color(0xFF333333), + textAlign = TextAlign.Center, + modifier = Modifier.fillMaxWidth() + ) + + Spacer(modifier = Modifier.height(80.dp)) + + // 搜索动画 + Box( + modifier = Modifier + .size(200.dp) + .background( + Color.White, + RoundedCornerShape(100.dp) + ), + contentAlignment = Alignment.Center + ) { + // 外圆环 - 旋转动画 + Box( + modifier = Modifier + .size(160.dp) + .rotate(rotation) + .background( + Color.Transparent + ), + contentAlignment = Alignment.Center + ) { + // 虚线圆环效果 + repeat(8) { index -> + Box( + modifier = Modifier + .size(8.dp) + .background( + Color(0xFF007AFF), + RoundedCornerShape(4.dp) + ) + .offset( + x = (70 * kotlin.math.cos(index * 45.0 * kotlin.math.PI / 180)).dp, + y = (70 * kotlin.math.sin(index * 45.0 * kotlin.math.PI / 180)).dp + ) + ) + } + } + + // 中心搜索图标 + Box( + modifier = Modifier + .size(80.dp) + .background( + Color(0xFF007AFF), + RoundedCornerShape(40.dp) + ), + contentAlignment = Alignment.Center + ) { + Text( + text = "🔍", + fontSize = 32.sp, + color = Color.White + ) + } + } + + Spacer(modifier = Modifier.height(60.dp)) + + // 进度指示器 + LinearProgressIndicator( + modifier = Modifier + .fillMaxWidth() + .height(4.dp), + color = Color(0xFF007AFF), + trackColor = Color(0xFFE5E5E5) + ) + + Spacer(modifier = Modifier.height(24.dp)) + + // 提示文字 + Text( + text = "请确保戒指在充电状态并且靠近手机", + fontSize = 16.sp, + color = Color(0xFF666666), + textAlign = TextAlign.Center, + modifier = Modifier.fillMaxWidth() + ) + + Spacer(modifier = Modifier.weight(1f)) + + // 找不到设备链接 + TextButton( + onClick = onDeviceNotFoundClick, + modifier = Modifier.padding(bottom = 40.dp) + ) { + Text( + text = "找不到设备?", + fontSize = 16.sp, + color = Color(0xFF007AFF), + textDecoration = TextDecoration.Underline + ) + } + } + } +} + +@Composable +@Preview +fun SearchingScreenPreview() { + SearchTip() +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/whitefish/ring/ui/guide/WearingFingerScreen.kt b/shared/src/commonMain/kotlin/com/whitefish/ring/ui/guide/WearingFingerScreen.kt new file mode 100644 index 0000000..3fe7040 --- /dev/null +++ b/shared/src/commonMain/kotlin/com/whitefish/ring/ui/guide/WearingFingerScreen.kt @@ -0,0 +1,236 @@ +package com.whitefish.ring.ui.guide + +import androidx.compose.foundation.background +import androidx.compose.foundation.border +import androidx.compose.foundation.clickable +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import org.jetbrains.compose.ui.tooling.preview.Preview + +enum class Hand { + LEFT, RIGHT +} + +enum class Finger { + THUMB, INDEX, MIDDLE, RING, PINKY +} + +data class WearingPosition( + val hand: Hand, + val finger: Finger +) + +@Composable +fun WearingFingerScreen( + onNextClick: () -> Unit = {}, + onFingerSelected: (WearingPosition) -> Unit = {} +) { + var selectedPosition by remember { mutableStateOf(null) } + + Box( + modifier = Modifier + .fillMaxSize() + .background(Color(0xFFF5F5F5)) + ) { + Column( + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 24.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Spacer(modifier = Modifier.height(80.dp)) + + // 标题 + Text( + text = "佩戴手指", + fontSize = 24.sp, + fontWeight = FontWeight.Medium, + color = Color(0xFF333333), + textAlign = TextAlign.Center, + modifier = Modifier.fillMaxWidth() + ) + + Spacer(modifier = Modifier.height(16.dp)) + + // 副标题 + Text( + text = "请选择您佩戴Acti戒指的手指", + fontSize = 16.sp, + color = Color(0xFF666666), + textAlign = TextAlign.Center, + modifier = Modifier.fillMaxWidth() + ) + + Spacer(modifier = Modifier.height(80.dp)) + + // 双手选择区域 + Row( + modifier = Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceEvenly + ) { + // 左手 + HandSelector( + hand = Hand.LEFT, + selectedPosition = selectedPosition, + onFingerClick = { finger -> + val position = WearingPosition(Hand.LEFT, finger) + selectedPosition = position + onFingerSelected(position) + } + ) + + // 右手 + HandSelector( + hand = Hand.RIGHT, + selectedPosition = selectedPosition, + onFingerClick = { finger -> + val position = WearingPosition(Hand.RIGHT, finger) + selectedPosition = position + onFingerSelected(position) + } + ) + } + + Spacer(modifier = Modifier.weight(1f)) + + // 下一步按钮 + Button( + onClick = onNextClick, + enabled = selectedPosition != null, + modifier = Modifier + .fillMaxWidth() + .height(56.dp), + shape = RoundedCornerShape(28.dp), + colors = ButtonDefaults.buttonColors( + containerColor = if (selectedPosition != null) Color(0xFF007AFF) else Color(0xFFCCCCCC), + contentColor = Color.White + ) + ) { + Text( + text = "下一步", + fontSize = 18.sp, + fontWeight = FontWeight.Medium + ) + } + + Spacer(modifier = Modifier.height(40.dp)) + } + } +} + +@Composable +private fun HandSelector( + hand: Hand, + selectedPosition: WearingPosition?, + onFingerClick: (Finger) -> Unit +) { + Column( + horizontalAlignment = Alignment.CenterHorizontally + ) { + // 手部图像占位符 + Box( + modifier = Modifier + .size(140.dp, 180.dp) + .background( + Color.White, + RoundedCornerShape(20.dp) + ) + .border( + width = 2.dp, + color = Color(0xFFE5E5E5), + shape = RoundedCornerShape(20.dp) + ), + contentAlignment = Alignment.Center + ) { + // 简化的手部表示 + Column( + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + // 拇指 + FingerButton( + finger = Finger.THUMB, + isSelected = selectedPosition?.hand == hand && selectedPosition.finger == Finger.THUMB, + onClick = { onFingerClick(Finger.THUMB) }, + modifier = Modifier.offset(x = if (hand == Hand.LEFT) 20.dp else (-20).dp) + ) + + Spacer(modifier = Modifier.height(8.dp)) + + // 其他四个手指 + Row( + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + listOf(Finger.INDEX, Finger.MIDDLE, Finger.RING, Finger.PINKY).forEach { finger -> + FingerButton( + finger = finger, + isSelected = selectedPosition?.hand == hand && selectedPosition.finger == finger, + onClick = { onFingerClick(finger) } + ) + } + } + + // 显示戒指图标在选中的手指上 + if (selectedPosition?.hand == hand) { + Text( + text = "💍", + fontSize = 16.sp, + modifier = Modifier.padding(top = 4.dp) + ) + } + } + } + + Spacer(modifier = Modifier.height(16.dp)) + + // 标签 + Text( + text = if (hand == Hand.LEFT) "左手" else "右手", + fontSize = 16.sp, + fontWeight = FontWeight.Medium, + color = Color(0xFF333333) + ) + } +} + +@Composable +private fun FingerButton( + finger: Finger, + isSelected: Boolean, + onClick: () -> Unit, + modifier: Modifier = Modifier +) { + Box( + modifier = modifier + .size(20.dp, 40.dp) + .clip(RoundedCornerShape(10.dp)) + .background( + if (isSelected) Color(0xFF007AFF) else Color(0xFFE5E5E5) + ) + .clickable { onClick() }, + contentAlignment = Alignment.Center + ) { + if (isSelected) { + Text( + text = "💍", + fontSize = 12.sp + ) + } + } +} + +@Composable +@Preview +fun WearingFingerScreenPreview() { + WearingFingerScreen() +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/whitefish/ring/ui/guide/WelcomeScreen.kt b/shared/src/commonMain/kotlin/com/whitefish/ring/ui/guide/WelcomeScreen.kt new file mode 100644 index 0000000..1ed9d5d --- /dev/null +++ b/shared/src/commonMain/kotlin/com/whitefish/ring/ui/guide/WelcomeScreen.kt @@ -0,0 +1,161 @@ +package com.whitefish.ring.ui.guide + +import androidx.compose.foundation.Image +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material3.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import org.jetbrains.compose.ui.tooling.preview.Preview + +@Composable +fun WelcomeScreen( + onStartClick: () -> Unit = {} +) { + var isChecked by remember { mutableStateOf(false) } + + Box( + modifier = Modifier + .fillMaxSize() + .background(Color(0xFFF5F5F5)) + ) { + Column( + modifier = Modifier + .fillMaxSize() + .padding(horizontal = 24.dp), + horizontalAlignment = Alignment.CenterHorizontally + ) { + Spacer(modifier = Modifier.height(120.dp)) + + // 主标题 + Text( + text = "Acti", + fontSize = 48.sp, + fontWeight = FontWeight.Bold, + color = Color(0xFF333333), + textAlign = TextAlign.Center + ) + + Spacer(modifier = Modifier.height(24.dp)) + + // 副标题 + Text( + text = "赋能每一个动作", + fontSize = 18.sp, + fontWeight = FontWeight.Normal, + color = Color(0xFF666666), + textAlign = TextAlign.Center + ) + + Spacer(modifier = Modifier.height(16.dp)) + + // 英文副标题 + Text( + text = "Empower Every Move", + fontSize = 16.sp, + fontWeight = FontWeight.Normal, + color = Color(0xFF999999), + textAlign = TextAlign.Center + ) + + Spacer(modifier = Modifier.weight(1f)) + + // 戒指图片占位符 + Box( + modifier = Modifier + .size(200.dp) + .background( + Color(0xFFE5E5E5), + RoundedCornerShape(16.dp) + ), + contentAlignment = Alignment.Center + ) { + Text( + text = "💍", + fontSize = 80.sp + ) + } + + Spacer(modifier = Modifier.weight(1f)) + + // 协议同意checkbox + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 16.dp), + verticalAlignment = Alignment.CenterVertically + ) { + Checkbox( + checked = isChecked, + onCheckedChange = { isChecked = it }, + colors = CheckboxDefaults.colors( + checkedColor = Color(0xFF007AFF) + ) + ) + + Spacer(modifier = Modifier.width(8.dp)) + + Text( + text = "我已阅读并同意", + fontSize = 14.sp, + color = Color(0xFF666666) + ) + + Text( + text = "《用户协议》", + fontSize = 14.sp, + color = Color(0xFF007AFF) + ) + + Text( + text = "和", + fontSize = 14.sp, + color = Color(0xFF666666) + ) + + Text( + text = "《隐私政策》", + fontSize = 14.sp, + color = Color(0xFF007AFF) + ) + } + + Spacer(modifier = Modifier.height(24.dp)) + + // 立即使用按钮 + Button( + onClick = onStartClick, + enabled = isChecked, + modifier = Modifier + .fillMaxWidth() + .height(56.dp), + shape = RoundedCornerShape(28.dp), + colors = ButtonDefaults.buttonColors( + containerColor = if (isChecked) Color(0xFF007AFF) else Color(0xFFCCCCCC), + contentColor = Color.White + ) + ) { + Text( + text = "立即使用", + fontSize = 18.sp, + fontWeight = FontWeight.Medium + ) + } + + Spacer(modifier = Modifier.height(40.dp)) + } + } +} + +@Composable +@Preview +fun WelcomeScreenPreview() { + WelcomeScreen() +} \ No newline at end of file diff --git a/shared/src/commonMain/kotlin/com/whitefish/ring/ui/home/HomeScreen.kt b/shared/src/commonMain/kotlin/com/whitefish/ring/ui/home/HomeScreen.kt index b4bbbc1..0820a44 100644 --- a/shared/src/commonMain/kotlin/com/whitefish/ring/ui/home/HomeScreen.kt +++ b/shared/src/commonMain/kotlin/com/whitefish/ring/ui/home/HomeScreen.kt @@ -19,6 +19,7 @@ import androidx.compose.ui.unit.sp import androidx.lifecycle.viewmodel.compose.viewModel import com.whitefish.ring.getNavigationBarHeight import com.whitefish.ring.getStatusBarHeight +import com.whitefish.ring.obtainDeviceManager import org.jetbrains.compose.resources.painterResource import org.jetbrains.compose.ui.tooling.preview.Preview import com.whitefish.ring.ui.home.state.StateScreen diff --git a/shared/src/commonMain/kotlin/com/whitefish/ring/ui/home/HomeViewModel.kt b/shared/src/commonMain/kotlin/com/whitefish/ring/ui/home/HomeViewModel.kt index 0455c26..5880e8b 100644 --- a/shared/src/commonMain/kotlin/com/whitefish/ring/ui/home/HomeViewModel.kt +++ b/shared/src/commonMain/kotlin/com/whitefish/ring/ui/home/HomeViewModel.kt @@ -1,9 +1,16 @@ package com.whitefish.ring.ui.home import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.whitefish.ring.device.IDeviceManager +import com.whitefish.ring.obtainDeviceManager +import io.github.aakira.napier.Napier +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch data class HomeUiState( val selectedTab: HomeTab = HomeTab.STATE, @@ -12,6 +19,30 @@ data class HomeUiState( ) class HomeViewModel : ViewModel() { + private val manager = obtainDeviceManager() + init { + collect() + } + + fun collect(){ + viewModelScope.launch { + launch { + manager.blePowerState.collectLatest { + if (it){ + Napier.i { "start scan" } + manager.startScan() + } + } + } + + launch { + manager.deviceList.collectLatest { + Napier.i { "deviceList:${it}" } + } + } + + } + } private val _uiState = MutableStateFlow(HomeUiState()) val uiState: StateFlow = _uiState.asStateFlow() diff --git a/shared/src/iosMain/kotlin/com/whitefish/ring/DeviceManager.kt b/shared/src/iosMain/kotlin/com/whitefish/ring/DeviceManager.kt new file mode 100644 index 0000000..9a96e0b --- /dev/null +++ b/shared/src/iosMain/kotlin/com/whitefish/ring/DeviceManager.kt @@ -0,0 +1,189 @@ +package com.whitefish.ring + +import androidx.compose.ui.util.fastFirstOrNull +import com.whitefish.ring.bean.ui.Device +import com.whitefish.ring.device.IDeviceManager +import com.whitefish.ring.objc.CMD_EXECTE_ERROR_REASON +import com.whitefish.ring.objc.DeviceCenter +import com.whitefish.ring.objc.EXCUTED_CMD +import com.whitefish.ring.objc.FUNCTION_ERROR +import com.whitefish.ring.objc.LTSRingSDK +import com.whitefish.ring.objc.OusideBleDiscovery +import com.whitefish.ring.objc.SRBLeService +import com.whitefish.ring.objc.SRBleDataProtocalProtocol +import com.whitefish.ring.objc.SRBleScanProtocalProtocol +import com.whitefish.ring.objc.SRDeviceInfo +import io.github.aakira.napier.Napier +import kotlinx.cinterop.ExperimentalForeignApi +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.IO +import kotlinx.coroutines.launch +import platform.CoreBluetooth.CBManagerState +import platform.Foundation.NSNumber +import platform.darwin.NSInteger +import platform.darwin.NSObject +import platform.darwin.NSUInteger + +@OptIn(ExperimentalForeignApi::class) +class DeviceManager: IDeviceManager() { + private val manager = DeviceCenter.instance() + + private var iosBleList = arrayListOf() + private val scope = CoroutineScope(Dispatchers.IO) + + // 将delegate对象存储为强引用的成员变量,避免被垃圾回收 + private val scanDelegate = object : NSObject(), SRBleScanProtocalProtocol { + override fun srBleDidConnectPeripheral(service: SRBLeService) { + Napier.i { "srBleDidConnectPeripheral" } + } + + override fun srBleDidDisconnectPeripheral(service: SRBLeService) { + Napier.i { "srBleDidDisconnectPeripheral" } + } + + override fun srBlePowerStateChange(state: CBManagerState) { + Napier.i { "srBlePowerStateChange:${state}" } + if (state.toInt() == 5){ + scope.launch { + blePowerState.emit(true) + } + } + } + + override fun srScanDeviceDidRefresh(perphelArray: List<*>) { + Napier.i { "srScanDeviceDidRefresh:${perphelArray}" } + iosBleList.clear() + val deviceList = perphelArray.map { + val device = it as SRBLeService + iosBleList.add(device) + Device(device.advDataLocalName.toString(),device.macAddress.toString()) + } + _deviceList.value = deviceList + } + } + + private val dataDelegate = object : NSObject(), SRBleDataProtocalProtocol { + override fun srBleDeviceDidReadyForReadAndWrite(service: SRBLeService) { + Napier.i { "srBleDeviceDidReadyForReadAndWrite" } + } + + override fun srBleRealtimeSpo(spo: NSNumber) { + Napier.i { "srBleRealtimeSpo" } + } + + override fun srBleRealtimeHeartRate(hr: NSNumber) { + } + + override fun srBleRealtimeHrv(hrv: NSNumber) { + Napier.i { "srBleRealtimeHrv" } + } + + override fun srBleDeviceBatteryLevel( + batteryLevel: NSUInteger, + IsCharging: Boolean + ) { + Napier.i { "srBleDeviceBatteryLevel" } + } + + override fun srBleSN(sn: String) { + Napier.i { "srBleSN:${sn}" } + } + + override fun srBleDeviceInfo(devInfo: SRDeviceInfo) { + Napier.i { "srBleDeviceInfo:${devInfo}" } + } + + override fun srBleHistorySr03DataWithCurrentCount( + currentCount: NSInteger, + IsComplete: Boolean + ) { + Napier.i { "srBleHistorySr03DataWithCurrentCount:${IsComplete}" } + } + + override fun srBleDeviceRealtimeSteps(steps: NSNumber) { + Napier.i { "srBleDeviceRealtimeSteps:${steps}" } + } + + override fun srBleDeviceRealtimeTemperature(temperature: NSNumber) { + Napier.i { "srBleDeviceRealtimeTemperature:${temperature}" } + } + + override fun srBleCmdExcute( + cmd: EXCUTED_CMD, + Succ: Boolean + ) { + + } + + override fun srBleCmdExcute( + cmd: EXCUTED_CMD, + Succ: Boolean, + Reason: CMD_EXECTE_ERROR_REASON + ) { + } + + override fun srBleHistoryDataCount(count: NSInteger) { + } + + override fun srBleHistoryDataProgress(percent: Float, IsComplete: Boolean) { + } + + override fun srBleHistoryDataTimeout() { + } + + override fun srBleIsbinded(isBinded: Boolean) { + } + + override fun srBleOEMAuthResult(authSucceddful: Boolean) { + bleReadyStateFlow.value = true + Napier.i { "srBleOEMAuthResult" } + } + + override fun srBleFunctionErrorCallBack( + error: FUNCTION_ERROR, + MehthodName: String + ) { + } + } + + init { + initializeManager() + } + + private fun initializeManager() { + Napier.i { "DeviceManager initializing..." } + manager.registWithisCustomBleManage(true) + // 使用成员变量而不是匿名对象 + manager.appScanDelegate = scanDelegate + manager.appDataDelegate = dataDelegate + Napier.i { "DeviceManager delegates set: scan=${scanDelegate}, data=${dataDelegate}" } + } + + // 添加重新初始化方法,在需要时可以调用 + fun reinitialize() { + Napier.i { "DeviceManager reinitializing..." } + initializeManager() + } + + override fun startScan() { + Napier.i { "Starting scan, delegate: ${manager.appScanDelegate}" } + manager.startBleScan() + } + + override fun stopScan() { + manager.stopBleScan() + } + + override fun connect(mac: String) { + iosBleList.fastFirstOrNull { it.macAddress == mac }?.let { + manager.connectDevice(it) + Napier.i { "connect device:${it}" } + } + } + + override fun bind() { + Napier.i { "bind device:${manager.currentDevice()}" } + manager.bindCurrentDevice() + } +} \ No newline at end of file diff --git a/shared/src/iosMain/kotlin/com/whitefish/ring/MainViewController.kt b/shared/src/iosMain/kotlin/com/whitefish/ring/MainViewController.kt index a782d55..a746f84 100644 --- a/shared/src/iosMain/kotlin/com/whitefish/ring/MainViewController.kt +++ b/shared/src/iosMain/kotlin/com/whitefish/ring/MainViewController.kt @@ -4,6 +4,4 @@ import androidx.compose.ui.window.ComposeUIViewController import io.github.aakira.napier.DebugAntilog import io.github.aakira.napier.Napier -fun MainViewController() = ComposeUIViewController { App() }.apply { - Napier.base(DebugAntilog()) -} \ No newline at end of file +fun MainViewController() = ComposeUIViewController { App() } \ No newline at end of file diff --git a/shared/src/iosMain/kotlin/com/whitefish/ring/Platform.ios.kt b/shared/src/iosMain/kotlin/com/whitefish/ring/Platform.ios.kt index 6abfb78..56ed02b 100644 --- a/shared/src/iosMain/kotlin/com/whitefish/ring/Platform.ios.kt +++ b/shared/src/iosMain/kotlin/com/whitefish/ring/Platform.ios.kt @@ -3,6 +3,9 @@ package com.whitefish.ring import androidx.compose.runtime.Composable import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +import com.whitefish.ring.device.IDeviceManager +import io.github.aakira.napier.DebugAntilog +import io.github.aakira.napier.Napier import platform.UIKit.UIDevice class IOSPlatform: Platform { @@ -49,4 +52,16 @@ actual fun getNavigationBarHeight(): Dp { 0.dp } } +} + +private val DeviceInstance: DeviceManager by lazy { + DeviceManager() +} +actual fun obtainDeviceManager(): IDeviceManager { + return DeviceInstance +} + +fun initLogger(){ + Napier.base(DebugAntilog()) + Napier.i { "Logger init success on ios" } } \ No newline at end of file diff --git a/shared/src/main/res/values/string.xml b/shared/src/main/res/values/string.xml new file mode 100644 index 0000000..c1daa14 --- /dev/null +++ b/shared/src/main/res/values/string.xml @@ -0,0 +1,225 @@ + + RingApp + + Acti + 赋能每一个动作\nEmpower Every Move + 立即使用 + 我已阅读井同意《用户隐私协议》和《用户注册协议》 + Hi,\n欢迎来到Acti + 手机号 + 请输入您的手机号 + 验证码 + 请输入验证码 + 未注册的手机号验证后自动注册登录 + 登录 + 连接您的Acti戒指 + 将您的戒指连接到充电器,并继续下一步。请确保您的手机已启用蓝牙功能。 + 下一步 + 正在搜索设备... + 附近设备 + 找不到设备? + 连接失败? + + First Fragment + Second Fragment + Next + Previous + %d 次/分 + 无数据 + + 为了可以扫描蓝牙设备,现在还需要开启位置服务。 + 您未连接戒指 + 提示 + + yyyy年MM月dd日 + yyyy年MM月 + MM月dd日 + yyyy年MM月dd日 + 今天 + 昨天 + + + 静息心率 + 步数 + 心率沉浸 + 呼吸速率 + 血氧饱和度 + 睡眠%s %s效率 + 苏醒/被打扰时间 %s + REM睡眠 %s + 浅度睡眠 %s + 深度睡眠 %s + 零星小睡 %s + %d小时%d分钟 + %d小时 + %d分钟 + 睡眠分析 + 睡眠分析%d + + 睡眠%s %s效率 + 睡眠%s + 睡眠详情 + 睡眠 + 零星小睡 + + 可用设备 + 注意:未绑定的Ring在充电时才会广播,请充电,以便APP可以扫描到。 + 连接中… + 请等待设备连接断开 + 受限模式 + 已启用受限模式,只能恢复出厂设置和自检。 + + + + 其他操作 + 手指温度 + 工厂测试 + 重启 + 恢复出厂设置 + 解綁 + 关机 + 设备信息 + 设备SN + 获取PPG读数(血氧、心率) + 仅心率 + 设置PPG参数 + SpO₂测量间隔 (0, 5~360, 分钟) + SpO₂测量间隔 + 心电图 + 设置心率&体温测量时长 + 测量时长(10 ~ 180,秒) + 提交 + 请输入测量时长! + + + 戒指断开连接,设置参数无法发送。 + + + + 选择 + 查询 + 通过文件管理器APP从本地选择固件文件 + 从服务器查询并下载新的固件到本地 + 您的设备暂未支持从服务器查询新的固件,缺少关键参数【%s】 + 升级 + 在您的设备上未找到文件浏览器应用程序。你想下载一个吗? + 该戒指为无线充电模式,请选择SR09W固件更新。 + 该戒指为NFC充电模式,请选择SR09N固件更新。 + 您的设备固件已经是最新版本:v%s. + 查询到最新的固件版本:v%s,下载中… + \nMD5匹配,正在保存文件到本地… + \nMD5不匹配!请检查。 + \n文件存储路径:%s + \n文件存储出错:%s + 查询到空内容! + 请求失败,code = %d。 + 选择此固件 + + 尺寸%d + 深黑色 + 银色 + 金色 + 玫瑰金 + 金/银混色 + 紫/银混色 + 玫瑰金/银混色 + 充电中 + 放电中 + + OEM验证失败 + 设备序列号空,连接认证失败。 + R1解密并生成R2失败。 + 连接失败,请使用凌拓NexRing智能戒指。 + 断开连接 + + 您的戒指型号不支持【锻炼模式】! + 锻炼 + 锻炼记录 + 锻炼时间 + 时间间隔 + 平均心率 + 最高心率 + 最低心率 + 同步数据中… + 没有可用的心率数据! + 选择一项锻炼 + 步行 + 室内跑步 + 室外跑步 + 室内骑车 + 室外骑车 + 山地自行车 + 游泳 + 添加详情 + 开始 + 已结束 + 进行中 + 提早结束 + 是否提早结束锻炼? + 戒指充电中,请佩戴后操作 + %d 秒 + %d 分钟 + %d 小时 + + 命令执行成功。 + 命令执行失败。 + >命令执行失败。戒指连接时OEM验证未通过。 + >命令执行失败。戒指正在主动测量。 + >命令执行失败。戒指处于锻炼模式。 + >命令执行失败。戒指正在执行APP发起的测量。 + >命令执行失败。参数错误。 + + 使用内置算法 + 输出原始波形 + 采样率 + + 请输入有效值 + + + + 您的戒指型号不支持【心电图测量】! + 开始 + 停止 + 心电图设置 + 时间基准 + 时间基准:%1$s + 增益 + 增益:%1$s + 生成PDF文件 + %d 秒 + 设备端参数设置 + "'记录时间:'yyyy年MM月dd日 HH:mm" + %s, %s,导联I,512赫,Linktop NexRing + "yyyy.MM.dd HH:mm" + "'%s' yyyy-MM-dd HH_mm'.pdf'" + 正在生成PDF文件… + 请先测量一次心电图。 + 心电图PGA增益(单位:V/V) + 您的戒指现在佩戴于 + 左手指 + 右手指 + 将另一只手的手指搭在戒指上,以便形成导联并完成心电测量。 + 心率 %s BPM + 平均心率 %s BPM + 窦性心律 + 房颤 + 低心率 + 高心率 + 不确定 + 记录结果不佳 + 无结果 + + 需要权限 + 需要蓝牙权限来扫描和连接设备。请在设置中授予权限。 + 没有蓝牙权限,应用可能无法正常工作。您可以在系统设置中手动开启权限。 + + OEM 认证失败 + 设备序列号空,OEM 认证失败。 + R1 解密并生成 R2 失败。 + + 您的戒指已开启 OEM 认证,但本 APP 设定的 OEM 字符串似乎与您的戒指所写入的不匹配, + 请检查 Demo 工程的 `res/values/arrays.xml` 的 `oem_array` 中的字符串元素是否覆盖正确? + 修改后请重新编译工程生成新的 APK 文件,安装后重试。 + 如果仍然失败,请联系我们的技术支持。 + + \ No newline at end of file