@convep_mobilogy/react-native-qms-plugin 0.10.4 → 0.10.6

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 (27) hide show
  1. package/android/build.gradle +1 -1
  2. package/ios/QmsPlugin/QmsDashboardViewManager.mm +408 -2
  3. package/ios/QmsPlugin/QmsModule.mm +221 -0
  4. package/ios/QmsPlugin/UIViewController+QmsAnalytics.m +292 -19
  5. package/ios/QmsPluginFramework.xcframework/Info.plist +5 -5
  6. package/ios/QmsPluginFramework.xcframework/ios-arm64/QmsPluginFramework.framework/Assets.car +0 -0
  7. package/ios/QmsPluginFramework.xcframework/ios-arm64/QmsPluginFramework.framework/Info.plist +0 -0
  8. package/ios/QmsPluginFramework.xcframework/ios-arm64/QmsPluginFramework.framework/Montserrat-Bold.ttf +0 -0
  9. package/ios/QmsPluginFramework.xcframework/ios-arm64/QmsPluginFramework.framework/Montserrat-Medium.ttf +0 -0
  10. package/ios/QmsPluginFramework.xcframework/ios-arm64/QmsPluginFramework.framework/Montserrat-Regular.ttf +0 -0
  11. package/ios/QmsPluginFramework.xcframework/ios-arm64/QmsPluginFramework.framework/QmsPluginFramework +0 -0
  12. package/ios/QmsPluginFramework.xcframework/ios-arm64/QmsPluginFramework.framework/_CodeSignature/CodeResources +36 -3
  13. package/ios/QmsPluginFramework.xcframework/ios-arm64_x86_64-simulator/QmsPluginFramework.framework/Assets.car +0 -0
  14. package/ios/QmsPluginFramework.xcframework/ios-arm64_x86_64-simulator/QmsPluginFramework.framework/Info.plist +0 -0
  15. package/ios/QmsPluginFramework.xcframework/ios-arm64_x86_64-simulator/QmsPluginFramework.framework/Montserrat-Bold.ttf +0 -0
  16. package/ios/QmsPluginFramework.xcframework/ios-arm64_x86_64-simulator/QmsPluginFramework.framework/Montserrat-Medium.ttf +0 -0
  17. package/ios/QmsPluginFramework.xcframework/ios-arm64_x86_64-simulator/QmsPluginFramework.framework/Montserrat-Regular.ttf +0 -0
  18. package/ios/QmsPluginFramework.xcframework/ios-arm64_x86_64-simulator/QmsPluginFramework.framework/QmsPluginFramework +0 -0
  19. package/ios/QmsPluginFramework.xcframework/ios-arm64_x86_64-simulator/QmsPluginFramework.framework/_CodeSignature/CodeResources +36 -3
  20. package/lib/module/QmsDashboardView.android.js +16 -16
  21. package/lib/module/QmsDashboardView.android.js.map +1 -1
  22. package/lib/module/QmsDashboardView.ios.js +38 -12
  23. package/lib/module/QmsDashboardView.ios.js.map +1 -1
  24. package/lib/typescript/src/QmsDashboardView.ios.d.ts.map +1 -1
  25. package/package.json +1 -1
  26. package/src/QmsDashboardView.android.tsx +16 -16
  27. package/src/QmsDashboardView.ios.tsx +64 -25
