@convep_mobilogy/react-native-qms-plugin 1.8.3 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/QmsPlugin.podspec +2 -4
  2. package/QmsPluginFramework.podspec +12 -0
  3. package/README.md +183 -4
  4. package/android/build.gradle +12 -2
  5. package/android/consumer-rules.pro +13 -0
  6. package/ios/QmsPlugin/QmsDashboardViewManager.mm +82 -29
  7. package/ios/QmsPlugin/QmsModule.mm +1296 -1
  8. package/ios/QmsPlugin/UIViewController+QmsAnalytics.m +3 -1
  9. package/lib/module/QmsDashboardView.android.js +121 -9
  10. package/lib/module/QmsDashboardView.android.js.map +1 -1
  11. package/lib/module/QmsPlugin.js +57 -0
  12. package/lib/module/QmsPlugin.js.map +1 -1
  13. package/lib/typescript/src/QmsDashboardView.android.d.ts.map +1 -1
  14. package/lib/typescript/src/QmsPlugin.d.ts +137 -0
  15. package/lib/typescript/src/QmsPlugin.d.ts.map +1 -1
  16. package/lib/typescript/src/index.d.ts +1 -1
  17. package/lib/typescript/src/index.d.ts.map +1 -1
  18. package/package.json +7 -4
  19. package/src/QmsDashboardView.android.tsx +156 -11
  20. package/src/QmsPlugin.ts +312 -0
  21. package/src/index.tsx +13 -1
  22. package/ios/QmsPluginFramework.xcframework/Info.plist +0 -44
  23. package/ios/QmsPluginFramework.xcframework/ios-arm64/QmsPluginFramework.framework/Assets.car +0 -0
  24. package/ios/QmsPluginFramework.xcframework/ios-arm64/QmsPluginFramework.framework/Headers/QmsPlugin.h +0 -43
  25. package/ios/QmsPluginFramework.xcframework/ios-arm64/QmsPluginFramework.framework/Headers/QmsPluginFramework.h +0 -17
  26. package/ios/QmsPluginFramework.xcframework/ios-arm64/QmsPluginFramework.framework/Info.plist +0 -0
  27. package/ios/QmsPluginFramework.xcframework/ios-arm64/QmsPluginFramework.framework/Modules/module.modulemap +0 -6
  28. package/ios/QmsPluginFramework.xcframework/ios-arm64/QmsPluginFramework.framework/Montserrat-Bold.ttf +0 -0
  29. package/ios/QmsPluginFramework.xcframework/ios-arm64/QmsPluginFramework.framework/Montserrat-BoldItalic.ttf +0 -0
  30. package/ios/QmsPluginFramework.xcframework/ios-arm64/QmsPluginFramework.framework/Montserrat-ExtraBold.ttf +0 -0
  31. package/ios/QmsPluginFramework.xcframework/ios-arm64/QmsPluginFramework.framework/Montserrat-ExtraLight.ttf +0 -0
  32. package/ios/QmsPluginFramework.xcframework/ios-arm64/QmsPluginFramework.framework/Montserrat-Italic.ttf +0 -0
  33. package/ios/QmsPluginFramework.xcframework/ios-arm64/QmsPluginFramework.framework/Montserrat-Light.ttf +0 -0
  34. package/ios/QmsPluginFramework.xcframework/ios-arm64/QmsPluginFramework.framework/Montserrat-Medium.ttf +0 -0
  35. package/ios/QmsPluginFramework.xcframework/ios-arm64/QmsPluginFramework.framework/Montserrat-Regular.ttf +0 -0
  36. package/ios/QmsPluginFramework.xcframework/ios-arm64/QmsPluginFramework.framework/Montserrat-SemiBold.ttf +0 -0
  37. package/ios/QmsPluginFramework.xcframework/ios-arm64/QmsPluginFramework.framework/Montserrat-Thin.ttf +0 -0
  38. package/ios/QmsPluginFramework.xcframework/ios-arm64/QmsPluginFramework.framework/QmsPluginFramework +0 -0
  39. package/ios/QmsPluginFramework.xcframework/ios-arm64/QmsPluginFramework.framework/_CodeSignature/CodeResources +0 -289
  40. package/ios/QmsPluginFramework.xcframework/ios-arm64/QmsPluginFramework.framework/bm.json +0 -781
  41. package/ios/QmsPluginFramework.xcframework/ios-arm64/QmsPluginFramework.framework/cn.json +0 -782
  42. package/ios/QmsPluginFramework.xcframework/ios-arm64/QmsPluginFramework.framework/en.json +0 -782
  43. package/ios/QmsPluginFramework.xcframework/ios-arm64_x86_64-simulator/QmsPluginFramework.framework/Assets.car +0 -0
  44. package/ios/QmsPluginFramework.xcframework/ios-arm64_x86_64-simulator/QmsPluginFramework.framework/Headers/QmsPlugin.h +0 -43
  45. package/ios/QmsPluginFramework.xcframework/ios-arm64_x86_64-simulator/QmsPluginFramework.framework/Headers/QmsPluginFramework.h +0 -17
  46. package/ios/QmsPluginFramework.xcframework/ios-arm64_x86_64-simulator/QmsPluginFramework.framework/Info.plist +0 -0
  47. package/ios/QmsPluginFramework.xcframework/ios-arm64_x86_64-simulator/QmsPluginFramework.framework/Modules/module.modulemap +0 -6
  48. package/ios/QmsPluginFramework.xcframework/ios-arm64_x86_64-simulator/QmsPluginFramework.framework/Montserrat-Bold.ttf +0 -0
  49. package/ios/QmsPluginFramework.xcframework/ios-arm64_x86_64-simulator/QmsPluginFramework.framework/Montserrat-BoldItalic.ttf +0 -0
  50. package/ios/QmsPluginFramework.xcframework/ios-arm64_x86_64-simulator/QmsPluginFramework.framework/Montserrat-ExtraBold.ttf +0 -0
  51. package/ios/QmsPluginFramework.xcframework/ios-arm64_x86_64-simulator/QmsPluginFramework.framework/Montserrat-ExtraLight.ttf +0 -0
  52. package/ios/QmsPluginFramework.xcframework/ios-arm64_x86_64-simulator/QmsPluginFramework.framework/Montserrat-Italic.ttf +0 -0
  53. package/ios/QmsPluginFramework.xcframework/ios-arm64_x86_64-simulator/QmsPluginFramework.framework/Montserrat-Light.ttf +0 -0
  54. package/ios/QmsPluginFramework.xcframework/ios-arm64_x86_64-simulator/QmsPluginFramework.framework/Montserrat-Medium.ttf +0 -0
  55. package/ios/QmsPluginFramework.xcframework/ios-arm64_x86_64-simulator/QmsPluginFramework.framework/Montserrat-Regular.ttf +0 -0
  56. package/ios/QmsPluginFramework.xcframework/ios-arm64_x86_64-simulator/QmsPluginFramework.framework/Montserrat-SemiBold.ttf +0 -0
  57. package/ios/QmsPluginFramework.xcframework/ios-arm64_x86_64-simulator/QmsPluginFramework.framework/Montserrat-Thin.ttf +0 -0
  58. package/ios/QmsPluginFramework.xcframework/ios-arm64_x86_64-simulator/QmsPluginFramework.framework/QmsPluginFramework +0 -0
  59. package/ios/QmsPluginFramework.xcframework/ios-arm64_x86_64-simulator/QmsPluginFramework.framework/_CodeSignature/CodeResources +0 -289
  60. package/ios/QmsPluginFramework.xcframework/ios-arm64_x86_64-simulator/QmsPluginFramework.framework/bm.json +0 -781
  61. package/ios/QmsPluginFramework.xcframework/ios-arm64_x86_64-simulator/QmsPluginFramework.framework/cn.json +0 -782
  62. package/ios/QmsPluginFramework.xcframework/ios-arm64_x86_64-simulator/QmsPluginFramework.framework/en.json +0 -782
