Browse Source

feat: device bind

AnranYus 4 weeks ago
parent
commit
ff17678b36
  1. 33
      .kotlin/errors/errors-1749093424243.log
  2. BIN
      .kotlin/metadata/kotlinCInteropLibraries/shared-iosArm64Cinterop-cinMain-SXIigQ.klib
  3. 0
      .kotlin/sessions/kotlin-compiler-7621307791481117302.salive
  4. 1
      androidApp/src/main/AndroidManifest.xml
  5. 23
      androidApp/src/main/java/com/whitefish/ring/android/MainActivity.kt
  6. 1
      build.gradle.kts
  7. 8
      gradle/libs.versions.toml
  8. 17
      iosApp/Podfile
  9. 2
      iosApp/Podfile.lock
  10. 2
      iosApp/Pods/Manifest.lock
  11. 231
      iosApp/Pods/Pods.xcodeproj/project.pbxproj
  12. 24
      iosApp/iosApp.xcodeproj/project.pbxproj
  13. 1
      iosApp/iosApp/ContentView.swift
  14. 4
      iosApp/iosApp/Info.plist
  15. 65
      iosApp/iosApp/Libs/LTSRingSDK+Desc.m
  16. 46
      iosApp/iosApp/Libs/Modules/DeviceCenter.m
  17. 324
      iosApp/iosApp/Libs/Modules/OusideBle/OusideBleDiscovery.m
  18. 23
      iosApp/iosApp/PrefixHeader.pch
  19. 1
      iosApp/iosApp/iosApp-Bridging-Header.h
  20. 13
      shared/build.gradle.kts
  21. BIN
      shared/libs/NexRingSDK_v1.4.0_release.aar
  22. BIN
      shared/libs/OemAuth_v2.0.0_release.aar
  23. BIN
      shared/libs/SleepStagingNativeLib_v5_ring_release_v2.5.6.1.aar
  24. 58
      shared/src/androidMain/kotlin/com/whitefish/ring/ActivityLifecycleCb.kt
  25. 31
      shared/src/androidMain/kotlin/com/whitefish/ring/Application.kt
  26. 230
      shared/src/androidMain/kotlin/com/whitefish/ring/DeviceManager.kt
  27. 18
      shared/src/androidMain/kotlin/com/whitefish/ring/HandlerHelper.kt
  28. 112
      shared/src/androidMain/kotlin/com/whitefish/ring/PermissionManager.kt
  29. 12
      shared/src/androidMain/kotlin/com/whitefish/ring/Platform.android.kt
  30. 49
      shared/src/androidMain/kotlin/com/whitefish/ring/Utils.kt
  31. 15
      shared/src/androidMain/kotlin/com/whitefish/ring/bt/BleDevice.kt
  32. 423
      shared/src/androidMain/kotlin/com/whitefish/ring/bt/BleManager.kt
  33. 8
      shared/src/androidMain/kotlin/com/whitefish/ring/bt/OnBleConnectionListener.kt
  34. 10
      shared/src/androidMain/kotlin/com/whitefish/ring/bt/OnBleScanCallback.kt
  35. 12
      shared/src/commonMain/kotlin/com/whitefish/ring/App.kt
  36. 3
      shared/src/commonMain/kotlin/com/whitefish/ring/bean/ui/Device.kt
  37. 30
      shared/src/commonMain/kotlin/com/whitefish/ring/device/IDeviceManager.kt
  38. 144
      shared/src/commonMain/kotlin/com/whitefish/ring/ui/guide/ConnectionGuideScreen.kt
  39. 150
      shared/src/commonMain/kotlin/com/whitefish/ring/ui/guide/DeviceScreen.kt
  40. 64
      shared/src/commonMain/kotlin/com/whitefish/ring/ui/guide/DeviceViewModel.kt
  41. 190
      shared/src/commonMain/kotlin/com/whitefish/ring/ui/guide/DominantHandScreen.kt
  42. 100
      shared/src/commonMain/kotlin/com/whitefish/ring/ui/guide/GuideNavigationScreen.kt
  43. 107
      shared/src/commonMain/kotlin/com/whitefish/ring/ui/guide/GuideScreensPreviews.kt
  44. 174
      shared/src/commonMain/kotlin/com/whitefish/ring/ui/guide/PersonalInfoScreen.kt
  45. 214
      shared/src/commonMain/kotlin/com/whitefish/ring/ui/guide/RegisterScreen.kt
  46. 166
      shared/src/commonMain/kotlin/com/whitefish/ring/ui/guide/SearchingScreen.kt
  47. 236
      shared/src/commonMain/kotlin/com/whitefish/ring/ui/guide/WearingFingerScreen.kt
  48. 161
      shared/src/commonMain/kotlin/com/whitefish/ring/ui/guide/WelcomeScreen.kt
  49. 1
      shared/src/commonMain/kotlin/com/whitefish/ring/ui/home/HomeScreen.kt
  50. 31
      shared/src/commonMain/kotlin/com/whitefish/ring/ui/home/HomeViewModel.kt
  51. 176
      shared/src/iosMain/kotlin/com/whitefish/ring/DeviceManager.kt
  52. 4
      shared/src/iosMain/kotlin/com/whitefish/ring/MainViewController.kt
  53. 1
      shared/src/iosMain/kotlin/com/whitefish/ring/Platform.ios.kt
  54. 225
      shared/src/main/res/values/string.xml

33
.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.<init>(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)

BIN
.kotlin/metadata/kotlinCInteropLibraries/shared-iosArm64Cinterop-cinMain-SXIigQ.klib

Binary file not shown.

0
.kotlin/sessions/kotlin-compiler-7621307791481117302.salive

1
androidApp/src/main/AndroidManifest.xml

@ -27,6 +27,7 @@
<application
android:allowBackup="false"
android:supportsRtl="true"
android:name="com.whitefish.ring.Application"
android:theme="@style/AppTheme">
<activity
android:name=".MainActivity"

23
androidApp/src/main/java/com/whitefish/ring/android/MainActivity.kt

@ -3,19 +3,30 @@ package com.whitefish.ring.android
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.activity.result.contract.ActivityResultContracts
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import com.whitefish.ring.AndroidPlatform
import com.whitefish.ring.App
import com.whitefish.ring.Greeting
import com.whitefish.ring.Application
import com.whitefish.ring.DeviceManager
import com.whitefish.ring.PermissionManager
import com.whitefish.ring.obtainDeviceManager
class MainActivity : ComponentActivity() {
private var permissionChecker =
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) {
PermissionManager.checkPermission(this)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
PermissionManager.permissionChecker = permissionChecker
PermissionManager.checkPermission(this)
setContent {
App()
}
}
}

1
build.gradle.kts

@ -7,4 +7,5 @@ plugins {
alias(libs.plugins.kotlinCocoapods).apply(false)
alias(libs.plugins.compose.compiler).apply(false)
alias(libs.plugins.composeMultiplatform) apply false
id("com.google.devtools.ksp") version "2.1.20-1.0.32" apply false
}

8
gradle/libs.versions.toml

@ -7,6 +7,9 @@ androidx-activityCompose = "1.8.0"
composeMultiplatform = "1.8.1"
androidx-lifecycle = "2.9.0"
vico = "2.1.3"
androidDatabaseSqlcipher = "4.5.4"
roomKtx = "2.6.1"
activityKtx = "1.10.1"
[libraries]
kotlin-test = { module = "org.jetbrains.kotlin:kotlin-test", version.ref = "kotlin" }
@ -20,6 +23,11 @@ androidx-lifecycle-viewmodel = { module = "org.jetbrains.androidx.lifecycle:life
lifecycle-viewmodel-compose = { module = "org.jetbrains.androidx.lifecycle:lifecycle-viewmodel-compose", version.ref = "androidx-lifecycle" }
androidx-lifecycle-runtimeCompose = { module = "org.jetbrains.androidx.lifecycle:lifecycle-runtime-compose", version.ref = "androidx-lifecycle" }
vico-multiplatform = { group = "com.patrykandpatrick.vico", name = "multiplatform", version.ref = "vico" }
android-database-sqlcipher = { module = "net.zetetic:android-database-sqlcipher", version.ref = "androidDatabaseSqlcipher" }
androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "roomKtx" }
androidx-room-ktx = { module = "androidx.room:room-ktx", version.ref = "roomKtx" }
androidx-room-runtime = { module = "androidx.room:room-runtime", version.ref = "roomKtx" }
androidx-activity-ktx = { group = "androidx.activity", name = "activity-ktx", version.ref = "activityKtx" }
[plugins]
androidApplication = { id = "com.android.application", version.ref = "agp" }

17
iosApp/Podfile

@ -15,18 +15,11 @@ target 'iosApp' do
pod 'DateTools'
pod 'MJExtension', '~> 3.4.1'
pod 'FMDB'
# 添加post_install脚本来配置静态库链接
post_install do |installer|
installer.pods_project.targets.each do |target|
target.build_configurations.each do |config|
if target.name == 'iosApp'
config.build_settings['OTHER_LDFLAGS'] ||= []
config.build_settings['OTHER_LDFLAGS'] << '-lRingSDK_2.0.2'
config.build_settings['LIBRARY_SEARCH_PATHS'] ||= []
config.build_settings['LIBRARY_SEARCH_PATHS'] << '$(PROJECT_DIR)/iosApp/Libs'
end
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
end

2
iosApp/Podfile.lock

@ -375,6 +375,6 @@ SPEC CHECKSUMS:
Toast: 1f5ea13423a1e6674c4abdac5be53587ae481c4e
YYKit: 7cda43304a8dc3696c449041e2cb3107b4e236e7
PODFILE CHECKSUM: 6c8b3a4cbbcb499c48be7819e3fe2546a0a463ba
PODFILE CHECKSUM: 4a1ed0dd46b1d5bb4c5e90ddd23567651f3f53c4
COCOAPODS: 1.16.2

2
iosApp/Pods/Manifest.lock

@ -375,6 +375,6 @@ SPEC CHECKSUMS:
Toast: 1f5ea13423a1e6674c4abdac5be53587ae481c4e
YYKit: 7cda43304a8dc3696c449041e2cb3107b4e236e7
PODFILE CHECKSUM: 6c8b3a4cbbcb499c48be7819e3fe2546a0a463ba
PODFILE CHECKSUM: 4a1ed0dd46b1d5bb4c5e90ddd23567651f3f53c4
COCOAPODS: 1.16.2

231
iosApp/Pods/Pods.xcodeproj/project.pbxproj