@@ -0,0 +1,221 @@
1
+ #import <Foundation/Foundation.h>
2
+ #import <React/RCTBridgeModule.h>
3
+ #import <UIKit/UIKit.h>
4
+ #import <objc/runtime.h>
5
+ #import <QmsPluginFramework/QmsPlugin.h>
6
+
7
+ static NSString * const kQmsThemePrimaryColorKey = @"QmsThemePrimaryColor";
8
+ static NSString * const kQmsThemeHeaderTextColorKey = @"QmsThemeHeaderTextColor";
9
+ static NSString * const kQmsThemeHeaderIconColorKey = @"QmsThemeHeaderIconColor";
10
+ static NSString * const kQmsThemeAccentColorKey = @"QmsThemeAccentColor";
11
+ static NSString * const kQmsThemeFontFamilyKey = @"QmsThemeFontFamily";
12
+ static NSString * const kQmsRoutePayloadNotification = @"QmsRoutePayloadNotification";
13
+
14
+ @interface QmsModule : NSObject <RCTBridgeModule>
15
+ @property (nonatomic, strong) UIView *bannerView;
16
+ @end
17
+
18
+ @implementation QmsModule
19
+
20
+ RCT_EXPORT_MODULE(QmsModule)
21
+
22
+ - (dispatch_queue_t)methodQueue {
23
+ return dispatch_get_main_queue();
24
+ }
25
+
26
+ - (UIViewController *)topViewController {
27
+ UIWindow *keyWindow = UIApplication.sharedApplication.keyWindow;
28
+ if (!keyWindow) {
29
+ for (UIScene *scene in UIApplication.sharedApplication.connectedScenes) {
30
+ if (![scene isKindOfClass:[UIWindowScene class]]) continue;
31
+ UIWindowScene *windowScene = (UIWindowScene *)scene;
32
+ if (windowScene.activationState != UISceneActivationStateForegroundActive) continue;
33
+ for (UIWindow *window in windowScene.windows) {
34
+ if (window.isKeyWindow) {
35
+ keyWindow = window;
36
+ break;
37
+ }
38
+ }
39
+ if (keyWindow) break;
40
+ }
41
+ }
42
+ UIViewController *vc = keyWindow.rootViewController;
43
+ while (vc.presentedViewController) vc = vc.presentedViewController;
44
+ return vc;
45
+ }
46
+
47
+ - (void)applyDashboardPropsToViewController:(UIViewController *)vc
48
+ userToken:(NSString *)userToken
49
+ clientID:(NSString *)clientID
50
+ clientCode:(NSString *)clientCode
51
+ isOrigin:(BOOL)isOrigin {
52
+ if (!vc) return;
53
+ @try {
54
+ [vc setValue:(clientID ?: @"") forKey:@"ClientID"];
55
+ [vc setValue:(clientCode ?: @"") forKey:@"ClientCode"];
56
+ [vc setValue:(userToken ?: @"") forKey:@"user_token"];
57
+ [vc setValue:@(isOrigin) forKey:@"isOrigin"];
58
+ } @catch (__unused NSException *exception) {}
59
+ }
60
+
61
+ - (void)routePayload:(NSDictionary *)payload {
62
+ if (![payload isKindOfClass:[NSDictionary class]] || payload.count == 0) return;
63
+ [[NSNotificationCenter defaultCenter] postNotificationName:kQmsRoutePayloadNotification
64
+ object:nil
65
+ userInfo:payload];
66
+ }
67
+
68
+ RCT_EXPORT_METHOD(showQms:(NSString *)userToken
69
+ ClientID:(NSString *)clientID
70
+ ClientCode:(NSString *)clientCode
71
+ isOrigin:(BOOL)isOrigin) {
72
+ UIViewController *presenter = [self topViewController];
73
+ if (!presenter) return;
74
+
75
+ UIViewController *vc = [QmsPluginUI makeViewController];
76
+ [self applyDashboardPropsToViewController:vc
77
+ userToken:userToken
78
+ clientID:clientID
79
+ clientCode:clientCode
80
+ isOrigin:isOrigin];
81
+
82
+ [presenter presentViewController:vc animated:YES completion:nil];
83
+ }
84
+
85
+ RCT_EXPORT_METHOD(showQmsWithPayload:(NSString *)userToken
86
+ ClientID:(NSString *)clientID
87
+ ClientCode:(NSString *)clientCode
88
+ isOrigin:(BOOL)isOrigin
89
+ payload:(NSDictionary *)payload) {
90
+ UIViewController *presenter = [self topViewController];
91
+ if (!presenter) return;
92
+
93
+ UIViewController *vc = [QmsPluginUI makeViewController];
94
+ [self applyDashboardPropsToViewController:vc
95
+ userToken:userToken
96
+ clientID:clientID
97
+ clientCode:clientCode
98
+ isOrigin:isOrigin];
99
+
100
+ [presenter presentViewController:vc animated:YES completion:^{
101
+ [self routePayload:payload];
102
+ }];
103
+ }
104
+
105
+ RCT_EXPORT_METHOD(setPrimaryColor:(NSString *)hexColor) {
106
+ NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
107
+ if (hexColor.length > 0) [defaults setObject:hexColor forKey:kQmsThemePrimaryColorKey];
108
+ else [defaults removeObjectForKey:kQmsThemePrimaryColorKey];
109
+ }
110
+
111
+ RCT_EXPORT_METHOD(setHeaderTextColor:(NSString *)hexColor) {
112
+ NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
113
+ if (hexColor.length > 0) [defaults setObject:hexColor forKey:kQmsThemeHeaderTextColorKey];
114
+ else [defaults removeObjectForKey:kQmsThemeHeaderTextColorKey];
115
+ }
116
+
117
+ RCT_EXPORT_METHOD(setHeaderIconColor:(NSString *)hexColor) {
118
+ NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
119
+ if (hexColor.length > 0) [defaults setObject:hexColor forKey:kQmsThemeHeaderIconColorKey];
120
+ else [defaults removeObjectForKey:kQmsThemeHeaderIconColorKey];
121
+ }
122
+
123
+ RCT_EXPORT_METHOD(setAccentColor:(NSString *)hexColor) {
124
+ NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
125
+ if (hexColor.length > 0) [defaults setObject:hexColor forKey:kQmsThemeAccentColorKey];
126
+ else [defaults removeObjectForKey:kQmsThemeAccentColorKey];
127
+ }
128
+
129
+ RCT_EXPORT_METHOD(setFontFamily:(NSString *)fontFamily) {
130
+ NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
131
+ if (fontFamily.length > 0) [defaults setObject:fontFamily forKey:kQmsThemeFontFamilyKey];
132
+ else [defaults removeObjectForKey:kQmsThemeFontFamilyKey];
133
+ }
134
+
135
+ RCT_EXPORT_METHOD(emitInAppNotification:(NSString *)title body:(NSString *)body) {
136
+ [self emitInAppNotificationWithPayload:title body:body data:nil];
137
+ }
138
+
139
+ RCT_EXPORT_METHOD(emitInAppNotificationWithPayload:(NSString *)title
140
+ body:(NSString *)body
141
+ data:(NSDictionary *)data) {
142
+ UIViewController *vc = [self topViewController];
143
+ if (!vc || !vc.view) return;
144
+
145
+ if (self.bannerView) {
146
+ [self.bannerView removeFromSuperview];
147
+ self.bannerView = nil;
148
+ }
149
+
150
+ UIView *banner = [[UIView alloc] init];
151
+ banner.translatesAutoresizingMaskIntoConstraints = NO;
152
+ banner.backgroundColor = [UIColor colorWithWhite:0.07 alpha:1.0];
153
+ banner.layer.cornerRadius = 12.0;
154
+ banner.clipsToBounds = YES;
155
+
156
+ UILabel *titleLabel = [[UILabel alloc] init];
157
+ titleLabel.translatesAutoresizingMaskIntoConstraints = NO;
158
+ titleLabel.textColor = UIColor.whiteColor;
159
+ titleLabel.font = [UIFont boldSystemFontOfSize:16];
160
+ titleLabel.text = (title.length > 0 ? title : @"Notification");
161
+
162
+ UILabel *bodyLabel = [[UILabel alloc] init];
163
+ bodyLabel.translatesAutoresizingMaskIntoConstraints = NO;
164
+ bodyLabel.textColor = [UIColor colorWithWhite:0.90 alpha:1.0];
165
+ bodyLabel.font = [UIFont systemFontOfSize:14];
166
+ bodyLabel.numberOfLines = 2;
167
+ bodyLabel.text = (body ?: @"");
168
+
169
+ [banner addSubview:titleLabel];
170
+ [banner addSubview:bodyLabel];
171
+ [vc.view addSubview:banner];
172
+ self.bannerView = banner;
173
+
174
+ [NSLayoutConstraint activateConstraints:@[
175
+ [banner.topAnchor constraintEqualToAnchor:vc.view.safeAreaLayoutGuide.topAnchor constant:8],
176
+ [banner.leadingAnchor constraintEqualToAnchor:vc.view.leadingAnchor constant:16],
177
+ [banner.trailingAnchor constraintEqualToAnchor:vc.view.trailingAnchor constant:-16],
178
+ [titleLabel.topAnchor constraintEqualToAnchor:banner.topAnchor constant:12],
179
+ [titleLabel.leadingAnchor constraintEqualToAnchor:banner.leadingAnchor constant:12],
180
+ [titleLabel.trailingAnchor constraintEqualToAnchor:banner.trailingAnchor constant:-12],
181
+ [bodyLabel.topAnchor constraintEqualToAnchor:titleLabel.bottomAnchor constant:6],
182
+ [bodyLabel.leadingAnchor constraintEqualToAnchor:banner.leadingAnchor constant:12],
183
+ [bodyLabel.trailingAnchor constraintEqualToAnchor:banner.trailingAnchor constant:-12],
184
+ [bodyLabel.bottomAnchor constraintEqualToAnchor:banner.bottomAnchor constant:-12],
185
+ ]];
186
+
187
+ banner.alpha = 0.0;
188
+ banner.transform = CGAffineTransformMakeTranslation(0, -40);
189
+ [UIView animateWithDuration:0.22 animations:^{
190
+ banner.alpha = 1.0;
191
+ banner.transform = CGAffineTransformIdentity;
192
+ }];
193
+
194
+ if ([data isKindOfClass:[NSDictionary class]] && data.count > 0) {
195
+ banner.userInteractionEnabled = YES;
196
+ UITapGestureRecognizer *tap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleBannerTap:)];
197
+ objc_setAssociatedObject(tap, "qms_payload", data, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
198
+ [banner addGestureRecognizer:tap];
199
+ }
200
+
201
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.5 * NSEC_PER_SEC)),
202
+ dispatch_get_main_queue(), ^{
203
+ if (self.bannerView != banner) return;
204
+ [UIView animateWithDuration:0.2 animations:^{
205
+ banner.alpha = 0.0;
206
+ banner.transform = CGAffineTransformMakeTranslation(0, -40);
207
+ } completion:^(__unused BOOL finished) {
208
+ [banner removeFromSuperview];
209
+ if (self.bannerView == banner) self.bannerView = nil;
210
+ }];
211
+ });
212
+ }
213
+
214
+ - (void)handleBannerTap:(UITapGestureRecognizer *)gesture {
215
+ NSDictionary *payload = objc_getAssociatedObject(gesture, "qms_payload");
216
+ [self routePayload:payload];
217
+ [gesture.view removeFromSuperview];
218
+ if (self.bannerView == gesture.view) self.bannerView = nil;
219
+ }
220
+
221
+ @end
@@ -4,6 +4,28 @@
4
4
 