package/QmsPlugin.podspec CHANGED
@@ -14,16 +14,14 @@ Pod::Spec.new do |s|
14
14
  s.homepage = homepage
15
15
  s.license = package["license"]
16
16
  s.authors = package["author"]
17
- s.platforms = { :ios => "12.4" }
17
+ s.platforms = { :ios => "15.1" }
18
18
 
19
19
  s.source = { :git => "http://convep.ddns.me:20000/WeiHan/weihan-plugin.git", :tag => "#{s.version}" }
20
20
  s.source_files = "ios/QmsPlugin/**/*.{h,m,mm,cpp}", "ios/QmsPluginSpec/**/*.{h,mm}"
21
21
  s.private_header_files = "ios/QmsPlugin/**/*.h"
22
22
  s.public_header_files = "ios/QmsPluginSpec/**/*.h"
23
23
 
24
- s.preserve_paths = "ios/QmsPluginFramework.xcframework"
25
- s.vendored_frameworks = "ios/QmsPluginFramework.xcframework"
26
-
24
+ s.dependency "QmsPluginFramework", "1.31.0"
27
25
  s.dependency "React-Core"
28
26
  s.dependency "ReactCommon"
29
27
  s.dependency "React-Codegen"
@@ -0,0 +1,12 @@
1
+ Pod::Spec.new do |s|
2
+ s.name = "QmsPluginFramework"
3
+ s.version = "1.31.0"
4
+ s.summary = "QMS Native iOS Framework"
5
+ s.description = "Native QMS iOS SDK distributed as XCFramework."
6
+ s.homepage = "https://sdk.convep.com"
7
+ s.license = { :type => "Proprietary", :text => "Copyright © Convep Mobilogy. All rights reserved." }
8
+ s.author = { "Convep Mobilogy" => "developer@convep.com" }
9
+ s.platform = :ios, "15.1"
10
+ s.source = { :http => "https://sdk.convep.com/ios/QMS/qms-plugin/1.31.0", :type => "zip", :sha256 => "6bae9da81e3b0df77a98edf939279290990539e954407dae90904b96192705cf" }
11
+ s.vendored_frameworks = "QmsPluginFramework.xcframework"
12
+ end
package/README.md CHANGED
@@ -2,6 +2,45 @@
2
2
 