@ -16,7 +16,6 @@
dependencies = (
);
name = shared;
productName = shared;
};
/* End PBXAggregateTarget section */
@ -33,7 +32,7 @@
02DD2667E8A91C38C53B4BD4334FEB1A /* QMUIPopupMenuItem.h in Headers */ = {isa = PBXBuildFile; fileRef = A445C85678E48F427EAB92744CE46240 /* QMUIPopupMenuItem.h */; settings = {ATTRIBUTES = (Public, ); }; };
02FC6119083751FFF17F4240C103B0D2 /* QMUIMultipleDelegates.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D2FE254FA20B41D815CD27CD996313B /* QMUIMultipleDelegates.m */; };
036069ADB7ED599B72ABA0B554DE988C /* UITabBar+QMUI.h in Headers */ = {isa = PBXBuildFile; fileRef = 8B3DE9C0679562961726358E30E0CF4D /* UITabBar+QMUI.h */; settings = {ATTRIBUTES = (Public, ); }; };
03F2428C9FE1AF75B287A39D46F646FA /* FMDB_Privacy.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 148D9AC15C4A9777E5ACBB46C03FE218 /* FMDB_Privacy.bundle */; };
03F2428C9FE1AF75B287A39D46F646FA /* FMDB-FMDB_Privacy in Resources */ = {isa = PBXBuildFile; fileRef = 148D9AC15C4A9777E5ACBB46C03FE218 /* FMDB-FMDB_Privacy */; };
0423F148FF3BE83F1C768636D238EA31 /* UIGestureRecognizer+YYAdd.h in Headers */ = {isa = PBXBuildFile; fileRef = 5E9C5BFBE54D1921986264CF1C25BF75 /* UIGestureRecognizer+YYAdd.h */; settings = {ATTRIBUTES = (Public, ); }; };
043BE2D1F296D7B1DDFC338A17483F9E /* UIBlurEffect+QMUI.m in Sources */ = {isa = PBXBuildFile; fileRef = 1BBB7BCFB162BF6A2493B48534E54217 /* UIBlurEffect+QMUI.m */; };
0553071F3990D0A143B6E7819523F1FD /* QMUIDisplayLinkAnimation.m in Sources */ = {isa = PBXBuildFile; fileRef = FE0B5A61460CD24E4C38E1A728B29E1C /* QMUIDisplayLinkAnimation.m */; };
@ -162,7 +161,7 @@
32278CF2E1574E7C6286DF5DEFFDF4B1 /* NSArray+YYAdd.h in Headers */ = {isa = PBXBuildFile; fileRef = 26936DFB369958C0639E041CA3CBB850 /* NSArray+YYAdd.h */; settings = {ATTRIBUTES = (Public, ); }; };
325CA20B9271F3E008234E1518B79061 /* MJRefresh-umbrella.h in Headers */ = {isa = PBXBuildFile; fileRef = 20CDAF1CB0DEA182EAEE0D1FCACD1753 /* MJRefresh-umbrella.h */; settings = {ATTRIBUTES = (Public, ); }; };
326E0584CC15454A4D382F12557A9E99 /* QMUIAssetsManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 0EF88EAB035DD06CAFF5935D5765A5C7 /* QMUIAssetsManager.m */; };
327BA3DDA513422E632D3DA4A8FC60EC /* MJRefresh.Privacy.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 7E3097CFEFDA621E9FB0E62009FF87FC /* MJRefresh.Privacy.bundle */; };
327BA3DDA513422E632D3DA4A8FC60EC /* MJRefresh-MJRefresh.Privacy in Resources */ = {isa = PBXBuildFile; fileRef = 7E3097CFEFDA621E9FB0E62009FF87FC /* MJRefresh-MJRefresh.Privacy */; };
32ED999775835BA5BCD74807860494CD /* QMUIPopupMenuItemViewProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = 34E616BC50DCC2AEC75F14AA7258B164 /* QMUIPopupMenuItemViewProtocol.h */; settings = {ATTRIBUTES = (Public, ); }; };
337CF76F666E98CADF9C68E00197AD6E /* QMUIKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 1C85EE5A2BC00B81A1B7CB802F149A70 /* QMUIKit.h */; settings = {ATTRIBUTES = (Public, ); }; };
33C42E79B137F94B3B149F264FB66DDE /* NSURL+QMUI.h in Headers */ = {isa = PBXBuildFile; fileRef = 1FFBCC31B267852E2AD3B92F3C1402F2 /* NSURL+QMUI.h */; settings = {ATTRIBUTES = (Public, ); }; };
@ -308,7 +307,7 @@
615C375B2B90EC501C06AC95C4166364 /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B9F955BC50584733694713C914F62CB7 /* MobileCoreServices.framework */; };
616439C72D917B6FF3998D29856C145A /* UINavigationController+QMUI.m in Sources */ = {isa = PBXBuildFile; fileRef = F39A09811531FB7C620856196C2835D0 /* UINavigationController+QMUI.m */; };
61857C821395B868C65A8FFE4DA1B4E3 /* MJExtension.h in Headers */ = {isa = PBXBuildFile; fileRef = E5F341C5EB9D1F0917CAB1980CAFAB9B /* MJExtension.h */; settings = {ATTRIBUTES = (Public, ); }; };
62CFA1DA1F44515EBEFAC8BBDCAFEA86 /* QMUIKit.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 77DEF480928809EA82047E24B3C57BA7 /* QMUIKit.bundle */; };
62CFA1DA1F44515EBEFAC8BBDCAFEA86 /* QMUIKit-QMUIKit in Resources */ = {isa = PBXBuildFile; fileRef = 77DEF480928809EA82047E24B3C57BA7 /* QMUIKit-QMUIKit */; };
6329CC6F7AB03B6D607686F7801639E7 /* YYThreadSafeDictionary.h in Headers */ = {isa = PBXBuildFile; fileRef = E8FCEBB74589811CC98A0038E721E5A2 /* YYThreadSafeDictionary.h */; settings = {ATTRIBUTES = (Public, ); }; };
634C47C0C99AED159B83DBF3DE7592F3 /* Toast.h in Headers */ = {isa = PBXBuildFile; fileRef = 9F56629659E56BEEABFC6C0DC292D68D /* Toast.h */; settings = {ATTRIBUTES = (Public, ); }; };
63D8A88D36948E62F745594854E0677F /* FMDB.h in Headers */ = {isa = PBXBuildFile; fileRef = C4B65E889ABBBBCF67A6F75E33F4E929 /* FMDB.h */; settings = {ATTRIBUTES = (Public, ); }; };
@ -360,7 +359,7 @@
7777DCBDDDD09DF78F650491FA9F029C /* QMUISheetPresentationSupports.h in Headers */ = {isa = PBXBuildFile; fileRef = F117A87C7FBEFEFE59FA67B7C2169487 /* QMUISheetPresentationSupports.h */; settings = {ATTRIBUTES = (Public, ); }; };
780E0EF90C74434AF53C49C3594357C2 /* JTCalendarDayView.h in Headers */ = {isa = PBXBuildFile; fileRef = 1486A22660C5EA362429493C82D5A21B /* JTCalendarDayView.h */; settings = {ATTRIBUTES = (Public, ); }; };
784908F07EC38CCAF4704A2FD06C4713 /* NSDictionary+YYAdd.h in Headers */ = {isa = PBXBuildFile; fileRef = A57F94A84D2B5A9F1A86301DA551CE81 /* NSDictionary+YYAdd.h */; settings = {ATTRIBUTES = (Public, ); }; };
7873F2F89CD0A435FAB776BC27BFB56A /* MJExtension.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 43EAAD2AB7E6B407E80E95F643F93D22 /* MJExtension.bundle */; };
7873F2F89CD0A435FAB776BC27BFB56A /* MJExtension-MJExtension in Resources */ = {isa = PBXBuildFile; fileRef = 43EAAD2AB7E6B407E80E95F643F93D22 /* MJExtension-MJExtension */; };
7902D28FC9EF5AFEB452F508C7F266B1 /* MJRefreshAutoNormalFooter.h in Headers */ = {isa = PBXBuildFile; fileRef = 9D1E8A0142331D3DF5CA8AA97FEC37C0 /* MJRefreshAutoNormalFooter.h */; settings = {ATTRIBUTES = (Public, ); }; };
7929CFE2870362624707CCEC764C20C7 /* QMUIWindowSizeMonitor.h in Headers */ = {isa = PBXBuildFile; fileRef = 70834183A5A494976C7DE256619019AF /* QMUIWindowSizeMonitor.h */; settings = {ATTRIBUTES = (Public, ); }; };
7989A6E79BFA78440C39F568D972305C /* MJRefresh.h in Headers */ = {isa = PBXBuildFile; fileRef = 3CDBED5F1B71979C8463CFEAB8C87D23 /* MJRefresh.h */; settings = {ATTRIBUTES = (Public, ); }; };
@ -635,10 +634,10 @@
D3F2C605B373F0902E2AB7F00545662E /* NSAttributedString+QMUI.h in Headers */ = {isa = PBXBuildFile; fileRef = 30D4A5765231AC1E7F4B145A7521BE61 /* NSAttributedString+QMUI.h */; settings = {ATTRIBUTES = (Public, ); }; };
D40D76BF0FF793CDA8FA9EFB67E7AE67 /* QMUIAsset.m in Sources */ = {isa = PBXBuildFile; fileRef = 88925A4E02D0802B4BC4424B98EB7DFC /* QMUIAsset.m */; };
D4C4BB5029ED675E59D58F2FAAAF454A /* YYImage.h in Headers */ = {isa = PBXBuildFile; fileRef = 8F12F54D282E372A275B71743E3F9F3D /* YYImage.h */; settings = {ATTRIBUTES = (Public, ); }; };
D4D4E8BD5F155B6C32FE15226FD3315C /* QMUIResources.bundle in Resources */ = {isa = PBXBuildFile; fileRef = FFDF743B2D7D655F7364FF1154FFEDE6 /* QMUIResources.bundle */; };
D4D4E8BD5F155B6C32FE15226FD3315C /* QMUIKit-QMUIResources in Resources */ = {isa = PBXBuildFile; fileRef = FFDF743B2D7D655F7364FF1154FFEDE6 /* QMUIKit-QMUIResources */; };
D5CFBD8B611789FC080D35D91127053F /* UIPasteboard+YYText.h in Headers */ = {isa = PBXBuildFile; fileRef = E11E78C23F020213B88F74354507BC71 /* UIPasteboard+YYText.h */; settings = {ATTRIBUTES = (Public, ); }; };
D606858436460D12F75B4B33F005EEA8 /* QMUIAssetsManager.h in Headers */ = {isa = PBXBuildFile; fileRef = AD7543DFBAEB5709781B696F48132044 /* QMUIAssetsManager.h */; settings = {ATTRIBUTES = (Public, ); }; };
D60FD96D6C7D9442E6A71E410301B3E2 /* Toast.bundle in Resources */ = {isa = PBXBuildFile; fileRef = F6A5F09CA59AF20B5A450FA1B72ECFB5 /* Toast.bundle */; };
D60FD96D6C7D9442E6A71E410301B3E2 /* Toast-Toast in Resources */ = {isa = PBXBuildFile; fileRef = F6A5F09CA59AF20B5A450FA1B72ECFB5 /* Toast-Toast */; };
D63E3BDFA1F026C99920F7EA132C533C /* QMUISheetPresentationSupports.m in Sources */ = {isa = PBXBuildFile; fileRef = E02E057999FC194ADFF5BD09E510A59C /* QMUISheetPresentationSupports.m */; };
D663837F4347AF58660EE6F7FD426ECE /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 52F6FA78ACC249DBB13C32B1446EA2E2 /* Foundation.framework */; };
D68875F0808F6E211E36E68591E30299 /* YYTextLayout.h in Headers */ = {isa = PBXBuildFile; fileRef = 509530A0C1B6312BF45926B12EDA08E7 /* YYTextLayout.h */; settings = {ATTRIBUTES = (Public, ); }; };
@ -930,7 +929,7 @@
144A00D8F331D7514F95182996BD0F5A /* CoreImage.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreImage.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS18.0.sdk/System/Library/Frameworks/CoreImage.framework; sourceTree = DEVELOPER_DIR; };
146A0435E39CB502277E47ABE2A2D502 /* YYWebImageOperation.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = YYWebImageOperation.m; path = YYKit/Image/YYWebImageOperation.m; sourceTree = "<group>"; };
1486A22660C5EA362429493C82D5A21B /* JTCalendarDayView.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = JTCalendarDayView.h; path = JTCalendar/Views/JTCalendarDayView.h; sourceTree = "<group>"; };
148D9AC15C4A9777E5ACBB46C03FE218 /* FMDB_Privacy.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FMDB_Privacy.bundle; sourceTree = BUILT_PRODUCTS_DIR; };
148D9AC15C4A9777E5ACBB46C03FE218 /* FMDB-FMDB_Privacy */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; name = "FMDB-FMDB_Privacy"; path = FMDB_Privacy.bundle; sourceTree = BUILT_PRODUCTS_DIR; };
1538F878B1B88F08D3E0B10CBE652804 /* QMUIThemeManager.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = QMUIThemeManager.h; path = QMUIKit/QMUIComponents/QMUITheme/QMUIThemeManager.h; sourceTree = "<group>"; };
155BD2EBB35A268BE50E5D16382E10CF /* UIBarItem+QMUIBadge.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "UIBarItem+QMUIBadge.m"; path = "QMUIKit/QMUIComponents/QMUIBadge/UIBarItem+QMUIBadge.m"; sourceTree = "<group>"; };
156999C714704B5CEC2FA8463D3AA837 /* UIVisualEffectView+QMUI.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "UIVisualEffectView+QMUI.m"; path = "QMUIKit/UIKitExtensions/UIVisualEffectView+QMUI.m"; sourceTree = "<group>"; };
@ -952,7 +951,7 @@
1B55B628750B4DA149C65E6C804055EE /* QMUINavigationBarScrollingSnapAnimator.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = QMUINavigationBarScrollingSnapAnimator.m; path = QMUIKit/QMUIComponents/QMUIScrollAnimator/QMUINavigationBarScrollingSnapAnimator.m; sourceTree = "<group>"; };
1BA4EE6FE0E42F8DA5BCE44A688D4021 /* UIViewController+QMUI.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UIViewController+QMUI.h"; path = "QMUIKit/UIKitExtensions/UIViewController+QMUI.h"; sourceTree = "<group>"; };
1BBB7BCFB162BF6A2493B48534E54217 /* UIBlurEffect+QMUI.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "UIBlurEffect+QMUI.m"; path = "QMUIKit/UIKitExtensions/UIBlurEffect+QMUI.m"; sourceTree = "<group>"; };
1BF46B4DF5FE6C6555418BB5DB0D22CE /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xml; name = PrivacyInfo.xcprivacy; path = MJRefresh/PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
1BF46B4DF5FE6C6555418BB5DB0D22CE /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; name = PrivacyInfo.xcprivacy; path = MJRefresh/PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
1C5B0ACBC0062108B17D261A2A00F7A4 /* QMUITips.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = QMUITips.h; path = QMUIKit/QMUIComponents/QMUITips.h; sourceTree = "<group>"; };
1C85EE5A2BC00B81A1B7CB802F149A70 /* QMUIKit.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = QMUIKit.h; path = QMUIKit/QMUIKit.h; sourceTree = "<group>"; };
1CB507858B4CDF0C15A6DB438078A24F /* UIImage+YYAdd.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UIImage+YYAdd.h"; path = "YYKit/Base/UIKit/UIImage+YYAdd.h"; sourceTree = "<group>"; };
@ -968,7 +967,7 @@
1F625FE003C7210B84C3183786251E4F /* NSKeyedUnarchiver+YYAdd.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSKeyedUnarchiver+YYAdd.m"; path = "YYKit/Base/Foundation/NSKeyedUnarchiver+YYAdd.m"; sourceTree = "<group>"; };
1FC00E30D2DF1107D7B123F9753645CE /* NSParagraphStyle+YYText.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "NSParagraphStyle+YYText.h"; path = "YYKit/Text/String/NSParagraphStyle+YYText.h"; sourceTree = "<group>"; };
1FFBCC31B267852E2AD3B92F3C1402F2 /* NSURL+QMUI.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "NSURL+QMUI.h"; path = "QMUIKit/UIKitExtensions/NSURL+QMUI.h"; sourceTree = "<group>"; };
1FFED36A657123030ABB700256D73F15 /* Masonry.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Masonry.framework; sourceTree = BUILT_PRODUCTS_DIR; };
1FFED36A657123030ABB700256D73F15 /* Masonry */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = Masonry; path = Masonry.framework; sourceTree = BUILT_PRODUCTS_DIR; };
203D7A8266F5D1948598F9634266001B /* NSString+QMUI.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "NSString+QMUI.h"; path = "QMUIKit/UIKitExtensions/NSString+QMUI.h"; sourceTree = "<group>"; };
20CDAF1CB0DEA182EAEE0D1FCACD1753 /* MJRefresh-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "MJRefresh-umbrella.h"; sourceTree = "<group>"; };
210CE7656DBF9E5DEB549A11738B6860 /* MJRefresh.bundle */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = "wrapper.plug-in"; name = MJRefresh.bundle; path = MJRefresh/MJRefresh.bundle; sourceTree = "<group>"; };
@ -1002,7 +1001,7 @@
2A1A3F3F9A5AA7D840B285E28A248B0A /* QMUITableView.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = QMUITableView.h; path = QMUIKit/QMUIComponents/QMUITableView.h; sourceTree = "<group>"; };
2A556F85B44157EE6E41E0CF4E31D7AB /* FMResultSet.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FMResultSet.h; path = src/fmdb/FMResultSet.h; sourceTree = "<group>"; };
2A6E56A5F1295AB6A110D6C498B90369 /* FMDB-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "FMDB-prefix.pch"; sourceTree = "<group>"; };
2B276B0A79173A1D6E83C9B4FB9A4A57 /* MJExtension.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = MJExtension.framework; sourceTree = BUILT_PRODUCTS_DIR; };
2B276B0A79173A1D6E83C9B4FB9A4A57 /* MJExtension */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = MJExtension; path = MJExtension.framework; sourceTree = BUILT_PRODUCTS_DIR; };
2B5DADD3C70404C695F2048EFB9519E9 /* Masonry.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = Masonry.debug.xcconfig; sourceTree = "<group>"; };
2B8C909CE5E7C512D6F7272FA5C056EB /* NSCharacterSet+QMUI.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "NSCharacterSet+QMUI.h"; path = "QMUIKit/UIKitExtensions/NSCharacterSet+QMUI.h"; sourceTree = "<group>"; };
2BAE7CE9D130CC9182E8DADCF821BB75 /* UINavigationItem+QMUI.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UINavigationItem+QMUI.h"; path = "QMUIKit/UIKitExtensions/UINavigationItem+QMUI.h"; sourceTree = "<group>"; };
@ -1076,7 +1075,7 @@
43880A90B2EB6BD442270F23831D8827 /* MASViewAttribute.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = MASViewAttribute.m; path = Masonry/MASViewAttribute.m; sourceTree = "<group>"; };
439B05E8444163695926633CD77A9B8B /* UILabel+QMUI.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UILabel+QMUI.h"; path = "QMUIKit/UIKitExtensions/UILabel+QMUI.h"; sourceTree = "<group>"; };
43DE542563498D897EAC83BECC760207 /* UIView+Toast.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "UIView+Toast.m"; path = "Toast/UIView+Toast.m"; sourceTree = "<group>"; };
43EAAD2AB7E6B407E80E95F643F93D22 /* MJExtension.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MJExtension.bundle; sourceTree = BUILT_PRODUCTS_DIR; };
43EAAD2AB7E6B407E80E95F643F93D22 /* MJExtension-MJExtension */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; name = "MJExtension-MJExtension"; path = MJExtension.bundle; sourceTree = BUILT_PRODUCTS_DIR; };
44BAE1FBF34F0B7C012B8A7CDEC17793 /* MBProgressHUD.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = MBProgressHUD.modulemap; sourceTree = "<group>"; };
44CE09DAE6F57AF77FD209344243F791 /* NSObject+MJKeyValue.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSObject+MJKeyValue.m"; path = "MJExtension/NSObject+MJKeyValue.m"; sourceTree = "<group>"; };
45322EEC3B916922730EE5121AD496DC /* MJRefresh.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = MJRefresh.debug.xcconfig; sourceTree = "<group>"; };
@ -1101,7 +1100,7 @@
4AD1803C1C0940757571408AF4621AA2 /* QMUIEasings.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = QMUIEasings.h; path = QMUIKit/QMUIComponents/QMUIAnimation/QMUIEasings.h; sourceTree = "<group>"; };
4AD7C2DC034DB543E9D24CD093F10210 /* NSObject+YYAddForARC.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSObject+YYAddForARC.m"; path = "YYKit/Base/Foundation/NSObject+YYAddForARC.m"; sourceTree = "<group>"; };
4AE8ECADEE4F9BE84B92B065692843F8 /* UIMenuController+QMUI.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UIMenuController+QMUI.h"; path = "QMUIKit/UIKitExtensions/UIMenuController+QMUI.h"; sourceTree = "<group>"; };
4AF171581392AD234F23BE913F0C22FE /* DateTools.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = DateTools.framework; sourceTree = BUILT_PRODUCTS_DIR; };
4AF171581392AD234F23BE913F0C22FE /* DateTools */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = DateTools; path = DateTools.framework; sourceTree = BUILT_PRODUCTS_DIR; };
4B192178A695EE4EC2CC5B5AD3E0D06D /* NSNotificationCenter+YYAdd.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSNotificationCenter+YYAdd.m"; path = "YYKit/Base/Foundation/NSNotificationCenter+YYAdd.m"; sourceTree = "<group>"; };
4BF8167F1F16B53D0D36DA6486C24AB5 /* UIView+QMUITheme.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "UIView+QMUITheme.m"; path = "QMUIKit/QMUIComponents/QMUITheme/UIView+QMUITheme.m"; sourceTree = "<group>"; };
4C8567BC18809B231D1174CCA61DEE84 /* JTCalendarWeek.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = JTCalendarWeek.h; path = JTCalendar/Protocols/JTCalendarWeek.h; sourceTree = "<group>"; };
@ -1142,7 +1141,7 @@
5522334BCBE27DD03CEDF79812364DB8 /* YYTextEffectWindow.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = YYTextEffectWindow.m; path = YYKit/Text/Component/YYTextEffectWindow.m; sourceTree = "<group>"; };
5577FE954794383A429608BE236F2105 /* UIView+QMUITheme.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UIView+QMUITheme.h"; path = "QMUIKit/QMUIComponents/QMUITheme/UIView+QMUITheme.h"; sourceTree = "<group>"; };
55ABB06C8A1800962A74E007E7733796 /* Pods-iosApp-frameworks.sh */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.script.sh; path = "Pods-iosApp-frameworks.sh"; sourceTree = "<group>"; };
55E0AFD333353D71ACC2207149E879D6 /* Toast.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Toast.framework; sourceTree = BUILT_PRODUCTS_DIR; };
55E0AFD333353D71ACC2207149E879D6 /* Toast */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = Toast; path = Toast.framework; sourceTree = BUILT_PRODUCTS_DIR; };
560DBC31EF8D71C0CE5ACE67B84F7467 /* ViewController+MASAdditions.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "ViewController+MASAdditions.m"; path = "Masonry/ViewController+MASAdditions.m"; sourceTree = "<group>"; };
561BE7E02597857878BBD6393DB888B5 /* QMUICellSizeKeyCache.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = QMUICellSizeKeyCache.m; path = QMUIKit/QMUIComponents/QMUICellSizeKeyCache/QMUICellSizeKeyCache.m; sourceTree = "<group>"; };
56E257C81857ACA18812DE13B487E283 /* UIImage+QMUITheme.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "UIImage+QMUITheme.m"; path = "QMUIKit/QMUIComponents/QMUITheme/UIImage+QMUITheme.m"; sourceTree = "<group>"; };
@ -1236,7 +1235,7 @@
7717F2669547DFACA2E8350992F2C5C4 /* QMUIStaticTableViewCellDataSource.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = QMUIStaticTableViewCellDataSource.m; path = QMUIKit/QMUIComponents/StaticTableView/QMUIStaticTableViewCellDataSource.m; sourceTree = "<group>"; };
77A31BF82F3B213410AA24EBC69F9864 /* DTTimePeriodChain.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = DTTimePeriodChain.m; path = DateTools/DateTools/DTTimePeriodChain.m; sourceTree = "<group>"; };
77DC5902FD0E09544E689A46772929E9 /* QMUITabBarViewController.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = QMUITabBarViewController.m; path = QMUIKit/QMUIMainFrame/QMUITabBarViewController.m; sourceTree = "<group>"; };
77DEF480928809EA82047E24B3C57BA7 /* QMUIKit.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = QMUIKit.bundle; sourceTree = BUILT_PRODUCTS_DIR; };
77DEF480928809EA82047E24B3C57BA7 /* QMUIKit-QMUIKit */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; name = "QMUIKit-QMUIKit"; path = QMUIKit.bundle; sourceTree = BUILT_PRODUCTS_DIR; };
79F980119DEEB9C4D92E87FE7796D2C4 /* UIBezierPath+QMUI.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "UIBezierPath+QMUI.m"; path = "QMUIKit/UIKitExtensions/UIBezierPath+QMUI.m"; sourceTree = "<group>"; };
7A2E08F52007C2164856EAD4FFA4BD7C /* ResourceBundle-QMUIKit-QMUIKit-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "ResourceBundle-QMUIKit-QMUIKit-Info.plist"; sourceTree = "<group>"; };
7B1A02173FD541731473196E24567A85 /* YYKitMacro.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = YYKitMacro.h; path = YYKit/Base/YYKitMacro.h; sourceTree = "<group>"; };
@ -1248,7 +1247,7 @@
7D722EC0B7C39B80EC104C18DD77B22D /* YYKit.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = YYKit.modulemap; sourceTree = "<group>"; };
7DC03FC27C6A637A4897F2F2F113F50F /* QMUIImagePickerPreviewViewController.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = QMUIImagePickerPreviewViewController.m; path = QMUIKit/QMUIComponents/ImagePickerLibrary/QMUIImagePickerPreviewViewController.m; sourceTree = "<group>"; };
7E0AE8C46252EC77CD69CCA94F8EAA10 /* QMUISegmentedControl.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = QMUISegmentedControl.m; path = QMUIKit/QMUIComponents/QMUISegmentedControl.m; sourceTree = "<group>"; };
7E3097CFEFDA621E9FB0E62009FF87FC /* MJRefresh.Privacy.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = MJRefresh.Privacy.bundle; sourceTree = BUILT_PRODUCTS_DIR; };
7E3097CFEFDA621E9FB0E62009FF87FC /* MJRefresh-MJRefresh.Privacy */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; name = "MJRefresh-MJRefresh.Privacy"; path = MJRefresh.Privacy.bundle; sourceTree = BUILT_PRODUCTS_DIR; };
7E5DA53E5B7691BE49BDF52955794E19 /* UIImageView+YYWebImage.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UIImageView+YYWebImage.h"; path = "YYKit/Image/Categories/UIImageView+YYWebImage.h"; sourceTree = "<group>"; };
7E6DFB04A0486C105583FCB120FC7F5E /* QMUIConsole.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = QMUIConsole.h; path = QMUIKit/QMUIComponents/QMUIConsole/QMUIConsole.h; sourceTree = "<group>"; };
7E9C2CDA618DA37BCE638643ADBCFCCC /* NSObject+QMUI.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSObject+QMUI.m"; path = "QMUIKit/UIKitExtensions/NSObject+QMUI.m"; sourceTree = "<group>"; };
@ -1272,7 +1271,7 @@
83B8D4168ED903D88916BCD33A4BD3DF /* MJRefreshConfig.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = MJRefreshConfig.m; path = MJRefresh/MJRefreshConfig.m; sourceTree = "<group>"; };
84E03CF4DDF15DB471C5CDCAE7D4BF35 /* UIFont+QMUI.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "UIFont+QMUI.m"; path = "QMUIKit/UIKitExtensions/UIFont+QMUI.m"; sourceTree = "<group>"; };
8567FB68C84B78EDD5692F7CB8D37560 /* CAAnimation+QMUI.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "CAAnimation+QMUI.h"; path = "QMUIKit/QMUIComponents/CAAnimation+QMUI.h"; sourceTree = "<group>"; };
8596B7A0C5A3E188061E9EDDAB573FCE /* QMUIKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = QMUIKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
8596B7A0C5A3E188061E9EDDAB573FCE /* QMUIKit */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = QMUIKit; path = QMUIKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
859F65CBAD1C7F4130531CCA3CBC642C /* UIControl+YYAdd.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "UIControl+YYAdd.m"; path = "YYKit/Base/UIKit/UIControl+YYAdd.m"; sourceTree = "<group>"; };
85B823E57AA708F105F6D3FB2B777E6C /* UITextView+QMUI.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "UITextView+QMUI.m"; path = "QMUIKit/UIKitExtensions/UITextView+QMUI.m"; sourceTree = "<group>"; };
875F3DC8605226054EF379B96768682B /* YYThreadSafeDictionary.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = YYThreadSafeDictionary.m; path = YYKit/Utility/YYThreadSafeDictionary.m; sourceTree = "<group>"; };
@ -1285,7 +1284,7 @@
8A84838CC30AC4504FE90E893AA3DA1B /* NSObject+YYAddForKVO.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "NSObject+YYAddForKVO.h"; path = "YYKit/Base/Foundation/NSObject+YYAddForKVO.h"; sourceTree = "<group>"; };
8AA10EA2ACEBF8BAE84C9BAA62D98A5B /* QMUIKit-prefix.pch */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "QMUIKit-prefix.pch"; sourceTree = "<group>"; };
8B3DE9C0679562961726358E30E0CF4D /* UITabBar+QMUI.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UITabBar+QMUI.h"; path = "QMUIKit/UIKitExtensions/UITabBar+QMUI.h"; sourceTree = "<group>"; };
8B8FAB0D627B17EDE1366984278705D9 /* MBProgressHUD.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = MBProgressHUD.framework; sourceTree = BUILT_PRODUCTS_DIR; };
8B8FAB0D627B17EDE1366984278705D9 /* MBProgressHUD */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = MBProgressHUD; path = MBProgressHUD.framework; sourceTree = BUILT_PRODUCTS_DIR; };
8BC38D1D00FF6962FC7D0377F12C10BB /* JTCalendarWeekDayView.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = JTCalendarWeekDayView.m; path = JTCalendar/Views/JTCalendarWeekDayView.m; sourceTree = "<group>"; };
8C26259258740078C8DCAEA6A23CB14C /* QMUIImagePickerCollectionViewCell.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = QMUIImagePickerCollectionViewCell.m; path = QMUIKit/QMUIComponents/ImagePickerLibrary/QMUIImagePickerCollectionViewCell.m; sourceTree = "<group>"; };
8C8C1AEAB496AE6EE122EA570CD73910 /* Toast-umbrella.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; path = "Toast-umbrella.h"; sourceTree = "<group>"; };
@ -1296,7 +1295,7 @@
8DE5F38A08177EE9C09B6B53D7064B8A /* JTCalendarManager.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = JTCalendarManager.m; path = JTCalendar/JTCalendarManager.m; sourceTree = "<group>"; };
8ED88CA768B5C686E04F982BE1C7160A /* QMUIImagePickerHelper.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = QMUIImagePickerHelper.h; path = QMUIKit/QMUIComponents/ImagePickerLibrary/QMUIImagePickerHelper.h; sourceTree = "<group>"; };
8EE583DFD1CABA9BA581808C375F4E4D /* Toast-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Toast-Info.plist"; sourceTree = "<group>"; };
8EF3F6D5B12FD742052D13B61D5C814F /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xml; name = PrivacyInfo.xcprivacy; path = privacy/PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
8EF3F6D5B12FD742052D13B61D5C814F /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; name = PrivacyInfo.xcprivacy; path = privacy/PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
8F12F54D282E372A275B71743E3F9F3D /* YYImage.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = YYImage.h; path = YYKit/Image/YYImage.h; sourceTree = "<group>"; };
8FC8C4A87070241C640DCFAA14CCC8CB /* YYTextLine.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = YYTextLine.m; path = YYKit/Text/Component/YYTextLine.m; sourceTree = "<group>"; };
904A84447FB88FC309A48C948FA1E289 /* QMUIDialogViewController.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = QMUIDialogViewController.m; path = QMUIKit/QMUIComponents/QMUIDialogViewController.m; sourceTree = "<group>"; };
@ -1323,7 +1322,7 @@
94B9B8910B23A0BF1D0F9763A0DAB01A /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS18.0.sdk/System/Library/Frameworks/SystemConfiguration.framework; sourceTree = DEVELOPER_DIR; };
9537B8ABCEC1F17A6241E8C414A58019 /* MKAnnotationView+YYWebImage.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "MKAnnotationView+YYWebImage.m"; path = "YYKit/Image/Categories/MKAnnotationView+YYWebImage.m"; sourceTree = "<group>"; };
9550C737553A5A30C38DE422329C7D20 /* QMUIKit.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = QMUIKit.modulemap; sourceTree = "<group>"; };
95784DA3E052291A415D111DC394DDB0 /* JTCalendar.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = JTCalendar.framework; sourceTree = BUILT_PRODUCTS_DIR; };
95784DA3E052291A415D111DC394DDB0 /* JTCalendar */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = JTCalendar; path = JTCalendar.framework; sourceTree = BUILT_PRODUCTS_DIR; };
961F2D673D70A5598ED8D4C01E8FE173 /* YYMemoryCache.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = YYMemoryCache.h; path = YYKit/Cache/YYMemoryCache.h; sourceTree = "<group>"; };
966B7812D4FDE4B60DFBC087B973DB41 /* MJRefreshNormalHeader.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = MJRefreshNormalHeader.m; path = MJRefresh/Custom/Header/MJRefreshNormalHeader.m; sourceTree = "<group>"; };
969AEBD6B204DBEAAC40D31E0F145485 /* UINavigationBar+Transition.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UINavigationBar+Transition.h"; path = "QMUIKit/QMUIComponents/NavigationBarTransition/UINavigationBar+Transition.h"; sourceTree = "<group>"; };
@ -1346,7 +1345,7 @@
9D3AB291AAA77506CEEA0B3275651F46 /* QMUILogger+QMUIConfigurationTemplate.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "QMUILogger+QMUIConfigurationTemplate.h"; path = "QMUIKit/QMUIComponents/QMUILogger+QMUIConfigurationTemplate.h"; sourceTree = "<group>"; };
9D658656CBA1A3194388574E9D1C972F /* QMUIDialogViewController.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = QMUIDialogViewController.h; path = QMUIKit/QMUIComponents/QMUIDialogViewController.h; sourceTree = "<group>"; };
9D931D0D1DD2F6F967102BE1C27B58AC /* QMUIPopupMenuItemView.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = QMUIPopupMenuItemView.h; path = QMUIKit/QMUIComponents/QMUIPopupMenuView/QMUIPopupMenuItemView.h; sourceTree = "<group>"; };
9D940727FF8FB9C785EB98E56350EF41 /* Podfile */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; indentWidth = 2; name = Podfile; path = ../Podfile; sourceTree = SOURCE_ROOT; tabWidth = 2; xcLanguageSpecificationIdentifier = xcode.lang.ruby; };
9D940727FF8FB9C785EB98E56350EF41 /* Podfile */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; indentWidth = 2; lastKnownFileType = text; name = Podfile; path = ../Podfile; sourceTree = SOURCE_ROOT; tabWidth = 2; xcLanguageSpecificationIdentifier = xcode.lang.ruby; };
9DB09952EC2FA5B9CA5A148F7B6EF258 /* NSData+YYAdd.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSData+YYAdd.m"; path = "YYKit/Base/Foundation/NSData+YYAdd.m"; sourceTree = "<group>"; };
9E56471C7EDE6544F1C2DF241DC0F337 /* QMUIScrollAnimator.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = QMUIScrollAnimator.h; path = QMUIKit/QMUIComponents/QMUIScrollAnimator/QMUIScrollAnimator.h; sourceTree = "<group>"; };
9EA987614A541E9052D4B2379ECB6CF3 /* AssetsLibrary.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AssetsLibrary.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS18.0.sdk/System/Library/Frameworks/AssetsLibrary.framework; sourceTree = DEVELOPER_DIR; };
@ -1375,7 +1374,7 @@
A366D8F61B66F9B0644F4C828EBC630F /* NSThread+YYAdd.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSThread+YYAdd.m"; path = "YYKit/Base/Foundation/NSThread+YYAdd.m"; sourceTree = "<group>"; };
A37DCDD5739412E29BC59828B9D6F6D1 /* UITableViewCell+QMUI.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "UITableViewCell+QMUI.m"; path = "QMUIKit/UIKitExtensions/UITableViewCell+QMUI.m"; sourceTree = "<group>"; };
A39ECB8468A0684D587FCCD9CF5B150D /* UIApplication+QMUI.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UIApplication+QMUI.h"; path = "QMUIKit/UIKitExtensions/UIApplication+QMUI.h"; sourceTree = "<group>"; };
A3A80BA70CFB7F75C5391BEBBBA8C9DA /* FMDB.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = FMDB.framework; sourceTree = BUILT_PRODUCTS_DIR; };
A3A80BA70CFB7F75C5391BEBBBA8C9DA /* FMDB */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = FMDB; path = FMDB.framework; sourceTree = BUILT_PRODUCTS_DIR; };
A3E3B2AFE230CEA9E7F8EA0DA684EC02 /* QMUITabBarViewController.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = QMUITabBarViewController.h; path = QMUIKit/QMUIMainFrame/QMUITabBarViewController.h; sourceTree = "<group>"; };
A445C85678E48F427EAB92744CE46240 /* QMUIPopupMenuItem.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = QMUIPopupMenuItem.h; path = QMUIKit/QMUIComponents/QMUIPopupMenuView/QMUIPopupMenuItem.h; sourceTree = "<group>"; };
A52375CEBA5EEE849E2C3A713CFE3B63 /* MJRefreshAutoStateFooter.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = MJRefreshAutoStateFooter.m; path = MJRefresh/Custom/Footer/Auto/MJRefreshAutoStateFooter.m; sourceTree = "<group>"; };
@ -1387,7 +1386,7 @@
A7D8EBE0A05AD61C8C7D8762FF98BD09 /* QMUIImagePreviewView.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = QMUIImagePreviewView.m; path = QMUIKit/QMUIComponents/QMUIImagePreviewView/QMUIImagePreviewView.m; sourceTree = "<group>"; };
A7FACA30C97F3BFC83588B3E50AA5B4B /* DateTools-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "DateTools-Info.plist"; sourceTree = "<group>"; };
A8169D3089AD4B331710912DFEEC08C9 /* QMUILogManagerViewController.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = QMUILogManagerViewController.m; path = QMUIKit/QMUIComponents/QMUILogManagerViewController.m; sourceTree = "<group>"; };
A81CAF7165BA36F56425571C70138AA9 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xml; name = PrivacyInfo.xcprivacy; path = QMUIKit/PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
A81CAF7165BA36F56425571C70138AA9 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; name = PrivacyInfo.xcprivacy; path = QMUIKit/PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
A82B1BED716DE5D52061470409334F68 /* MJRefresh.modulemap */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.module; path = MJRefresh.modulemap; sourceTree = "<group>"; };
A8664BF30872D5794A2246C65BE5C62F /* Photos.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Photos.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS18.0.sdk/System/Library/Frameworks/Photos.framework; sourceTree = DEVELOPER_DIR; };
A86C1CDEA08E9FEFC85D6165E784B3A5 /* UINavigationBar+QMUIBarProtocol.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UINavigationBar+QMUIBarProtocol.h"; path = "QMUIKit/UIKitExtensions/QMUIBarProtocol/UINavigationBar+QMUIBarProtocol.h"; sourceTree = "<group>"; };
@ -1421,7 +1420,7 @@
AF5B039453FE62D663CB7CB29ED98F0A /* MJRefresh.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = MJRefresh.release.xcconfig; sourceTree = "<group>"; };
AFEE9CC03509452585E6C4679E10464B /* QMUICommonViewController.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = QMUICommonViewController.h; path = QMUIKit/QMUIMainFrame/QMUICommonViewController.h; sourceTree = "<group>"; };
B04C735B97EC4B936D408F673F7FCB28 /* QMUICellHeightKeyCache.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = QMUICellHeightKeyCache.h; path = QMUIKit/QMUIComponents/QMUICellHeightKeyCache/QMUICellHeightKeyCache.h; sourceTree = "<group>"; };
B097DD7534E741D5C41838011D755842 /* Pods_iosApp.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_iosApp.framework; sourceTree = BUILT_PRODUCTS_DIR; };
B097DD7534E741D5C41838011D755842 /* Pods-iosApp */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = "Pods-iosApp"; path = Pods_iosApp.framework; sourceTree = BUILT_PRODUCTS_DIR; };
B0E0499F15203226BF565DB1BFB6DE2B /* QMUITheme.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = QMUITheme.h; path = QMUIKit/QMUIComponents/QMUITheme/QMUITheme.h; sourceTree = "<group>"; };
B0FE5A520EE5266F1CBEB05466DE30C6 /* NSString+YYAdd.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "NSString+YYAdd.h"; path = "YYKit/Base/Foundation/NSString+YYAdd.h"; sourceTree = "<group>"; };
B18E769BEE575C532C4B87E1AC4D560D /* QMUICore.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = QMUICore.h; path = QMUIKit/QMUICore/QMUICore.h; sourceTree = "<group>"; };
@ -1448,7 +1447,7 @@
B6DB9D1615EE8A837522538688F4B777 /* NSBundle+MJRefresh.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "NSBundle+MJRefresh.h"; path = "MJRefresh/NSBundle+MJRefresh.h"; sourceTree = "<group>"; };
B7925FFC9C6E0D7F7E94846D2F6570FC /* QMUIImagePickerViewController.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = QMUIImagePickerViewController.m; path = QMUIKit/QMUIComponents/ImagePickerLibrary/QMUIImagePickerViewController.m; sourceTree = "<group>"; };
B7DDEBFFF99F2983FADE57DADF047954 /* Masonry-Info.plist */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.plist.xml; path = "Masonry-Info.plist"; sourceTree = "<group>"; };
B859C319A7199FDF80B64D597C478C52 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xml; name = PrivacyInfo.xcprivacy; path = MJExtension/PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
B859C319A7199FDF80B64D597C478C52 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; name = PrivacyInfo.xcprivacy; path = MJExtension/PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
B87072D3B9792B6CF15677BB3187DEC1 /* FMDatabaseQueue.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = FMDatabaseQueue.h; path = src/fmdb/FMDatabaseQueue.h; sourceTree = "<group>"; };
B92FF99DE7C65FF0FE229B85F4375DF2 /* UIBarButtonItem+YYAdd.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UIBarButtonItem+YYAdd.h"; path = "YYKit/Base/UIKit/UIBarButtonItem+YYAdd.h"; sourceTree = "<group>"; };
B93BB930C684F4F41D32F3208BD05836 /* Toast-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "Toast-dummy.m"; sourceTree = "<group>"; };
@ -1564,20 +1563,20 @@
DFC8EA9D75040D5DF7E41A9EB8B06365 /* UIView+QMUI.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UIView+QMUI.h"; path = "QMUIKit/UIKitExtensions/UIView+QMUI.h"; sourceTree = "<group>"; };
E02E057999FC194ADFF5BD09E510A59C /* QMUISheetPresentationSupports.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = QMUISheetPresentationSupports.m; path = QMUIKit/QMUIComponents/QMUISheetPresentation/QMUISheetPresentationSupports.m; sourceTree = "<group>"; };
E05874626ECD90A0246CAAB55D4EFF36 /* NSBundle+MJRefresh.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSBundle+MJRefresh.m"; path = "MJRefresh/NSBundle+MJRefresh.m"; sourceTree = "<group>"; };
E0808F98C2488C041B64234F38FB396F /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xml; name = PrivacyInfo.xcprivacy; path = Toast/Resources/PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
E0808F98C2488C041B64234F38FB396F /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; includeInIndex = 1; name = PrivacyInfo.xcprivacy; path = Toast/Resources/PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
E09E3B2B558328C84304AF372F4A5194 /* JTCalendarWeekView.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = JTCalendarWeekView.m; path = JTCalendar/Views/JTCalendarWeekView.m; sourceTree = "<group>"; };
E11E78C23F020213B88F74354507BC71 /* UIPasteboard+YYText.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UIPasteboard+YYText.h"; path = "YYKit/Text/String/UIPasteboard+YYText.h"; sourceTree = "<group>"; };
E23CE514570EF1F8D4C5876F9E01B060 /* UIControl+YYAdd.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UIControl+YYAdd.h"; path = "YYKit/Base/UIKit/UIControl+YYAdd.h"; sourceTree = "<group>"; };
E2E022FDBAEF5A5C4AC8E7AD124F0877 /* UITableView+YYAdd.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UITableView+YYAdd.h"; path = "YYKit/Base/UIKit/UITableView+YYAdd.h"; sourceTree = "<group>"; };
E2E6BF268E792329201BC2BF5DE0B8DB /* shared.podspec */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; indentWidth = 2; path = shared.podspec; sourceTree = "<group>"; tabWidth = 2; xcLanguageSpecificationIdentifier = xcode.lang.ruby; };
E2E6BF268E792329201BC2BF5DE0B8DB /* shared.podspec */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; includeInIndex = 1; indentWidth = 2; lastKnownFileType = text; path = shared.podspec; sourceTree = "<group>"; tabWidth = 2; xcLanguageSpecificationIdentifier = xcode.lang.ruby; };
E2FC95BB9F61DE49FA89BFDBE60FA448 /* UIInterface+QMUI.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "UIInterface+QMUI.m"; path = "QMUIKit/UIKitExtensions/UIInterface+QMUI.m"; sourceTree = "<group>"; };
E325DCCD357AF9DF2F7C506424659D7E /* YYKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = YYKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
E325DCCD357AF9DF2F7C506424659D7E /* YYKit */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = YYKit; path = YYKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
E39BE12967CFA5BCDF680F7E8376943C /* shared.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = shared.release.xcconfig; sourceTree = "<group>"; };
E421DAC2E39B321B2E8F66F48872AFC1 /* NSString+YYAdd.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSString+YYAdd.m"; path = "YYKit/Base/Foundation/NSString+YYAdd.m"; sourceTree = "<group>"; };
E462E23B3674BF94EAB1504D506F2803 /* Pods-iosApp.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = "Pods-iosApp.debug.xcconfig"; sourceTree = "<group>"; };
E46C60D542D0A844448162142EC75414 /* View+MASAdditions.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "View+MASAdditions.m"; path = "Masonry/View+MASAdditions.m"; sourceTree = "<group>"; };
E47DBF9B62AEE05EBC325255EEF945CF /* QMUIBarProtocolPrivate.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = QMUIBarProtocolPrivate.m; path = QMUIKit/UIKitExtensions/QMUIBarProtocol/QMUIBarProtocolPrivate.m; sourceTree = "<group>"; };
E49D6D248DD1CEE584E6776B9164A1B2 /* MJRefresh.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = MJRefresh.framework; sourceTree = BUILT_PRODUCTS_DIR; };
E49D6D248DD1CEE584E6776B9164A1B2 /* MJRefresh */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; name = MJRefresh; path = MJRefresh.framework; sourceTree = BUILT_PRODUCTS_DIR; };
E4C923318724794E3CC670804C2D6A6B /* Pods-iosApp-acknowledgements.markdown */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text; path = "Pods-iosApp-acknowledgements.markdown"; sourceTree = "<group>"; };
E54CCF51B605536EFA83945EF94B4EEA /* YYTextParser.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = YYTextParser.m; path = YYKit/Text/String/YYTextParser.m; sourceTree = "<group>"; };
E5826B9926F269D68CAC9BD758B80CA3 /* QMUIKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; path = QMUIKit.debug.xcconfig; sourceTree = "<group>"; };
@ -1618,7 +1617,7 @@
EF049A659713139834385CEE82C69609 /* UIWindow+QMUI.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "UIWindow+QMUI.m"; path = "QMUIKit/UIKitExtensions/UIWindow+QMUI.m"; sourceTree = "<group>"; };
EFA9DC5FB92D08D8950DDBD1310F1D22 /* QMUIButton.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = QMUIButton.h; path = QMUIKit/QMUIComponents/QMUIButton/QMUIButton.h; sourceTree = "<group>"; };
EFBDCA93B4680A2BB8A955F659251FA1 /* FMResultSet.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = FMResultSet.m; path = src/fmdb/FMResultSet.m; sourceTree = "<group>"; };
F064C0D7CE795102A652A82AEBBA7514 /* compose-resources */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = folder; name = "compose-resources"; path = "build/compose/cocoapods/compose-resources"; sourceTree = "<group>"; };
F064C0D7CE795102A652A82AEBBA7514 /* compose-resources */ = {isa = PBXFileReference; includeInIndex = 1; name = "compose-resources"; path = "build/compose/cocoapods/compose-resources"; sourceTree = "<group>"; };
F07E48F2C3EE67A8994B426EF4BF07AE /* JTCalendarMenuView.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = JTCalendarMenuView.h; path = JTCalendar/Views/JTCalendarMenuView.h; sourceTree = "<group>"; };
F117A87C7FBEFEFE59FA67B7C2169487 /* QMUISheetPresentationSupports.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = QMUISheetPresentationSupports.h; path = QMUIKit/QMUIComponents/QMUISheetPresentation/QMUISheetPresentationSupports.h; sourceTree = "<group>"; };
F123B632FFC891BD7ACF07399B314C59 /* MJRefreshStateTrailer.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = MJRefreshStateTrailer.m; path = MJRefresh/Custom/Trailer/MJRefreshStateTrailer.m; sourceTree = "<group>"; };
@ -1641,7 +1640,7 @@
F59C3DC9BD951FF1F657BADC8BA2E399 /* Images.xcassets */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = folder.assetcatalog; name = Images.xcassets; path = QMUIKit/QMUIResources/Images.xcassets; sourceTree = "<group>"; };
F660573EA68F19AEBD85810F770B605E /* NSNumber+YYAdd.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "NSNumber+YYAdd.m"; path = "YYKit/Base/Foundation/NSNumber+YYAdd.m"; sourceTree = "<group>"; };
F68D082E47A4E2F4A0E530138B6A04DF /* YYSpriteSheetImage.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = YYSpriteSheetImage.h; path = YYKit/Image/YYSpriteSheetImage.h; sourceTree = "<group>"; };
F6A5F09CA59AF20B5A450FA1B72ECFB5 /* Toast.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = Toast.bundle; sourceTree = BUILT_PRODUCTS_DIR; };
F6A5F09CA59AF20B5A450FA1B72ECFB5 /* Toast-Toast */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; name = "Toast-Toast"; path = Toast.bundle; sourceTree = BUILT_PRODUCTS_DIR; };
F7379BE8F406F3C72A6231E3793B2664 /* YYKVStorage.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = YYKVStorage.m; path = YYKit/Cache/YYKVStorage.m; sourceTree = "<group>"; };
F79D3EEA43B8DB0BC3CCB347E263757D /* UIActivityIndicatorView+QMUI.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; name = "UIActivityIndicatorView+QMUI.m"; path = "QMUIKit/UIKitExtensions/UIActivityIndicatorView+QMUI.m"; sourceTree = "<group>"; };
F7BFCD2C98284A7E9D224C33B48B3F7F /* NSNumber+QMUI.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "NSNumber+QMUI.h"; path = "QMUIKit/UIKitExtensions/NSNumber+QMUI.h"; sourceTree = "<group>"; };
@ -1670,7 +1669,7 @@
FEF639B20DD6F38FF8EBB3EC61143B7D /* UIView+YYAdd.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = "UIView+YYAdd.h"; path = "YYKit/Base/UIKit/UIView+YYAdd.h"; sourceTree = "<group>"; };
FF1C43BE49695E132A0C408E341DA0D7 /* YYKit-dummy.m */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.objc; path = "YYKit-dummy.m"; sourceTree = "<group>"; };
FFAF591ACE0B050AED1F749C6E2F9444 /* JTVerticalCalendarView.h */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = sourcecode.c.h; name = JTVerticalCalendarView.h; path = JTCalendar/Views/JTVerticalCalendarView.h; sourceTree = "<group>"; };
FFDF743B2D7D655F7364FF1154FFEDE6 /* QMUIResources.bundle */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = QMUIResources.bundle; sourceTree = BUILT_PRODUCTS_DIR; };
FFDF743B2D7D655F7364FF1154FFEDE6 /* QMUIKit-QMUIResources */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; name = "QMUIKit-QMUIResources"; path = QMUIResources.bundle; sourceTree = BUILT_PRODUCTS_DIR; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -1992,6 +1991,7 @@
560DBC31EF8D71C0CE5ACE67B84F7467 /* ViewController+MASAdditions.m */,
7BBCFB40EB87A89F94E866E9E40EB17F /* Support Files */,
);
name = Masonry;
path = Masonry;
sourceTree = "<group>";
};
@ -2091,6 +2091,7 @@
6A976840C663BDE1D11F983C55587F48 /* MBProgressHUD.m */,
DBF5826E493B9701A1E7031256471519 /* Support Files */,
);
name = MBProgressHUD;
path = MBProgressHUD;
sourceTree = "<group>";
};
@ -2155,6 +2156,7 @@
AE83BD689E0100F727209055F1A8CBFE /* JTVerticalCalendarView.m */,
35368B3F77B483377BBB9CCA2D1EDEBA /* Support Files */,
);
name = JTCalendar;
path = JTCalendar;
sourceTree = "<group>";
};
@ -2185,6 +2187,7 @@
76CE4D3A7AE1A8E71D8FD57706767234 /* Resources */,
AA5316CB55C7534E376E3DF36109F8F9 /* Support Files */,
);
name = MJExtension;
path = MJExtension;
sourceTree = "<group>";
};
@ -2194,6 +2197,7 @@
30F3A671CAA8FEC94E6F14DD3507038D /* Core */,
8B5DCD800073497DDB7B0F7613A5CDAA /* Support Files */,
);
name = FMDB;
path = FMDB;
sourceTree = "<group>";
};
@ -2344,23 +2348,23 @@
3A74C13BD22F5258125530629A5F8D14 /* Products */ = {
isa = PBXGroup;
children = (
4AF171581392AD234F23BE913F0C22FE /* DateTools.framework */,
A3A80BA70CFB7F75C5391BEBBBA8C9DA /* FMDB.framework */,
148D9AC15C4A9777E5ACBB46C03FE218 /* FMDB_Privacy.bundle */,
95784DA3E052291A415D111DC394DDB0 /* JTCalendar.framework */,
1FFED36A657123030ABB700256D73F15 /* Masonry.framework */,
8B8FAB0D627B17EDE1366984278705D9 /* MBProgressHUD.framework */,
2B276B0A79173A1D6E83C9B4FB9A4A57 /* MJExtension.framework */,
43EAAD2AB7E6B407E80E95F643F93D22 /* MJExtension.bundle */,
E49D6D248DD1CEE584E6776B9164A1B2 /* MJRefresh.framework */,
7E3097CFEFDA621E9FB0E62009FF87FC /* MJRefresh.Privacy.bundle */,
B097DD7534E741D5C41838011D755842 /* Pods_iosApp.framework */,
8596B7A0C5A3E188061E9EDDAB573FCE /* QMUIKit.framework */,
77DEF480928809EA82047E24B3C57BA7 /* QMUIKit.bundle */,
FFDF743B2D7D655F7364FF1154FFEDE6 /* QMUIResources.bundle */,
55E0AFD333353D71ACC2207149E879D6 /* Toast.framework */,
F6A5F09CA59AF20B5A450FA1B72ECFB5 /* Toast.bundle */,
E325DCCD357AF9DF2F7C506424659D7E /* YYKit.framework */,
4AF171581392AD234F23BE913F0C22FE /* DateTools */,
A3A80BA70CFB7F75C5391BEBBBA8C9DA /* FMDB */,
148D9AC15C4A9777E5ACBB46C03FE218 /* FMDB-FMDB_Privacy */,
95784DA3E052291A415D111DC394DDB0 /* JTCalendar */,
1FFED36A657123030ABB700256D73F15 /* Masonry */,
8B8FAB0D627B17EDE1366984278705D9 /* MBProgressHUD */,
2B276B0A79173A1D6E83C9B4FB9A4A57 /* MJExtension */,
43EAAD2AB7E6B407E80E95F643F93D22 /* MJExtension-MJExtension */,
E49D6D248DD1CEE584E6776B9164A1B2 /* MJRefresh */,
7E3097CFEFDA621E9FB0E62009FF87FC /* MJRefresh-MJRefresh.Privacy */,
B097DD7534E741D5C41838011D755842 /* Pods-iosApp */,
8596B7A0C5A3E188061E9EDDAB573FCE /* QMUIKit */,
77DEF480928809EA82047E24B3C57BA7 /* QMUIKit-QMUIKit */,
FFDF743B2D7D655F7364FF1154FFEDE6 /* QMUIKit-QMUIResources */,
55E0AFD333353D71ACC2207149E879D6 /* Toast */,
F6A5F09CA59AF20B5A450FA1B72ECFB5 /* Toast-Toast */,
E325DCCD357AF9DF2F7C506424659D7E /* YYKit */,
);
name = Products;
sourceTree = "<group>";
@ -2719,6 +2723,7 @@
EAC932B63F4CB3BC8F70262E06A858DA /* Resources */,
5F6E41DD88E68F94EBA958A6C348C3FD /* Support Files */,
);
name = Toast;
path = Toast;
sourceTree = "<group>";
};
@ -2972,6 +2977,7 @@
1E6EAF556834D0F6AEB4BEA3D04D1AF2 /* no-arc */,
18A559C0F8BE56F8E20591E08CEBD075 /* Support Files */,
);
name = YYKit;
path = YYKit;
sourceTree = "<group>";
};
@ -3108,6 +3114,7 @@
BB895CE877FC657AB7E5B8A0035555B2 /* Resources */,
C1ACA7E450ABD1AA622C5C3B4B9C682C /* Support Files */,
);
name = DateTools;
path = DateTools;
sourceTree = "<group>";
};
@ -3192,6 +3199,7 @@
E6307622D19FF978837C843981D42250 /* Resources */,
0BDACE2DF2DD5FF2526B04F2EA826B75 /* Support Files */,
);
name = MJRefresh;
path = MJRefresh;
sourceTree = "<group>";
};
@ -3258,6 +3266,7 @@
AA2F6EEF8EF543F539A97B1DF314F534 /* Resources */,
5B2FC84517AB039B6B9A71D68459A6D9 /* Support Files */,
);
name = QMUIKit;
path = QMUIKit;
sourceTree = "<group>";
};
@ -3932,7 +3941,7 @@
);
name = YYKit;
productName = YYKit;
productReference = E325DCCD357AF9DF2F7C506424659D7E /* YYKit.framework */;
productReference = E325DCCD357AF9DF2F7C506424659D7E /* YYKit */;
productType = "com.apple.product-type.framework";
};
225FB3DC8F47C58DEEAE716AFE3005BD /* QMUIKit-QMUIKit */ = {
@ -3949,7 +3958,7 @@
);
name = "QMUIKit-QMUIKit";
productName = QMUIKit;
productReference = 77DEF480928809EA82047E24B3C57BA7 /* QMUIKit.bundle */;
productReference = 77DEF480928809EA82047E24B3C57BA7 /* QMUIKit-QMUIKit */;
productType = "com.apple.product-type.bundle";
};
2B1A4F9261E8F421732B6CB1319CCC3E /* DateTools */ = {
@ -3967,7 +3976,7 @@
);
name = DateTools;
productName = DateTools;
productReference = 4AF171581392AD234F23BE913F0C22FE /* DateTools.framework */;
productReference = 4AF171581392AD234F23BE913F0C22FE /* DateTools */;
productType = "com.apple.product-type.framework";
};
4D3BA58D0583DF37575CACAB3DDADC85 /* MJExtension */ = {
@ -3986,7 +3995,7 @@
);
name = MJExtension;
productName = MJExtension;
productReference = 2B276B0A79173A1D6E83C9B4FB9A4A57 /* MJExtension.framework */;
productReference = 2B276B0A79173A1D6E83C9B4FB9A4A57 /* MJExtension */;
productType = "com.apple.product-type.framework";
};
55AF53E6C77A10ED4985E04D74A8878E /* Masonry */ = {
@ -4004,7 +4013,7 @@
);
name = Masonry;
productName = Masonry;
productReference = 1FFED36A657123030ABB700256D73F15 /* Masonry.framework */;
productReference = 1FFED36A657123030ABB700256D73F15 /* Masonry */;
productType = "com.apple.product-type.framework";
};
6868056D761E163D10FDAF8CF1C4D9B8 /* MJRefresh */ = {
@ -4023,7 +4032,7 @@
);
name = MJRefresh;
productName = MJRefresh;
productReference = E49D6D248DD1CEE584E6776B9164A1B2 /* MJRefresh.framework */;
productReference = E49D6D248DD1CEE584E6776B9164A1B2 /* MJRefresh */;
productType = "com.apple.product-type.framework";
};
740124B3EE5D14F0E8AF4C9163C297A8 /* QMUIKit */ = {
@ -4043,7 +4052,7 @@
);
name = QMUIKit;
productName = QMUIKit;
productReference = 8596B7A0C5A3E188061E9EDDAB573FCE /* QMUIKit.framework */;
productReference = 8596B7A0C5A3E188061E9EDDAB573FCE /* QMUIKit */;
productType = "com.apple.product-type.framework";
};
7F7C709A913CD2DAF7541A1D8CAC7706 /* JTCalendar */ = {
@ -4061,7 +4070,7 @@
);
name = JTCalendar;
productName = JTCalendar;
productReference = 95784DA3E052291A415D111DC394DDB0 /* JTCalendar.framework */;
productReference = 95784DA3E052291A415D111DC394DDB0 /* JTCalendar */;
productType = "com.apple.product-type.framework";
};
82B0A41D3031FF27D78E17B0A9A46FB0 /* MBProgressHUD */ = {
@ -4079,7 +4088,7 @@
);
name = MBProgressHUD;
productName = MBProgressHUD;
productReference = 8B8FAB0D627B17EDE1366984278705D9 /* MBProgressHUD.framework */;
productReference = 8B8FAB0D627B17EDE1366984278705D9 /* MBProgressHUD */;
productType = "com.apple.product-type.framework";
};
8592E0E389D40AC17881400ADC67ABC0 /* FMDB */ = {
@ -4098,7 +4107,7 @@
);
name = FMDB;
productName = FMDB;
productReference = A3A80BA70CFB7F75C5391BEBBBA8C9DA /* FMDB.framework */;
productReference = A3A80BA70CFB7F75C5391BEBBBA8C9DA /* FMDB */;
productType = "com.apple.product-type.framework";
};
973B9A51B49701F13767694DCAF5C37D /* FMDB-FMDB_Privacy */ = {
@ -4115,7 +4124,7 @@
);
name = "FMDB-FMDB_Privacy";
productName = FMDB_Privacy;
productReference = 148D9AC15C4A9777E5ACBB46C03FE218 /* FMDB_Privacy.bundle */;
productReference = 148D9AC15C4A9777E5ACBB46C03FE218 /* FMDB-FMDB_Privacy */;
productType = "com.apple.product-type.bundle";
};
9972C9CC43A34349C035FE6C913368BF /* Toast-Toast */ = {
@ -4132,7 +4141,7 @@
);
name = "Toast-Toast";
productName = Toast;
productReference = F6A5F09CA59AF20B5A450FA1B72ECFB5 /* Toast.bundle */;
productReference = F6A5F09CA59AF20B5A450FA1B72ECFB5 /* Toast-Toast */;
productType = "com.apple.product-type.bundle";
};
A4F02C53B5B4FD6A5A304A7F0FAC06E6 /* QMUIKit-QMUIResources */ = {
@ -4149,7 +4158,7 @@
);
name = "QMUIKit-QMUIResources";
productName = QMUIResources;
productReference = FFDF743B2D7D655F7364FF1154FFEDE6 /* QMUIResources.bundle */;
productReference = FFDF743B2D7D655F7364FF1154FFEDE6 /* QMUIKit-QMUIResources */;
productType = "com.apple.product-type.bundle";
};
A80A4D6B185BA43BC06122FED0C15F94 /* Toast */ = {
@ -4168,7 +4177,7 @@
);
name = Toast;
productName = Toast;
productReference = 55E0AFD333353D71ACC2207149E879D6 /* Toast.framework */;
productReference = 55E0AFD333353D71ACC2207149E879D6 /* Toast */;
productType = "com.apple.product-type.framework";
};
B26054DF1DEA11585A231AF6D1D80D5E /* MJRefresh-MJRefresh.Privacy */ = {
@ -4185,7 +4194,7 @@
);
name = "MJRefresh-MJRefresh.Privacy";
productName = MJRefresh.Privacy;
productReference = 7E3097CFEFDA621E9FB0E62009FF87FC /* MJRefresh.Privacy.bundle */;
productReference = 7E3097CFEFDA621E9FB0E62009FF87FC /* MJRefresh-MJRefresh.Privacy */;
productType = "com.apple.product-type.bundle";
};
B32AF3F43989CBA171BB1FB3957A4509 /* MJExtension-MJExtension */ = {
@ -4202,7 +4211,7 @@
);
name = "MJExtension-MJExtension";
productName = MJExtension;
productReference = 43EAAD2AB7E6B407E80E95F643F93D22 /* MJExtension.bundle */;
productReference = 43EAAD2AB7E6B407E80E95F643F93D22 /* MJExtension-MJExtension */;
productType = "com.apple.product-type.bundle";
};
ED39C638569286489CD697A6C8964146 /* Pods-iosApp */ = {
@ -4231,7 +4240,7 @@
);
name = "Pods-iosApp";
productName = Pods_iosApp;
productReference = B097DD7534E741D5C41838011D755842 /* Pods_iosApp.framework */;
productReference = B097DD7534E741D5C41838011D755842 /* Pods-iosApp */;
productType = "com.apple.product-type.framework";
};
/* End PBXNativeTarget section */
@ -4252,6 +4261,8 @@
en,
);
mainGroup = CF1408CF629C7361332E53B88F7BD30C;
minimizedProjectReferenceProxies = 0;
preferredProjectObjectVersion = 77;
productRefGroup = 3A74C13BD22F5258125530629A5F8D14 /* Products */;
projectDirPath = "";
projectRoot = "";
@ -4305,7 +4316,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
03F2428C9FE1AF75B287A39D46F646FA /* FMDB_Privacy.bundle in Resources */,
03F2428C9FE1AF75B287A39D46F646FA /* FMDB-FMDB_Privacy in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -4321,7 +4332,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
7873F2F89CD0A435FAB776BC27BFB56A /* MJExtension.bundle in Resources */,
7873F2F89CD0A435FAB776BC27BFB56A /* MJExtension-MJExtension in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -4354,7 +4365,7 @@
buildActionMask = 2147483647;
files = (
D90DF1376DF5E2EA644313BCD2E03058 /* MJRefresh.bundle in Resources */,
327BA3DDA513422E632D3DA4A8FC60EC /* MJRefresh.Privacy.bundle in Resources */,
327BA3DDA513422E632D3DA4A8FC60EC /* MJRefresh-MJRefresh.Privacy in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -4369,7 +4380,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
D60FD96D6C7D9442E6A71E410301B3E2 /* Toast.bundle in Resources */,
D60FD96D6C7D9442E6A71E410301B3E2 /* Toast-Toast in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -4385,8 +4396,8 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
62CFA1DA1F44515EBEFAC8BBDCAFEA86 /* QMUIKit.bundle in Resources */,
D4D4E8BD5F155B6C32FE15226FD3315C /* QMUIResources.bundle in Resources */,
62CFA1DA1F44515EBEFAC8BBDCAFEA86 /* QMUIKit-QMUIKit in Resources */,
D4D4E8BD5F155B6C32FE15226FD3315C /* QMUIKit-QMUIResources in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -4420,11 +4431,7 @@
buildActionMask = 2147483647;
files = (
);
inputPaths = (
);
name = "[CP-User] Build shared";
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = " if [ \"YES\" = \"$OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED\" ]; then\n echo \"Skipping Gradle build task invocation due to OVERRIDE_KOTLIN_BUILD_IDE_SUPPORTED environment variable set to \"YES\"\"\n exit 0\n fi\n set -ev\n REPO_ROOT=\"$PODS_TARGET_SRCROOT\"\n \"$REPO_ROOT/../gradlew\" -p \"$REPO_ROOT\" $KOTLIN_PROJECT_PATH:syncFramework -Pkotlin.native.cocoapods.platform=$PLATFORM_NAME -Pkotlin.native.cocoapods.archs=\"$ARCHS\" -Pkotlin.native.cocoapods.configuration=\"$CONFIGURATION\"\n";
@ -4991,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;
@ -5018,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",
@ -5058,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",
@ -5087,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;
@ -5114,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 = 12;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@ -5204,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;
@ -5231,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 = 12;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@ -5271,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",
@ -5310,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",
@ -5337,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;
@ -5365,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",
@ -5404,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",
@ -5443,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",
@ -5481,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 = 12;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@ -5509,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;
@ -5526,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",
@ -5556,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",
@ -5595,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",
@ -5635,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 = 12;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@ -5674,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",
@ -5713,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 = 12;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@ -5740,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;
@ -5757,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",
@ -5775,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;
@ -5803,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",
@ -5842,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 = 12;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@ -5881,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 = 12;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@ -5909,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;
@ -5936,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 = 12;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@ -5965,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;
@ -5982,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;
@ -5999,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;
@ -6027,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",
@ -6066,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 = 12;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",
@ -6160,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;
@ -6187,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 = 12;
IPHONEOS_DEPLOYMENT_TARGET = 14.0;
LD_RUNPATH_SEARCH_PATHS = (
"$(inherited)",
"@executable_path/Frameworks",

24
iosApp/iosApp.xcodeproj/project.pbxproj

@ -47,6 +47,8 @@
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 */; };
7555FF83242A565900829871 /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7555FF82242A565900829871 /* ContentView.swift */; };
/* End PBXBuildFile section */
@ -172,8 +174,10 @@
2044628B2DF07DCD009AF7B6 /* LTSRingSDK+Desc.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "LTSRingSDK+Desc.h"; sourceTree = "<group>"; };
2044628E2DF07DF9009AF7B6 /* LoginVc.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = LoginVc.h; sourceTree = "<group>"; };
204462912DF07E1A009AF7B6 /* MainNav.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MainNav.h; sourceTree = "<group>"; };
204462972DF07E75009AF7B6 /* PrefixHeader.pch */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PrefixHeader.pch; sourceTree = "<group>"; };
204462AC2DF082FA009AF7B6 /* NSString+Check.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSString+Check.h"; sourceTree = "<group>"; };
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 = "<group>"; };
204463432DF1EA80009AF7B6 /* LTSRingSDK+Desc.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "LTSRingSDK+Desc.m"; sourceTree = "<group>"; };
2152FB032600AC8F00CF470E /* iOSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSApp.swift; sourceTree = "<group>"; };
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 = "<group>"; };
3F1F0F9655FE3CEE26EB7A52 /* Pods_iosApp.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_iosApp.framework; sourceTree = BUILT_PRODUCTS_DIR; };
@ -191,6 +195,7 @@
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;
@ -395,7 +400,7 @@
204462602DF07D18009AF7B6 /* Libs */ = {
isa = PBXGroup;
children = (
204462972DF07E75009AF7B6 /* PrefixHeader.pch */,
204463432DF1EA80009AF7B6 /* LTSRingSDK+Desc.m */,
204461FD2DF07D18009AF7B6 /* BLESDK */,
204462092DF07D18009AF7B6 /* DataBase */,
204462192DF07D18009AF7B6 /* DateTools */,
@ -449,6 +454,7 @@
7555FF7D242A565900829871 /* iosApp */ = {
isa = PBXGroup;
children = (
204463422DF1DC53009AF7B6 /* PrefixHeader.pch */,
058557BA273AAA24004C7B11 /* Assets.xcassets */,
7555FF82242A565900829871 /* ContentView.swift */,
7555FF8C242A565B00829871 /* Info.plist */,
@ -463,6 +469,7 @@
7F492F9D678DB5C178EE9E8B /* Frameworks */ = {
isa = PBXGroup;
children = (
204462AF2DF0935D009AF7B6 /* CoreBluetooth.framework */,
204461F02DF06099009AF7B6 /* libRingSDK_2.0.2.a */,
204461EE2DF06041009AF7B6 /* Security.framework */,
204461EC2DF06034009AF7B6 /* Foundation.framework */,
@ -572,14 +579,10 @@
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
inputPaths = (
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-frameworks.sh\"\n";
@ -593,14 +596,10 @@
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-resources-${CONFIGURATION}-input-files.xcfilelist",
);
inputPaths = (
);
name = "[CP] Copy Pods Resources";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-resources-${CONFIGURATION}-output-files.xcfilelist",
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-resources.sh\"\n";
@ -640,6 +639,7 @@
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 */,
@ -805,7 +805,7 @@
"-lRingSDK_2.0.2",
"$(inherited)",
);
PRODUCT_BUNDLE_IDENTIFIER = moe.uni.app;
PRODUCT_BUNDLE_IDENTIFIER = moe.uni.ring;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "iosApp/iosApp-Bridging-Header.h";
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
@ -844,7 +844,7 @@
"-lRingSDK_2.0.2",
"$(inherited)",
);
PRODUCT_BUNDLE_IDENTIFIER = moe.uni.app;
PRODUCT_BUNDLE_IDENTIFIER = moe.uni.ring;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OBJC_BRIDGING_HEADER = "iosApp/iosApp-Bridging-Header.h";
SWIFT_VERSION = 5.0;

1
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)
}

4
iosApp/iosApp/Info.plist

@ -22,6 +22,10 @@
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>NSBluetoothAlwaysUsageDescription</key>
<string>此应用需要使用蓝牙来扫描和连接蓝牙设备</string>
<key>NSBluetoothPeripheralUsageDescription</key>
<string>此应用需要使用蓝牙来扫描和连接蓝牙设备</string>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>

65
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

46
iosApp/iosApp/Libs/Modules/DeviceCenter.m

@ -8,12 +8,12 @@
#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 "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,11 +491,11 @@ 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 -- %@",
// cmd,
// [self.sdk cmdErrorDesc:cmd] ,
// isSucc ? @"succ" :@"fail", (unsigned long)reason,
// [SRDeviceInfo descryOfErrorReason:reason] );
NSLog(@"Command 0x%.2X - %@ ,Exec result %@ , Fail reason:%lu -- %@",
cmd,
[self.sdk cmdErrorDesc:cmd] ,
isSucc ? @"succ" :@"fail", (unsigned long)reason,
[SRDeviceInfo descryOfErrorReason:reason] );
if ([self.appDataDelegate respondsToSelector:@selector(srBleCmdExcute:Succ:Reason:)]) {
[self.appDataDelegate srBleCmdExcute:cmd Succ:isSucc Reason: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) {

324
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];

23
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 */

1
iosApp/iosApp/iosApp-Bridging-Header.h

@ -2,4 +2,3 @@
// Use this file to import your target's public headers that you would like to expose to Swift.
//
#import "DeviceCenter.h"

13
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 {
@ -84,4 +85,14 @@ android {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
}
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)
}

BIN
shared/libs/NexRingSDK_v1.4.0_release.aar

Binary file not shown.

BIN
shared/libs/OemAuth_v2.0.0_release.aar

Binary file not shown.

BIN
shared/libs/SleepStagingNativeLib_v5_ring_release_v2.5.6.1.aar

Binary file not shown.

58
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<Activity>()
/**
* 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)
}
}

31
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)
}
}

230
shared/src/androidMain/kotlin/com/whitefish/ring/DeviceManager.kt

@ -1,13 +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>?) {
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
}
}
}
}
class DeviceManager: IDeviceManager {
override fun startScan() {
TODO("Not yet implemented")
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<Device>().apply {
addAll(_deviceList.value)
add(Device(result.device.name,result.device.address))
}
_deviceList.value = newDevices
}
override fun onScanFinished() {
}
})
}
}
}
override fun stopScan() {
TODO("Not yet implemented")
}
}
}

18
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)

112
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<Array<String>>? = 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<String>? {
val dinedPermissions: MutableList<String> = 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.")
}
}
}
}

12
shared/src/androidMain/kotlin/com/whitefish/ring/Platform.android.kt

@ -1,13 +1,18 @@
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}"
}
@ -56,6 +61,7 @@ private fun getNavigationBarHeightPx(context: Context): Int {
return navigationBarHeight
}
private val DeviceInstance = DeviceManager()
actual fun obtainDeviceManager(): IDeviceManager {
return DeviceManager()
}
return DeviceInstance
}

49
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() }
}

15
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,
)

423
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<OnBleConnectionListener> = ArrayList()
private var mOnBleScanCallback: OnBleScanCallback? = null
var bleGatt: BluetoothGatt? = null
private val scanDevMacList: MutableList<String> = 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()
}
}
}

8
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()
}

10
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()
}

12
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()
}
}

3
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)

30
shared/src/commonMain/kotlin/com/whitefish/ring/device/IDeviceManager.kt

@ -1,6 +1,30 @@
package com.whitefish.ring.device
interface IDeviceManager {
fun startScan()
fun stopScan()
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<List<Device>>(emptyList())
val deviceList = _deviceList.asStateFlow()
protected val _bleState = MutableStateFlow<Int>(-1)
val bleState = _bleState.asStateFlow()
val bleReadyStateFlow = MutableStateFlow(false)
val blePowerState = MutableStateFlow<Boolean>(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()
}

144
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()
}

150
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{
}
}

64
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<Device> = 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)
}
}

190
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<Hand?>(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()
}

100
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 ->
// 保存选择的惯用手
}
)
}
}
}

107
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()
}

174
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()
}

214
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()
}

166
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()
}

236
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<WearingPosition?>(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()
}

161
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()
}

1
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

31
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<HomeUiState> = _uiState.asStateFlow()

176
shared/src/iosMain/kotlin/com/whitefish/ring/DeviceManager.kt

@ -1,19 +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()
override fun startScan() {
class DeviceManager: IDeviceManager() {
private val manager = DeviceCenter.instance()
private var iosBleList = arrayListOf<SRBLeService>()
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()
}
}

4
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())
}
fun MainViewController() = ComposeUIViewController { App() }

1
shared/src/iosMain/kotlin/com/whitefish/ring/Platform.ios.kt

@ -63,4 +63,5 @@ actual fun obtainDeviceManager(): IDeviceManager {
fun initLogger(){
Napier.base(DebugAntilog())
Napier.i { "Logger init success on ios" }
}

225
shared/src/main/res/values/string.xml

@ -0,0 +1,225 @@
<resources>
<string name="app_name">RingApp</string>
<string name="launcher_title_text">Acti</string>
<string name="launcher_description_text">赋能每一个动作\nEmpower Every Move</string>
<string name="launcher_button_text">立即使用</string>
<string name="launcher_agreement_text">我已阅读井同意《用户隐私协议》和《用户注册协议》</string>
<string name="login_description">Hi,\n欢迎来到Acti</string>
<string name="login_phone_number">手机号</string>
<string name="login_phone_hint">请输入您的手机号</string>
<string name="login_code">验证码</string>
<string name="login_code_hint">请输入验证码</string>
<string name="login_unregister">未注册的手机号验证后自动注册登录</string>
<string name="login_button_text">登录</string>
<string name="connect_title">连接您的Acti戒指</string>
<string name="connect_description">将您的戒指连接到充电器,并继续下一步。请确保您的手机已启用蓝牙功能。</string>
<string name="connect_button">下一步</string>
<string name="devices_scan">正在搜索设备...</string>
<string name="devices_list">附近设备</string>
<string name="devices_can_not_find">找不到设备?</string>
<string name="devices_connect_fail">连接失败?</string>
<string name="first_fragment_label">First Fragment</string>
<string name="second_fragment_label">Second Fragment</string>
<string name="next">Next</string>
<string name="previous">Previous</string>
<string name="heart_rate_value">%d 次/分</string>
<string name="label_no_data">无数据</string>
<string name="dialog_msg_turn_on_location_service">为了可以扫描蓝牙设备,现在还需要开启位置服务。</string>
<string name="tip_ring_not_connected">您未连接戒指</string>
<string name="dialog_title_tip">提示</string>
<string name="date_format">yyyy年MM月dd日</string>
<string name="date_format_ym">yyyy年MM月</string>
<string name="date_format_md">MM月dd日</string>
<string name="date_format_ymd">yyyy年MM月dd日</string>
<string name="label_today">今天</string>
<string name="label_yesterday">昨天</string>
<!-- ************************************************************************************** -->
<string name="label_rhr">静息心率</string>
<string name="label_step">步数</string>
<string name="label_sleep_hr_dip">心率沉浸</string>
<string name="label_sleep_br">呼吸速率</string>
<string name="label_sleep_spo2">血氧饱和度</string>
<string name="label_text_sleep_duration">睡眠%s %s效率</string>
<string name="desc_sleep_state_wake">苏醒/被打扰时间 %s</string>
<string name="desc_sleep_state_rem">REM睡眠 %s</string>
<string name="desc_sleep_state_light">浅度睡眠 %s</string>
<string name="desc_sleep_state_deep">深度睡眠 %s</string>
<string name="desc_sleep_state_nap">零星小睡 %s</string>
<string name="time_format_hh_mm">%d小时%d分钟</string>
<string name="time_format_hh">%d小时</string>
<string name="time_format_mm">%d分钟</string>
<string name="label_sleep_session">睡眠分析</string>
<string name="label_sleep_session_num">睡眠分析%d</string>
<!-- ************************************************************************************** -->
<string name="tip_text_sleep_duration">睡眠%s %s效率</string>
<string name="tip_text_nap_duration">睡眠%s</string>
<string name="label_sleep_details">睡眠详情</string>
<string name="session_tab_sleep">睡眠</string>
<string name="session_tab_nap">零星小睡</string>
<string name="title_available_devices">可用设备</string>
<string name="note_for_scan_devices">注意:未绑定的Ring在充电时才会广播,请充电,以便APP可以扫描到。</string>
<string name="tip_connecting">连接中…</string>
<string name="tip_wait_for_device_disconnected">请等待设备连接断开</string>
<string name="dialog_title_restricted_mode">受限模式</string>
<string name="dialog_msg_restricted_mode">已启用受限模式,只能恢复出厂设置和自检。</string>
<string name="activity_title_others">其他操作</string>
<string name="btn_label_skin_temp">手指温度</string>
<string name="btn_label_factory_test">工厂测试</string>
<string name="btn_label_reboot">重启</string>
<string name="btn_label_reset">恢复出厂设置</string>
<string name="btn_label_unbind">解綁</string>
<string name="btn_label_shutdown">关机</string>
<string name="btn_label_device_info">设备信息</string>
<string name="btn_label_device_sn">设备SN</string>
<string name="btn_label_take_ppg_readings">获取PPG读数(血氧、心率)</string>
<string name="btn_label_take_ppg_readings_tip">仅心率</string>
<string name="btn_label_set_ppg_params">设置PPG参数</string>
<string name="et_hint_bo_measurement_interval">SpO₂测量间隔 (0, 5~360, 分钟)</string>
<string name="btn_label_spo_measure_interval">SpO₂测量间隔</string>
<string name="btn_label_ecg">心电图</string>
<string name="label_set_health_measurement_duration">设置心率&amp;体温测量时长</string>
<string name="et_hint_set_health_measurement_duration">测量时长(10 ~ 180,秒)</string>
<string name="btn_commit">提交</string>
<string name="tip_for_empty_measurement_duration">请输入测量时长!</string>
<string name="label_off"></string>
<string name="label_on"></string>
<string name="set_to_dev_error">戒指断开连接,设置参数无法发送。</string>
<!-- ************************************************************************************** -->
<string name="btn_select_firmware_file">选择</string>
<string name="btn_query">查询</string>
<string name="label_select_fw_from_local">通过文件管理器APP从本地选择固件文件</string>
<string name="label_query_new_fw_from_server">从服务器查询并下载新的固件到本地</string>
<string name="tip_cannot_support_query_new_fw_from_server">您的设备暂未支持从服务器查询新的固件,缺少关键参数【%s】</string>
<string name="btn_upgrade">升级</string>
<string name="dfu_alert_no_file_browser_message">在您的设备上未找到文件浏览器应用程序。你想下载一个吗?</string>
<string name="ota_charging_mode_tip_for_wireless">该戒指为无线充电模式,请选择SR09W固件更新。</string>
<string name="ota_charging_mode_tip_for_nfc">该戒指为NFC充电模式,请选择SR09N固件更新。</string>
<string name="text_fw_already_the_last_ver">您的设备固件已经是最新版本:v%s.</string>
<string name="text_found_last_fw">查询到最新的固件版本:v%s,下载中…</string>
<string name="text_md5_matched">\nMD5匹配,正在保存文件到本地…</string>
<string name="text_md5_not_match">\nMD5不匹配!请检查。</string>
<string name="text_file_storage_path">\n文件存储路径:%s</string>
<string name="text_file_storage_error">\n文件存储出错:%s</string>
<string name="tip_empty_content_found">查询到空内容!</string>
<string name="tip_request_failed">请求失败,code = %d。</string>
<string name="btn_select_this_fw_file">选择此固件</string>
<string name="dev_size">尺寸%d</string>
<string name="ring_color_deep_black">深黑色</string>
<string name="ring_color_sliver">银色</string>
<string name="ring_color_golden">金色</string>
<string name="ring_color_rose_gold">玫瑰金</string>
<string name="ring_color_gold_silver_mixed">金/银混色</string>
<string name="ring_color_purple_silver_mixed">紫/银混色</string>
<string name="ring_color_rose_gold_silver_mixed">玫瑰金/银混色</string>
<string name="battery_level_charging">充电中</string>
<string name="battery_level_discharging">放电中</string>
<string name="dialog_title_oem_certification_failed">OEM验证失败</string>
<string name="dialog_msg_oem_certify_fail_cause_by_sn_null">设备序列号空,连接认证失败。</string>
<string name="dialog_msg_oem_certify_fail_cause_by_r1_to_r2">R1解密并生成R2失败。</string>
<string name="dialog_msg_oem_certify_fail_cause_by_certificate_r2">连接失败,请使用凌拓NexRing智能戒指。</string>
<string name="btn_label_disconnected">断开连接</string>
<string name="tip_your_ring_do_not_support_workout_mode">您的戒指型号不支持【锻炼模式】!</string>
<string name="title_workout">锻炼</string>
<string name="title_workout_history">锻炼记录</string>
<string name="workout_detail_duration">锻炼时间</string>
<string name="workout_detail_interval">时间间隔</string>
<string name="label_hr_avg">平均心率</string>
<string name="label_hr_max">最高心率</string>
<string name="label_hr_min">最低心率</string>
<string name="tip_synchronizing_data">同步数据中…</string>
<string name="tip_no_hr_data_available">没有可用的心率数据!</string>
<string name="label_choose_a_sport">选择一项锻炼</string>
<string name="workout_type_walking">步行</string>
<string name="workout_type_indoor_running">室内跑步</string>
<string name="workout_type_outdoor_running">室外跑步</string>
<string name="workout_type_indoor_cycling">室内骑车</string>
<string name="workout_type_outdoor_cycling">室外骑车</string>
<string name="workout_type_mountain_biking">山地自行车</string>
<string name="workout_type_swimming">游泳</string>
<string name="label_add_details">添加详情</string>
<string name="workout_mode_btn_start">开始</string>
<string name="label_workout_mode_finish">已结束</string>
<string name="label_workout_mode_in_progress">进行中</string>
<string name="btn_end_early">提早结束</string>
<string name="dialog_msg_end_workout_mode_early">是否提早结束锻炼?</string>
<string name="tip_ring_busy_for_charging">戒指充电中,请佩戴后操作</string>
<string name="text_sec">%d 秒</string>
<string name="text_min">%d 分钟</string>
<string name="text_hour">%d 小时</string>
<string name="cmd_execute_success">命令执行成功。</string>
<string name="cmd_execute_failed_1">命令执行失败。</string>
<string name="cmd_execute_failed_2">>命令执行失败。戒指连接时OEM验证未通过。</string>
<string name="cmd_execute_failed_3">>命令执行失败。戒指正在主动测量。</string>
<string name="cmd_execute_failed_4">>命令执行失败。戒指处于锻炼模式。</string>
<string name="cmd_execute_failed_5">>命令执行失败。戒指正在执行APP发起的测量。</string>
<string name="cmd_execute_failed_6">>命令执行失败。参数错误。</string>
<string name="label_use_built_in_algo">使用内置算法</string>
<string name="label_output_raw_data">输出原始波形</string>
<string name="label_sampling_rate">采样率</string>
<string name="input_interval_value_error_hint">请输入有效值</string>
<!-- ECG -->
<string name="tip_your_ring_do_not_support_ecg">您的戒指型号不支持【心电图测量】!</string>
<string name="btn_start">开始</string>
<string name="btn_stop">停止</string>
<string name="ecg_settings">心电图设置</string>
<string name="paper_speed">时间基准</string>
<string name="paper_speed_value">时间基准:%1$s</string>
<string name="gain">增益</string>
<string name="gain_value">增益:%1$s</string>
<string name="action_generate_pdf">生成PDF文件</string>
<string name="label_countdown">%d 秒</string>
<string name="title_param_settings_in_dev">设备端参数设置</string>
<string name="pdf_ecg_record_time_label">"'记录时间:'yyyy年MM月dd日 HH:mm"</string>
<string name="pdf_ecg_page_footer_label">%s, %s,导联I,512赫,Linktop NexRing</string>
<string name="pdf_data_measure_time_format">"yyyy.MM.dd HH:mm"</string>
<string name="pdf_file_name_format">"'%s' yyyy-MM-dd HH_mm'.pdf'"</string>
<string name="msg_tip_generating_pdf_file">正在生成PDF文件…</string>
<string name="tip_pls_take_an_ecg_first">请先测量一次心电图。</string>
<string name="label_ecg_pga_gain">心电图PGA增益(单位:V/V)</string>
<string name="title_wear_habit">您的戒指现在佩戴于</string>
<string name="item_wear_habit_lf">左手指</string>
<string name="item_wear_habit_rf">右手指</string>
<string name="tip_ecg_lead_on">将另一只手的手指搭在戒指上,以便形成导联并完成心电测量。</string>
<string name="ecg_hr_value">心率 %s BPM</string>
<string name="ecg_avg_hr_value">平均心率 %s BPM</string>
<string name="ecg_arrhythmia_sinus_rhythm">窦性心律</string>
<string name="ecg_arrhythmia_afib">房颤</string>
<string name="ecg_arrhythmia_low_hr">低心率</string>
<string name="ecg_arrhythmia_high_hr">高心率</string>
<string name="ecg_arrhythmia_inconclusive">不确定</string>
<string name="ecg_arrhythmia_poor_recording">记录结果不佳</string>
<string name="ecg_arrhythmia_no_result">无结果</string>
<string name="permission_required">需要权限</string>
<string name="permission_bluetooth_rationale">需要蓝牙权限来扫描和连接设备。请在设置中授予权限。</string>
<string name="permission_bluetooth_denied">没有蓝牙权限,应用可能无法正常工作。您可以在系统设置中手动开启权限。</string>
<string name="dialog_title_oem_auth_failed">OEM 认证失败</string>
<string name="dialog_msg_oem_auth_failed_cause_by_sn_null">设备序列号空,OEM 认证失败。</string>
<string name="dialog_msg_oem_auth_failed_cause_by_r1_to_r2">R1 解密并生成 R2 失败。</string>
<string name="dialog_msg_oem_auth_failed_cause_by_check_r2">
您的戒指已开启 OEM 认证,但本 APP 设定的 OEM 字符串似乎与您的戒指所写入的不匹配,
请检查 Demo 工程的 `res/values/arrays.xml` 的 `oem_array` 中的字符串元素是否覆盖正确?
修改后请重新编译工程生成新的 APK 文件,安装后重试。
如果仍然失败,请联系我们的技术支持。
</string>
</resources>
Loading…
Cancel
Save