5
5
  static NSString * const QmsAnalyticsScreenNotification = @"QmsAnalyticsScreenNotification";
6
6
  static NSString * const QmsAnalyticsEventNotification = @"QmsAnalyticsEventNotification";
7
+ static NSString * const QmsThemePrimaryColorKey = @"QmsThemePrimaryColor";
8
+ static NSString * const QmsThemeHeaderTextColorKey = @"QmsThemeHeaderTextColor";
9
+ static NSString * const QmsThemeAccentColorKey = @"QmsThemeAccentColor";
10
+ static NSString * const QmsThemeFontFamilyKey = @"QmsThemeFontFamily";
11
+ static char QmsStatusBarFillViewKey;
12
+
13
+ static void qms_swizzleInstanceMethod(Class cls, SEL originalSelector, SEL swizzledSelector) {
14
+ Method originalMethod = class_getInstanceMethod(cls, originalSelector);
15
+ Method swizzledMethod = class_getInstanceMethod(cls, swizzledSelector);
16
+ BOOL didAddMethod = class_addMethod(cls,
17
+ originalSelector,
18
+ method_getImplementation(swizzledMethod),
19
+ method_getTypeEncoding(swizzledMethod));
20
+ if (didAddMethod) {
21
+ class_replaceMethod(cls,
22
+ swizzledSelector,
23
+ method_getImplementation(originalMethod),
24
+ method_getTypeEncoding(originalMethod));
25
+ } else {
26
+ method_exchangeImplementations(originalMethod, swizzledMethod);
27
+ }
28
+ }
7
29
 