3
3
  A React Native plugin that provides QMS (Quality Management System) components and APIs for defect management, dashboard visualization, and QMS workflow operations.
4
4
 
5
+ ## Development setup
6
+
7
+ The development toolchain is managed with [mise](https://mise.jdx.dev/). From
8
+ the repository root, install the pinned Node.js, Java, and Ruby versions and
9
+ the JavaScript dependencies:
10
+
11
+ ```sh
12
+ mise install
13
+ mise run setup
14
+ ```
15
+
16
+ The project pins Yarn through the `packageManager` field in `package.json`; the
17
+ setup task enables Corepack before running the immutable Yarn install. Android
18
+ SDK tooling and Xcode are platform dependencies and are not installed by mise.
19
+
20
+ See [Contributing](CONTRIBUTING.md) for the complete development workflow and
21
+ available mise tasks.
22
+
23
+ To create a separate framework workspace with npm and local-development example
24
+ apps, run:
25
+
26
+ ```sh
27
+ ./setup_framework.sh
28
+ ```
29
+
30
+ The script confirms before moving this repository into a new
31
+ `QmsPlugin_Framework` directory one level above its current location. See the
32
+ contributing guide for the generated layout and non-interactive usage. It adds
33
+ mise configuration to both generated apps and runs `mise install` plus
34
+ `mise run setup` in the plugin and each app.
35
+
36
+ For direct Kotlin/Java and Swift/Objective-C integration with the native AAR
37
+ and XCFramework, see
38
+ [Native QMS SDK Integration for Android and iOS](docs/ANDROID_IOS_INTEGRATION.md).
39
+
40
+ For native Android development, AAR/Maven generation, example-app testing, and
41
+ npm publishing, see
42
+ [Android SDK Development and npm Release](docs/ANDROID_DEVELOPMENT_AND_RELEASE.md).
43
+
5
44
  ---
6
45
 
7
46
  # Installation
@@ -22,20 +61,132 @@ yarn add @convep_mobilogy/react-native-qms-plugin
22
61
 
23
62
  # iOS Setup
24
63
 
25
- Autolinking should set up everything automatically.
64
+ Add the hosted native framework pod to the application `Podfile`. The podspec
65
+ is shipped with the npm package and CocoaPods downloads the matching ZIP from
66
+ `sdk.convep.com`:
67
+
68
+ ```ruby
69
+ qms_plugin_package = Pod::Executable.execute_command('node', ['-p',
70
+ 'require.resolve(
71
+ "@convep_mobilogy/react-native-qms-plugin/package.json",
72
+ {paths: [process.argv[1]]},
73
+ )', __dir__]).strip
74
+
75
+ target 'YourApp' do
76
+ pod 'QmsPluginFramework',
77
+ :path => File.dirname(qms_plugin_package)
78
+ end
79
+ ```
26
80
 
27
- 1. Navigate to your iOS folder:
81
+ Autolinking sets up the React Native wrapper. From the application's root
82
+ directory, install all CocoaPods dependencies:
28
83
 
29
84
  ```sh
30
85
  cd ios
31
86
  pod install
32
87
  ```
33
88
 
89
+ ## iOS framework development
90
+
91
+ Use the following workflow when changing the native iOS framework and testing
92
+ it in the included example app.
93
+
94
+ ### 1. Open the framework project
95
+
96
+ Open the following project in Xcode and make the required native changes:
97
+
98
+ ```text
99
+ framework/ios_framework/QmsPluginFramework.xcodeproj
100
+ ```
101
+
102
+ ### 2. Build and publish the XCFramework
103
+
104
+ From the plugin root, run the build helper. It archives the framework for both
105
+ iOS devices and the iOS Simulator, creates the XCFramework, packages it, and
106
+ uploads the versioned ZIP used by the native CocoaPod:
107
+
108
+ ```sh
109
+ ./compile_framework.sh ios
110
+ ```
111
+
112
+ You can optionally provide the SDK version:
113
+
114
+ ```sh
115
+ ./compile_framework.sh ios <sdk-version>
116
+ ```
117
+
118
+ The versioned XCFramework ZIP and its SHA-256 checksum are written to
119
+ `framework/ios_framework/build/releases/<sdk-version>/`. The root
120
+ `QmsPluginFramework.podspec` is updated for the release. The native pod has no
121
+ React dependency. The command uploads the ZIP to
122
+ `https://sdk.convep.com/api/uploadIOS` using the same credentials and upload
123
+ fields as Android. See [the iOS release guide](docs/IOS_COCOAPODS_RELEASE.md)
124
+ for upload settings and the client Podfile.
125
+
126
+ To build the XCFramework manually instead, run the following commands:
127
+
128
+ ```sh
129
+ cd framework/ios_framework
130
+ rm -rf QmsPluginFramework.xcframework
131
+
132
+ xcodebuild archive \
133
+ -project QmsPluginFramework.xcodeproj \
134
+ -scheme QmsPluginFramework \
135
+ -destination "generic/platform=iOS" \
136
+ -archivePath ./build/ios_devices.xcarchive \
137
+ SKIP_INSTALL=NO \
138
+ BUILD_LIBRARY_FOR_DISTRIBUTION=YES
139
+
140
+ xcodebuild archive \
141
+ -project QmsPluginFramework.xcodeproj \
142
+ -scheme QmsPluginFramework \
143
+ -destination "generic/platform=iOS Simulator" \
144
+ -archivePath ./build/ios_simulators.xcarchive \
145
+ SKIP_INSTALL=NO \
146
+ BUILD_LIBRARY_FOR_DISTRIBUTION=YES
147
+
148
+ xcodebuild -create-xcframework \
149
+ -framework ./build/ios_devices.xcarchive/Products/Library/Frameworks/QmsPluginFramework.framework \
150
+ -framework ./build/ios_simulators.xcarchive/Products/Library/Frameworks/QmsPluginFramework.framework \
151
+ -output ./QmsPluginFramework.xcframework
152
+ ```
153
+
154
+ ### 3. Install the example app's iOS dependencies
155
+
156
+ From the plugin root, run this after the uploaded version is available so
157
+ CocoaPods downloads the latest native build:
158
+
159
+ ```sh
160
+ cd example/ios
161
+ pod install
162
+ ```
163
+
164
+ ### 4. Start Metro
165
+
166
+ In a separate terminal, start Metro from the plugin root:
167
+
168
+ ```sh
169
+ cd example
170
+ npx react-native start
171
+ ```
172
+
173
+ ### 5. Build and run the example app
174
+
175
+ Open the CocoaPods workspace (not the `.xcodeproj`) and run the desired iOS
176
+ Simulator or connected device:
177
+
178
+ ```text
179
+ example/ios/QmsPluginExample.xcworkspace
180
+ ```
181
+
182
+ Repeat steps 2 through 5 after each native framework change.
183
+
34
184
  ---
35
185
 
36
186
  # Android Setup
37
187
 
38
- Autolinking should set up everything automatically, but you need to add the private Maven repo and enable core library desugaring.
188
+ Autolinking should set up everything automatically, but you need to add the QMS
189
+ Maven repository and enable core library desugaring.
39
190
 
40
191
  1. In `android/build.gradle`, add the Maven repo:
41
192
 
@@ -47,7 +198,10 @@ allprojects {
47
198
  maven { url = uri("https://www.jitpack.io") }
48
199
 
49
200
  maven {
50
- url = uri("https://raw.githubusercontent.com/ConvepMobilogy/QMS_framework/main/maven-repo")
201
+ url = uri("https://sdk.convep.com/maven")
202
+ content {
203
+ includeGroup("com.convep.qms")
204
+ }
51
205
  }
52
206
  }
53
207
  }