8
30
  @interface UIViewController (QmsAnalytics)
9
31
  @end
@@ -14,28 +36,19 @@ static NSString * const QmsAnalyticsEventNotification = @"QmsAnalyticsEventNotif
14
36
  static dispatch_once_t onceToken;
15
37
  dispatch_once(&onceToken, ^{
16
38
  Class class = [self class];
17
- SEL originalSelector = @selector(viewDidAppear:);
18
- SEL swizzledSelector = @selector(qms_viewDidAppear:);
19
-
20
- Method originalMethod = class_getInstanceMethod(class, originalSelector);
21
- Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
22
-
23
- BOOL didAddMethod =
24
- class_addMethod(class,
25
- originalSelector,
26
- method_getImplementation(swizzledMethod),
27
- method_getTypeEncoding(swizzledMethod));
28
- if (didAddMethod) {
29
- class_replaceMethod(class,
30
- swizzledSelector,
31
- method_getImplementation(originalMethod),
32
- method_getTypeEncoding(originalMethod));
33
- } else {
34
- method_exchangeImplementations(originalMethod, swizzledMethod);
35
- }
39
+ qms_swizzleInstanceMethod(class, @selector(viewWillAppear:), @selector(qms_viewWillAppear:));
40
+ qms_swizzleInstanceMethod(class, @selector(viewDidAppear:), @selector(qms_viewDidAppear:));
41
+ qms_swizzleInstanceMethod(class, @selector(viewDidLayoutSubviews), @selector(qms_viewDidLayoutSubviews));
36
42
  });
37
43
  }
38
44
 
45
+ - (void)qms_viewWillAppear:(BOOL)animated {
46
+ [self qms_viewWillAppear:animated];
47
+ if (![self qms_shouldTrackAnalytics]) return;
48
+ [self qms_applyThemeIfNeeded];
49
+ [self qms_scheduleThemeReapplyPasses];
50
+ }
51
+
39
52
  - (void)qms_viewDidAppear:(BOOL)animated {
40
53
  [self qms_viewDidAppear:animated];
41
54
 
@@ -43,6 +56,9 @@ static NSString * const QmsAnalyticsEventNotification = @"QmsAnalyticsEventNotif
43
56
  return;
44
57
  }
45
58
 
59
+ [self qms_applyThemeIfNeeded];
60
+ [self qms_scheduleThemeReapplyPasses];
61
+
46
62
  NSString *screenName = NSStringFromClass([self class]);
47
63
  NSString *screenClass = screenName;
48
64
 
@@ -67,6 +83,263 @@ static NSString * const QmsAnalyticsEventNotification = @"QmsAnalyticsEventNotif
67
83
  userInfo:eventInfo];
68
84
  }
69
85
 
86
+ - (void)qms_scheduleThemeReapplyPasses {
87
+ __weak typeof(self) weakSelf = self;
88
+ NSArray<NSNumber *> *delays = @[@0.02, @0.12, @0.30, @0.55];
89
+ for (NSNumber *delay in delays) {
90
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delay.doubleValue * NSEC_PER_SEC)),
91
+ dispatch_get_main_queue(), ^{
92
+ __strong typeof(weakSelf) strongSelf = weakSelf;
93
+ if (!strongSelf || !strongSelf.view.window) return;
94
+ if (![strongSelf qms_shouldTrackAnalytics]) return;
95
+ [strongSelf qms_applyThemeIfNeeded];
96
+ });
97
+ }
98
+ }
99
+
100
+ - (void)qms_viewDidLayoutSubviews {
101
+ [self qms_viewDidLayoutSubviews];
102
+ if (![self qms_shouldTrackAnalytics]) return;
103
+ [self qms_applyThemeIfNeeded];
104
+ }
105
+
106
+ - (UIColor *)qms_colorFromHexString:(NSString *)hexString {
107
+ if (hexString.length == 0) return nil;
108
+ NSString *value = [[hexString stringByTrimmingCharactersInSet:
109
+ [NSCharacterSet whitespaceAndNewlineCharacterSet]]
110
+ uppercaseString];
111
+ if ([value hasPrefix:@"#"]) value = [value substringFromIndex:1];
112
+ if (value.length == 3) {
113
+ unichar c0 = [value characterAtIndex:0];
114
+ unichar c1 = [value characterAtIndex:1];
115
+ unichar c2 = [value characterAtIndex:2];
116
+ value = [NSString stringWithFormat:@"%C%C%C%C%C%C", c0, c0, c1, c1, c2, c2];
117
+ }
118
+ if (value.length != 6 && value.length != 8) return nil;
119
+
120
+ unsigned int hex = 0;
121
+ [[NSScanner scannerWithString:value] scanHexInt:&hex];
122
+ CGFloat a = 1.0, r = 0, g = 0, b = 0;
123
+ if (value.length == 8) {
124
+ a = ((hex >> 24) & 0xFF) / 255.0;
125
+ r = ((hex >> 16) & 0xFF) / 255.0;
126
+ g = ((hex >> 8) & 0xFF) / 255.0;
127
+ b = (hex & 0xFF) / 255.0;
128
+ } else {
129
+ r = ((hex >> 16) & 0xFF) / 255.0;
130
+ g = ((hex >> 8) & 0xFF) / 255.0;
131
+ b = (hex & 0xFF) / 255.0;
132
+ }
133
+ return [UIColor colorWithRed:r green:g blue:b alpha:a];
134
+ }
135
+
136
+ - (BOOL)qms_isColor:(UIColor *)color near:(UIColor *)target tolerance:(CGFloat)tolerance {
137
+ if (!color || !target) return NO;
138
+ CGFloat r1 = 0, g1 = 0, b1 = 0, a1 = 0;
139
+ CGFloat r2 = 0, g2 = 0, b2 = 0, a2 = 0;
140
+ if (![color getRed:&r1 green:&g1 blue:&b1 alpha:&a1]) return NO;
141
+ if (![target getRed:&r2 green:&g2 blue:&b2 alpha:&a2]) return NO;
142
+ return (fabs(r1 - r2) <= tolerance &&
143
+ fabs(g1 - g2) <= tolerance &&
144
+ fabs(b1 - b2) <= tolerance &&
145
+ fabs(a1 - a2) <= tolerance);
146
+ }
147
+
148
+ - (BOOL)qms_isDefaultPrimaryColor:(UIColor *)color {
149
+ UIColor *primaryA = [UIColor colorWithRed:0.0 green:0.50 blue:0.60 alpha:1.0];
150
+ UIColor *primaryB = [UIColor colorWithRed:0.0 green:0.43 blue:0.55 alpha:1.0];
151
+ return [self qms_isColor:color near:primaryA tolerance:0.18] ||
152
+ [self qms_isColor:color near:primaryB tolerance:0.18];
153
+ }
154
+
155
+ - (void)qms_applyPrimaryRecursively:(UIView *)view themeColor:(UIColor *)themeColor {
156
+ if (!view || !themeColor) return;
157
+ if ([self qms_isDefaultPrimaryColor:view.backgroundColor]) {
158
+ view.backgroundColor = themeColor;
159
+ }
160
+ for (UIView *subview in view.subviews) {
161
+ [self qms_applyPrimaryRecursively:subview themeColor:themeColor];
162
+ }
163
+ }
164
+
165
+ - (BOOL)qms_hasPrimaryAncestor:(UIView *)view primaryColor:(UIColor *)primaryColor {
166
+ UIView *current = view;
167
+ while (current) {
168
+ if ([self qms_isColor:current.backgroundColor near:primaryColor tolerance:0.08]) {
169
+ return YES;
170
+ }
171
+ current = current.superview;
172
+ }
173
+ return NO;
174
+ }
175
+
176
+ - (void)qms_applyForegroundRecursively:(UIView *)view
177
+ primaryColor:(UIColor *)primaryColor
178
+ headerTextColor:(UIColor *)headerTextColor
179
+ accentColor:(UIColor *)accentColor {
180
+ if (!view) return;
181
+
182
+ BOOL onPrimary = [self qms_hasPrimaryAncestor:view primaryColor:primaryColor];
183
+ CGPoint origin = CGPointZero;
184
+ if (view.superview) {
185
+ origin = [view.superview convertPoint:view.frame.origin toView:self.view];
186
+ }
187
+ BOOL nearHeader = origin.y <= 170.0;
188
+ UIColor *resolved = nil;
189
+ if (onPrimary) {
190
+ if (nearHeader && headerTextColor) {
191
+ resolved = headerTextColor;
192
+ } else {
193
+ resolved = accentColor ?: headerTextColor;
194
+ }
195
+ }
196
+
197
+ if (resolved) {
198
+ if ([view isKindOfClass:[UILabel class]]) {
199
+ ((UILabel *)view).textColor = resolved;
200
+ } else if ([view isKindOfClass:[UIButton class]]) {
201
+ UIButton *button = (UIButton *)view;
202
+ button.tintColor = resolved;
203
+ [button setTitleColor:resolved forState:UIControlStateNormal];
204
+ } else if ([view isKindOfClass:[UIImageView class]]) {
205
+ ((UIImageView *)view).tintColor = resolved;
206
+ }
207
+ }
208
+
209
+ for (UIView *subview in view.subviews) {
210
+ [self qms_applyForegroundRecursively:subview
211
+ primaryColor:primaryColor
212
+ headerTextColor:headerTextColor
213
+ accentColor:accentColor];
214
+ }
215
+ }
216
+
217
+ - (void)qms_applyThemeIfNeeded {
218
+ NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
219
+ UIColor *themeColor = [self qms_colorFromHexString:[defaults stringForKey:QmsThemePrimaryColorKey]];
220
+ UIColor *headerTextColor = [self qms_colorFromHexString:[defaults stringForKey:QmsThemeHeaderTextColorKey]];
221
+ UIColor *accentColor = [self qms_colorFromHexString:[defaults stringForKey:QmsThemeAccentColorKey]];
222
+ NSString *fontFamily = [defaults stringForKey:QmsThemeFontFamilyKey];
223
+ if (themeColor) {
224
+ [self qms_applyPrimaryRecursively:self.view themeColor:themeColor];
225
+ [self qms_applyForegroundRecursively:self.view
226
+ primaryColor:themeColor
227
+ headerTextColor:headerTextColor
228
+ accentColor:accentColor];
229
+ }
230
+ if (fontFamily.length > 0) {
231
+ [self qms_applyFontRecursively:self.view fontFamily:fontFamily];
232
+ } else {
233
+ [self qms_resetFontRecursively:self.view];
234
+ }
235
+ [self qms_applyStatusBarFillWithColor:themeColor];
236
+ }
237
+
238
+ - (void)qms_applyStatusBarFillWithColor:(UIColor *)themeColor {
239
+ UIColor *primary = themeColor ?: [UIColor colorWithRed:0.0 green:0.43 blue:0.55 alpha:1.0];
240
+ UIView *fillView = objc_getAssociatedObject(self, &QmsStatusBarFillViewKey);
241
+ if (!fillView) {
242
+ fillView = [[UIView alloc] init];
243
+ fillView.translatesAutoresizingMaskIntoConstraints = NO;
244
+ fillView.userInteractionEnabled = NO;
245
+ fillView.tag = 938477;
246
+ [self.view insertSubview:fillView atIndex:0];
247
+ [NSLayoutConstraint activateConstraints:@[
248
+ [fillView.topAnchor constraintEqualToAnchor:self.view.topAnchor],
249
+ [fillView.leadingAnchor constraintEqualToAnchor:self.view.leadingAnchor],
250
+ [fillView.trailingAnchor constraintEqualToAnchor:self.view.trailingAnchor],
251
+ [fillView.bottomAnchor constraintEqualToAnchor:self.view.safeAreaLayoutGuide.topAnchor]
252
+ ]];
253
+ objc_setAssociatedObject(self, &QmsStatusBarFillViewKey, fillView, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
254
+ }
255
+ fillView.backgroundColor = primary;
256
+ }
257
+
258
+ - (UIFont *)qms_fontByPreservingStyleFromFont:(UIFont *)sourceFont fontFamily:(NSString *)fontFamily {
259
+ if (!sourceFont || fontFamily.length == 0) return sourceFont;
260
+ UIFont *base = [UIFont fontWithName:fontFamily size:sourceFont.pointSize];
261
+ if (!base) return sourceFont;
262
+
263
+ UIFontDescriptorSymbolicTraits traits = sourceFont.fontDescriptor.symbolicTraits;
264
+ UIFontDescriptor *descriptor = [base.fontDescriptor fontDescriptorWithSymbolicTraits:traits];
265
+ if (descriptor) {
266
+ return [UIFont fontWithDescriptor:descriptor size:sourceFont.pointSize];
267
+ }
268
+ return base;
269
+ }
270
+
271
+ - (UIFont *)qms_systemFontByPreservingStyleFromFont:(UIFont *)sourceFont {
272
+ if (!sourceFont) return sourceFont;
273
+ UIFontDescriptorSymbolicTraits traits = sourceFont.fontDescriptor.symbolicTraits;
274
+ BOOL isBold = (traits & UIFontDescriptorTraitBold) == UIFontDescriptorTraitBold;
275
+ BOOL isItalic = (traits & UIFontDescriptorTraitItalic) == UIFontDescriptorTraitItalic;
276
+
277
+ UIFont *result = nil;
278
+ if (isBold && isItalic) {
279
+ UIFontDescriptor *desc =
280
+ [[UIFont systemFontOfSize:sourceFont.pointSize].fontDescriptor
281
+ fontDescriptorWithSymbolicTraits:(UIFontDescriptorTraitBold | UIFontDescriptorTraitItalic)];
282
+ if (desc) {
283
+ result = [UIFont fontWithDescriptor:desc size:sourceFont.pointSize];
284
+ }
285
+ } else if (isBold) {
286
+ result = [UIFont boldSystemFontOfSize:sourceFont.pointSize];
287
+ } else if (isItalic) {
288
+ result = [UIFont italicSystemFontOfSize:sourceFont.pointSize];
289
+ } else {
290
+ result = [UIFont systemFontOfSize:sourceFont.pointSize];
291
+ }
292
+ return result ?: [UIFont systemFontOfSize:sourceFont.pointSize];
293
+ }
294
+
295
+ - (void)qms_applyFontRecursively:(UIView *)view fontFamily:(NSString *)fontFamily {
296
+ if (!view || fontFamily.length == 0) return;
297
+
298
+ if ([view isKindOfClass:[UILabel class]]) {
299
+ UILabel *label = (UILabel *)view;
300
+ label.font = [self qms_fontByPreservingStyleFromFont:label.font fontFamily:fontFamily];
301
+ } else if ([view isKindOfClass:[UIButton class]]) {
302
+ UIButton *button = (UIButton *)view;
303
+ if (button.titleLabel) {
304
+ button.titleLabel.font = [self qms_fontByPreservingStyleFromFont:button.titleLabel.font fontFamily:fontFamily];
305
+ }
306
+ } else if ([view isKindOfClass:[UITextField class]]) {
307
+ UITextField *textField = (UITextField *)view;
308
+ textField.font = [self qms_fontByPreservingStyleFromFont:textField.font fontFamily:fontFamily];
309
+ } else if ([view isKindOfClass:[UITextView class]]) {
310
+ UITextView *textView = (UITextView *)view;
311
+ textView.font = [self qms_fontByPreservingStyleFromFont:textView.font fontFamily:fontFamily];
312
+ }
313
+
314
+ for (UIView *subview in view.subviews) {
315
+ [self qms_applyFontRecursively:subview fontFamily:fontFamily];
316
+ }
317
+ }
318
+
319
+ - (void)qms_resetFontRecursively:(UIView *)view {
320
+ if (!view) return;
321
+
322
+ if ([view isKindOfClass:[UILabel class]]) {
323
+ UILabel *label = (UILabel *)view;
324
+ label.font = [self qms_systemFontByPreservingStyleFromFont:label.font];
325
+ } else if ([view isKindOfClass:[UIButton class]]) {
326
+ UIButton *button = (UIButton *)view;
327
+ if (button.titleLabel) {
328
+ button.titleLabel.font = [self qms_systemFontByPreservingStyleFromFont:button.titleLabel.font];
329
+ }
330
+ } else if ([view isKindOfClass:[UITextField class]]) {
331
+ UITextField *textField = (UITextField *)view;
332
+ textField.font = [self qms_systemFontByPreservingStyleFromFont:textField.font];
333
+ } else if ([view isKindOfClass:[UITextView class]]) {
334
+ UITextView *textView = (UITextView *)view;
335
+ textView.font = [self qms_systemFontByPreservingStyleFromFont:textView.font];
336
+ }
337
+
338
+ for (UIView *subview in view.subviews) {
339
+ [self qms_resetFontRecursively:subview];
340
+ }
341
+ }
342
+
70
343
  - (BOOL)qms_shouldTrackAnalytics {
71
344
  NSBundle *bundle = [NSBundle bundleForClass:[self class]];
72
345
  NSString *bundleId = bundle.bundleIdentifier ?: @"";
@@ -8,32 +8,32 @@
8
8
  <key>BinaryPath</key>
9
9
  <string>QmsPluginFramework.framework/QmsPluginFramework</string>
10
10
  <key>LibraryIdentifier</key>
11
- <string>ios-arm64_x86_64-simulator</string>
11
+ <string>ios-arm64</string>
12
12
  <key>LibraryPath</key>
13
13
  <string>QmsPluginFramework.framework</string>
14
14
  <key>SupportedArchitectures</key>
15
15
  <array>
16
16
  <string>arm64</string>
17
- <string>x86_64</string>
18
17
  </array>
19
18
  <key>SupportedPlatform</key>
20
19
  <string>ios</string>
21
- <key>SupportedPlatformVariant</key>
22
- <string>simulator</string>
23
20
  </dict>
24
21
  <dict>
25
22
  <key>BinaryPath</key>
26
23
  <string>QmsPluginFramework.framework/QmsPluginFramework</string>
27
24
  <key>LibraryIdentifier</key>
28
- <string>ios-arm64</string>
25
+ <string>ios-arm64_x86_64-simulator</string>
29
26
  <key>LibraryPath</key>
30
27
  <string>QmsPluginFramework.framework</string>
31
28
  <key>SupportedArchitectures</key>
32
29
  <array>
33
30
  <string>arm64</string>
31
+ <string>x86_64</string>
34
32
  </array>
35
33
  <key>SupportedPlatform</key>
36
34
  <string>ios</string>
35
+ <key>SupportedPlatformVariant</key>
36
+ <string>simulator</string>
37
37
  </dict>
38
38
  </array>
39
39
  <key>CFBundlePackageType</key>
@@ -6,7 +6,7 @@
6
6
  <dict>
7
7
  <key>Assets.car</key>
8
8
  <data>
9
- 1i2tuf6x04oClRQdI25h80iAWww=
9
+ kEpAubglJYNFlRT4LSRI2MmXQlg=
10
10
  </data>
11
11
  <key>Headers/QmsPlugin.h</key>
12
12
  <data>
@@ -18,12 +18,24 @@
18
18
  </data>
19
19
  <key>Info.plist</key>
20
20
  <data>
21
- w/Rc8R+JLx3x4AhZWV+3tG1CxCM=
21
+ sF1nzLQuh4BXmXMChhVDzjKB5C4=
22
22
  </data>
23
23
  <key>Modules/module.modulemap</key>
24
24
  <data>
25
25
  BD9YnWBkeFmDcuOl4cAjALd/7yw=
26
26
  </data>
27
+ <key>Montserrat-Bold.ttf</key>
28
+ <data>
29
+ vnwUOAcKtjfhFxaRXk5QkDcm3Mg=
30
+ </data>
31
+ <key>Montserrat-Medium.ttf</key>
32
+ <data>
33
+ d/NVNBXYWMZTuupFqsqyAZZ3YzQ=
34
+ </data>
35
+ <key>Montserrat-Regular.ttf</key>
36
+ <data>
37
+ 7BPvHyyZ+HgNby6z+fJ++J8wFhs=
38
+ </data>
27
39
  </dict>
28
40
  <key>files2</key>
29
41
  <dict>
@@ -31,7 +43,7 @@
31
43
  <dict>
32
44
  <key>hash2</key>
33
45
  <data>
34
- 1mZEAflZoNOaTXIhGRR7gPGBZ99fx2jFZ147Fkro4WM=
46
+ 0sUdnIkwFavEcnM4vnqYtEL76ynKxgMhnZSeTzEv99c=
35
47
  </data>
36
48
  </dict>
37
49
  <key>Headers/QmsPlugin.h</key>
@@ -55,6 +67,27 @@
55
67
  e+P6RtiuicqWfZ3H2B43r960DPkFwUWqXKWU+w7OVD0=
56
68
  </data>
57
69
  </dict>
70
+ <key>Montserrat-Bold.ttf</key>
71
+ <dict>
72
+ <key>hash2</key>
73
+ <data>
74
+ f6k9fapcL4gaZycinCEXgt5rdgCPvWNGs9dmTo/IP0M=
75
+ </data>
76
+ </dict>
77
+ <key>Montserrat-Medium.ttf</key>
78
+ <dict>
79
+ <key>hash2</key>
80
+ <data>
81
+ zRYcujo83PaK1yGSD80WTIfnt8yea61RAnjD+jeNkdI=
82
+ </data>
83
+ </dict>
84
+ <key>Montserrat-Regular.ttf</key>
85
+ <dict>
86
+ <key>hash2</key>
87
+ <data>
88
+ 75z5nwF1vvUwuIk0vZBPz1b3c87G/U37jMr3ziu9OV4=
89
+ </data>
90
+ </dict>
58
91
  </dict>
59
92
  <key>rules</key>
60
93
  <dict>