@@ -125,6 +279,31 @@ const styles = StyleSheet.create({
125
279
 
126
280
  ---
127
281
 
282
+ ## Open Add Issue Directly
283
+
284
+ Use `QmsPlugin.openAddIssue` when the host app already knows the target unit and wants to skip the QMS project/unit/dashboard flow.
285
+
286
+ ```tsx
287
+ import { QmsPlugin } from '@convep_mobilogy/react-native-qms-plugin';
288
+
289
+ async function handleAddIssuePress() {
290
+ await QmsPlugin.openAddIssue({
291
+ userToken: '<UserToken>',
292
+ clientID: '<ClientID>',
293
+ clientCode: '<CompanyCode>',
294
+ isOrigin: false,
295
+ drawingPlanId: 1183,
296
+ projectId: 4,
297
+ unitName: 'C-L(7)',
298
+ projectName: 'Ara E Residence Dev',
299
+ });
300
+ }
301
+ ```
302
+
303
+ `drawingPlanId` is required because Add Issue needs a selected unit plan. `projectId`, `unitName`, and `projectName` are optional but recommended so the SDK can display and cache the correct context.
304
+
305
+ ---
306
+
128
307
  # Props
129
308
 
130
309
  ## QmsDashboardView Props
@@ -21,6 +21,16 @@ apply plugin: "kotlin-android"
21
21
 
22
22
  apply plugin: "com.facebook.react"
23
23
 
24
+ def qmsLocalMavenRepo = uri("$projectDir/maven-repo")
25
+
26
+ // Make the bundled native framework available to the consuming app. This lets
27
+ // local TGZ installs use the matching AAR without waiting for remote publishing.
28
+ rootProject.allprojects {
29
+ repositories {
30
+ maven { url = qmsLocalMavenRepo }
31
+ }
32
+ }
33
+
24
34
  def getExtOrIntegerDefault(name) {
25
35
  return rootProject.ext.has(name) ? rootProject.ext.get(name) : (project.properties["QmsPlugin_" + name]).toInteger()
26
36
  }
@@ -33,6 +43,7 @@ android {
33
43
  defaultConfig {
34
44
  minSdkVersion getExtOrIntegerDefault("minSdkVersion")
35
45
  targetSdkVersion getExtOrIntegerDefault("targetSdkVersion")
46
+ consumerProguardFiles "consumer-rules.pro"
36
47
  }
37
48
 
38
49
  buildFeatures {
@@ -75,8 +86,7 @@ dependencies {
75
86
  implementation "com.facebook.react:react-android"
76
87
  implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
77
88
  implementation 'com.facebook.react:react-native:+'
78
- implementation "com.convep.qms:qms-plugin:1.25.2"
79
-
89
+ implementation "com.convep.qms:qms-plugin:1.31.0"
80
90
  implementation 'androidx.appcompat:appcompat:1.6.1'
81
91
 
82
92
  implementation 'com.squareup.okhttp3:logging-interceptor:4.12.0'
@@ -0,0 +1,13 @@
1
+ # React Native creates this package from the generated PackageList. Keep the
2
+ # package and its native module entry points stable in minified host apps.
3
+ -keep class com.qmsplugin.QmsPluginPackage { *; }
4
+ -keep class com.qmsplugin.QmsPluginModule { *; }
5
+
6
+ # The wrapper returns the native framework module to React Native. ReactMethod
7
+ # methods are invoked through bridge metadata rather than ordinary Java calls.
8
+ -keep class com.convep.qms.QmsModule { *; }
9
+ -keepclassmembers class com.convep.qms.** {
10
+ @com.facebook.react.bridge.ReactMethod <methods>;
11
+ }
12
+
13
+ -keepattributes RuntimeVisibleAnnotations,RuntimeVisibleParameterAnnotations,AnnotationDefault
@@ -72,9 +72,12 @@ static NSString * const kQmsRoutePayloadNotification = @"QmsRoutePayloadNotifica
72
72
 
73
73
  - (instancetype)initWithFrame:(CGRect)frame {
74
74
  if (self = [super initWithFrame:frame]) {
75
- _dashboardVC = [QmsPluginUI makeViewController];
75
+ // The controller must only be created after this native view is mounted.
76
+ // Creating it here lets credential props start the API flow before React
77
+ // has forwarded isOrigin, leaving a detached controller making requests.
78
+ _dashboardVC = nil;
76
79
  _didSetupVC = NO;
77
- _dashboardVC.view.backgroundColor = [UIColor whiteColor];
80
+ _isOrigin = NO;
78
81
 
79
82
  // Listen for native dashboard close notification
80
83
  [[NSNotificationCenter defaultCenter] addObserver:self
@@ -114,6 +117,7 @@ static NSString * const kQmsRoutePayloadNotification = @"QmsRoutePayloadNotifica
114
117
 
115
118
  // 1️⃣ create VC with props
116
119
  _dashboardVC = [QmsPluginUI makeViewController];
120
+ _dashboardVC.view.backgroundColor = [UIColor whiteColor];
117
121
 
118
122
  [parentVC addChildViewController:_dashboardVC];
119
123
  _dashboardVC.view.frame = parentVC.view.bounds;
@@ -177,8 +181,13 @@ static NSString * const kQmsRoutePayloadNotification = @"QmsRoutePayloadNotifica
177
181
  }
178
182
 
179
183
  - (void)cleanupDashboard {
184
+ // APIConfig is process-global inside the framework. Reset it before
185
+ // teardown so a later embedded dashboard cannot inherit origin mode.
186
+ _isOrigin = NO;
180
187
  if (!_dashboardVC) return;
181
188
 
189
+ [self forwardProp:@"isOrigin" value:@NO];
190
+
182
191
  [_dashboardVC willMoveToParentViewController:nil];
183
192
  [_dashboardVC.view removeFromSuperview];
184
193
  [_dashboardVC removeFromParentViewController];
@@ -268,6 +277,9 @@ static NSString * const kQmsRoutePayloadNotification = @"QmsRoutePayloadNotifica
268
277
  }
269
278
 
270
279
  - (void)forwardPropsToVC {
280
+ // Apply mode before credentials. The token setter can make the project
281
+ // controller ready and start its API chain immediately.
282
+ [self forwardProp:@"isOrigin" value:@(_isOrigin)];
271
283
  [self forwardProp:@"ClientID" value:_ClientID];
272
284
  [self forwardProp:@"ClientCode" value:_ClientCode];
273
285
  [self forwardProp:@"user_token" value:_user_token];
@@ -277,7 +289,6 @@ static NSString * const kQmsRoutePayloadNotification = @"QmsRoutePayloadNotifica
277
289
  [self forwardProp:@"headerIconColor" value:_headerIconColor];
278
290
  [self forwardProp:@"accentColor" value:_accentColor];
279
291
  [self forwardProp:@"fontFamily" value:_fontFamily];
280
- [self forwardProp:@"isOrigin" value:@(_isOrigin)];
281
292
  [self forwardProp:@"versionNumber" value:_versionNumber];
282
293
  [self applyAllThemeImmediatelyIfNeeded];
283
294
  }
@@ -299,7 +310,7 @@ static NSString * const kQmsRoutePayloadNotification = @"QmsRoutePayloadNotifica
299
310
  @"new_issue", @"new_info", @"accept_issue", @"reject_issue", @"decline_issue",
300
311
  @"start_fix", @"redo", @"completed", @"close_internal", @"close_external",
301
312
  @"pending_owner_acceptance", @"assign_issue", @"reassign_issue", @"poa_reminder",
302
- @"auto_close_reminder", @"pending_access_reminder", @"overdue_reminder",
313
+ @"auto_close_reminder", @"overdue_reminder",
303
314
  @"issue_summary_reminder", @"lodge_summary_reminder", @"overdue_summary_reminder"
304
315
  ]];
305
316
  });
@@ -311,8 +322,15 @@ static NSString * const kQmsRoutePayloadNotification = @"QmsRoutePayloadNotifica
311
322
  [type isEqualToString:@"unattend_appt_reminder"];
312
323
  }
313
324
 
314
- - (BOOL)isDraftReminderType:(NSString *)type {
315
- return [type isEqualToString:@"draft_reminder"];
325
+ - (BOOL)isDashboardType:(NSString *)type {
326
+ static NSSet<NSString *> *types;
327
+ static dispatch_once_t onceToken;
328
+ dispatch_once(&onceToken, ^{
329
+ types = [NSSet setWithArray:@[
330
+ @"pending_access_reminder", @"completion_letter", @"key_collection", @"defect_summary", @"clearance_letter"
331
+ ]];
332
+ });
333
+ return type.length > 0 && [types containsObject:type];
316
334
  }
317
335
 
318
336
  - (void)applyPayloadContext:(NSDictionary *)payload {
@@ -429,23 +447,9 @@ static NSString * const kQmsRoutePayloadNotification = @"QmsRoutePayloadNotifica
429
447
  UIViewController *presenter = self.dashboardVC;
430
448
  if (!presenter) return;
431
449
 
432
- Class cls = NSClassFromString(@"DraftListingViewController");
433
- if (!cls) {
434
- NSLog(@"🧭 [DraftReminder] DraftListingViewController class not found");
435
- return;
436
- }
437
-
438
- UIViewController *vc = [[cls alloc] init];
439
- if (!vc) return;
440
-
441
- // Pass project name if available so the listing filters to the right project
442
- NSString *projectName = payload[@"project_name"];
443
- if (projectName.length > 0) {
444
- @try { [vc setValue:projectName forKey:@"projectName"]; }
445
- @catch (__unused NSException *e) {}
446
- }
447
-
448
- [self presentDeepLinkVC:vc];
450
+ // Delegate to QmsPluginUI which handles account switching + presentation.
451
+ // AccountManager and DraftListingViewController are both inside the framework.
452
+ [QmsPluginUI presentDraftListingFromPayload:payload fromViewController:presenter];
449
453
  }
450
454
 
451
455
  - (void)routePayloadIfNeeded:(NSDictionary *)payload force:(BOOL)force {
@@ -485,13 +489,12 @@ static NSString * const kQmsRoutePayloadNotification = @"QmsRoutePayloadNotifica
485
489
  } else if ([self isAppointmentType:type]) {
486
490
  NSLog(@"🧭 [QmsDashboardView] routing → presentAppointmentFromPayload (appointment type: %@)", type);
487
491
  [self presentAppointmentDetailsViewControllerWithPayload:payload title:title ?: @""];
488
- } else if ([type isEqualToString:@"clearance_letter"]) {
489
- NSLog(@"🧭 [QmsDashboardView] routing → presentDashboardFromPayload (clearance_letter)");
492
+ } else if ([self isDashboardType:type]) {
493
+ NSLog(@"🧭 [QmsDashboardView] routing → presentDashboardFromPayload (dashboard type: %@)", type);
490
494
  [self presentDashboardViewControllerWithPayload:payload title:title ?: @""];
491
- } else if ([self isDraftReminderType:type]) {
495
+ } else if ([type isEqualToString:@"draft_reminder"]) {
492
496
  NSLog(@"🧭 [QmsDashboardView] routing → presentDraftListingViewController (draft_reminder)");
493
- // [self presentDraftListingViewControllerWithPayload:payload];
494
- [self presentDashboardViewControllerWithPayload:payload title:title ?: @""];
497
+ [self presentDraftListingViewControllerWithPayload:payload];
495
498
  } else {
496
499
  NSLog(@"🧭 [QmsDashboardView] routePayloadIfNeeded — no route matched for type=%@", type ?: @"(none)");
497
500
  }
@@ -544,6 +547,12 @@ static NSString * const kQmsRoutePayloadNotification = @"QmsRoutePayloadNotifica
544
547
  - (void)applyHeaderThemeRecursivelyOnView:(UIView *)view textColor:(UIColor *)textColor iconColor:(UIColor *)iconColor {
545
548
  if (!view) return;
546
549
 
550
+ // Skip views tagged 9001 and their entire subtree — they manage their own colour.
551
+ if (view.tag == 9001) return;
552
+
553
+ // Never reach into UISegmentedControl internals.
554
+ if ([view isKindOfClass:[UISegmentedControl class]]) return;
555
+
547
556
  if (textColor && [view isKindOfClass:[UILabel class]]) {
548
557
  UILabel *label = (UILabel *)view;
549
558
  CGPoint originInRoot = [label.superview convertPoint:label.frame.origin toView:_dashboardVC.view];
@@ -588,6 +597,13 @@ static NSString * const kQmsRoutePayloadNotification = @"QmsRoutePayloadNotifica
588
597
  - (void)applyThemeRecursivelyOnView:(UIView *)view color:(UIColor *)themeColor {
589
598
  if (!view || !themeColor) return;
590
599
 
600
+ // Skip views tagged 9001 and their entire subtree — they manage their own colour.
601
+ if (view.tag == 9001) return;
602
+
603
+ // Never reach into UISegmentedControl internals — its tint/selected colour
604
+ // is set directly via selectedSegmentTintColor and must not be overwritten here.
605
+ if ([view isKindOfClass:[UISegmentedControl class]]) return;
606
+
591
607
  CGPoint originInRoot = [view.superview convertPoint:view.frame.origin toView:_dashboardVC.view];
592
608
  BOOL nearHeader = originInRoot.y <= 140.0 && view.bounds.size.height <= 120.0;
593
609
  if (nearHeader && [self isDefaultQmsBlueColor:view.backgroundColor]) {
@@ -644,6 +660,16 @@ static NSString * const kQmsRoutePayloadNotification = @"QmsRoutePayloadNotifica
644
660
  - (void)applyAccentRecursivelyOnView:(UIView *)view color:(UIColor *)color {
645
661
  if (!view || !color) return;
646
662
 
663
+ // Skip views (and their entire subtree) that have been tagged 9001 —
664
+ // this lets individual controls opt-out of theme colour replacement.
665
+ if (view.tag == 9001) return;
666
+
667
+ if ([view isKindOfClass:[UISegmentedControl class]]) {
668
+ // Never touch the internals of a UISegmentedControl — its
669
+ // selectedSegmentTintColor is managed by the VC that owns it.
670
+ return;
671
+ }
672
+
647
673
  if ([view isKindOfClass:[UIButton class]]) {
648
674
  UIButton *button = (UIButton *)view;
649
675
  button.tintColor = color;
@@ -798,7 +824,10 @@ RCT_EXPORT_MODULE();
798
824
  @"onAnalyticsScreen",
799
825
  @"onAnalyticsEvent",
800
826
  @"QmsSdkOnAnalyticsScreen",
801
- @"QmsSdkOnAnalyticsEvent"
827
+ @"QmsSdkOnAnalyticsEvent",
828
+ @"onDraftIssueCreated",
829
+ @"onDraftIssueDeleted",
830
+ @"onPoaSignOffCompleted"
802
831
  ];
803
832
  }
804
833
 
@@ -826,6 +855,18 @@ RCT_EXPORT_MODULE();
826
855
  selector:@selector(handleActiveAccountChangeEmitter:)
827
856
  name:@"QmsActiveAccountDidChangeNotification"
828
857
  object:nil];
858
+ [center addObserver:self
859
+ selector:@selector(handleDraftIssueCreated:)
860
+ name:@"QmsDraftIssueCreatedNotification"
861
+ object:nil];
862
+ [center addObserver:self
863
+ selector:@selector(handleDraftIssueDeleted:)
864
+ name:@"QmsDraftIssueDeletedNotification"
865
+ object:nil];
866
+ [center addObserver:self
867
+ selector:@selector(handlePoaSignOffCompleted:)
868
+ name:@"QmsPoaSignOffCompleted"
869
+ object:nil];
829
870
  }
830
871
 
831
872
  - (void)stopObserving {
@@ -855,6 +896,18 @@ RCT_EXPORT_MODULE();
855
896
  [self sendEventWithName:@"onActiveAccountChange" body:note.userInfo];
856
897
  }
857
898
 
899
+ - (void)handleDraftIssueCreated:(NSNotification *)note {
900
+ [self sendEventWithName:@"onDraftIssueCreated" body:note.userInfo];
901
+ }
902
+
903
+ - (void)handleDraftIssueDeleted:(NSNotification *)note {
904
+ [self sendEventWithName:@"onDraftIssueDeleted" body:note.userInfo];
905
+ }
906
+
907
+ - (void)handlePoaSignOffCompleted:(NSNotification *)note {
908
+ [self sendEventWithName:@"onPoaSignOffCompleted" body:note.userInfo ?: @{}];
909
+ }
910
+
858
911
  - (void)dealloc {
859
912
  [self stopObserving];
860
913
  }