Browse Source

#15 iOS In-app purchases support & bump version

Alex Trofimov 5 năm trước cách đây
mục cha
commit
909786dad8

+ 111 - 10
App.js

@@ -17,6 +17,7 @@ import {
     ActivityIndicator,
     Dimensions,
     StatusBar,
+    Alert,
 } from 'react-native';
 import { 
     View,
@@ -27,6 +28,13 @@ import {
     Drawer,
 } from 'native-base';
 
+import RNIap, {
+  purchaseErrorListener,
+  purchaseUpdatedListener,
+  type ProductPurchase,
+  type PurchaseError
+} from 'react-native-iap';
+
 import { initialMode, useDarkMode, DynamicStyleSheet, DynamicValue, useDynamicStyleSheet, eventEmitter } from 'react-native-dark-mode';
 import JitsiMeet, { JitsiMeetView } from 'react-native-jitsi-meet';
 import VideoCall from './VideoCall';
@@ -35,18 +43,22 @@ import { version } from './package.json';
 
 type Props = {};
 
-const BASE_URL = 'https://una.io/';
-const MIX_LIGHT = '12';
-const MIX_DARK = '13';
-const TEMPLATE = 'protean';
-const TITLE = 'UNA.IO';
-const ONESIGNALAPPID = '';
+const BASE_URL = 'https://una.io/'; // site URL
+const MIX_LIGHT = '12'; // template styles mix for light mode
+const MIX_DARK = '13'; // template styles mix for dark mode
+const TEMPLATE = 'protean'; // template name
+const TITLE = 'UNA.IO'; // app title
+const ONESIGNALAPPID = ''; // you can obtain one from https://onesignal.com/
+const PAYMENTS_CALLBACK = ''; // empty string means payment functionality is disabled
 
 export default class App extends Component<Props> {
 
     loading = false;
     videoCallAudio = false;
     mode = initialMode;
+    purchaseUpdateSubscription = null;
+    purchaseErrorSubscription = null;
+    products = {};
 
     constructor(props) {
         super(props);
@@ -74,13 +86,35 @@ export default class App extends Component<Props> {
         this.onVideoCallStart = this.onVideoCallStart.bind(this);        
     }
 
-    onModeChanged(newMode) {
+    onModeChanged(newMode){
         this.mode = newMode;
         this.setState ({
             key: this.state.key + 1,
         });
     }
 
+    async onRequestPurchase (sku: string) {
+        if ('ios' === Platform.OS)
+            RNIap.clearTransactionIOS();
+        try {
+            await RNIap.requestPurchase(sku, false);
+        } catch (err) {
+            console.warn(err.code, err.message);
+            Alert.alert(err.message);
+        }
+    }
+
+    async onRequestSubscription (sku: string) {
+        if ('ios' === Platform.OS)
+            RNIap.clearTransactionIOS();
+        try {            
+            await RNIap.requestSubscription(sku);
+        } catch (err) {
+            console.warn(err.code, err.message);
+            Alert.alert(err.message);
+        }
+    }
+
     onConferenceTerminated(e) {
         this.endVideoCall();        
         this.injectJavaScript("bx_mobile_apps_video_terminated(" + JSON.stringify(e.nativeEvent) + ")");
@@ -329,7 +363,7 @@ export default class App extends Component<Props> {
         eventEmitter.on('currentModeChanged', this.onModeChanged.bind(this));
     }
 
-    componentDidMount() {
+    async componentDidMount() {
         OneSignal.init(ONESIGNALAPPID, {kOSSettingsKeyAutoPrompt : true});
         OneSignal.inFocusDisplaying(0);
 
@@ -344,6 +378,54 @@ export default class App extends Component<Props> {
         }
 
         SplashScreen.hide();
+
+        if (PAYMENTS_CALLBACK != '') {
+            var _self = this;
+            try {
+                await RNIap.initConnection();
+                const sProducts = await fetch(BASE_URL + 'modules/?r=oauth2/com/get_products_names&module=system&class=BaseServices');
+                _self.products = await sProducts.json();
+                await RNIap.getSubscriptions(Object.keys(_self.products));
+                await RNIap.getProducts(Object.keys(_self.products));
+            } catch(err) {
+                console.warn(err);
+            }
+
+            purchaseUpdateSubscription = purchaseUpdatedListener(async (oPurchaseData: ProductPurchase) => {
+                console.log('purchaseUpdatedListener', oPurchaseData);
+
+                if ('undefined' !== typeof(oPurchaseData.transactionId) && _self.state.data.loggedin != 0) {
+                    var sProductName = 'undefined' !== typeof(oPurchaseData.productId) ? oPurchaseData.productId : null;
+                    var sTxId = 'undefined' !== typeof(oPurchaseData.transactionId) ? oPurchaseData.transactionId : '';
+                    var sTxIdOriginal = 'undefined' !== typeof(oPurchaseData.originalTransactionIdentifierIOS) ? oPurchaseData.originalTransactionIdentifierIOS : '';
+
+                    await fetch(PAYMENTS_CALLBACK, {
+                        method: 'POST',
+                        headers: {
+                            Accept: 'application/json',
+                            'Content-Type': 'application/json',
+                            'User-Agent': "UNAMobileApp/Mobile (" + Platform.OS + ")",
+                        },
+                        body: JSON.stringify({
+                            notification_type: 'INITIAL_BUY_CUSTOM',
+                            module: 'undefined' !== typeof(_self.products[sProductName]) ? _self.products[sProductName] : null,
+                            product: sProductName,
+                            count: 1,
+                            original_transaction: sTxIdOriginal,
+                            transaction: sTxId,
+                            profile_id: _self.state.data.loggedin ? _self.state.data.user_info.id : 0,
+                            original_data: oPurchaseData,
+                        })
+                    });
+    
+                    if ('ios' === Platform.OS)
+                        RNIap.finishTransactionIOS(sTxId);
+                }
+            });
+            purchaseErrorSubscription = purchaseErrorListener((error: PurchaseError) => {
+                console.log('purchaseErrorListener', error);
+            }); 
+        }
     }
     
     componentWillUnmount() {
@@ -352,6 +434,15 @@ export default class App extends Component<Props> {
         OneSignal.removeEventListener('received', this.onNotificationReceived);        
         OneSignal.removeEventListener('opened', this.onNotificationOpened);
 
+        if (this.purchaseUpdateSubscription) {
+            this.purchaseUpdateSubscription.remove();
+            this.purchaseUpdateSubscription = null;
+        }
+        if (this.purchaseErrorSubscription) {
+            this.purchaseErrorSubscription.remove();
+            this.purchaseErrorSubscription = null;
+        }
+
         if (Platform.OS === 'android') {
             this.backHandler.remove();
         }
@@ -408,6 +499,14 @@ export default class App extends Component<Props> {
         if ('undefined' !== typeof(oMsgData['video_call_stop']) && oMsgData['video_call_stop']) {
             this.endVideoCall ();
         }
+
+        if ('undefined' !== typeof(oMsgData['request_purchase']) && oMsgData['request_purchase']) {
+            this.onRequestPurchase (oMsgData['request_purchase']);
+        }
+        if ('undefined' !== typeof(oMsgData['request_subscription']) && oMsgData['request_subscription']) {
+            this.onRequestSubscription (oMsgData['request_subscription']);
+        }
+
         if ('undefined' !== typeof(oMsgData['goto_home']) && oMsgData['goto_home']) {
             this.onHomeMenu ();
         }
@@ -467,6 +566,8 @@ export default class App extends Component<Props> {
                 onMessengerMenu={this.onMessengerMenu.bind(this)}
                 onProfileMenu={this.onProfileMenu.bind(this)}
                 onVideoCallToggle={this.onVideoCallToggle.bind(this)}
+                onRequestSubscription={this.onRequestSubscription} 
+                onRequestPurchase={this.onRequestPurchase} 
                 onConferenceTerminated={this.onConferenceTerminated}
                 onConferenceJoined={this.onConferenceJoined}
                 onConferenceWillJoin={this.onConferenceWillJoin} />
@@ -504,7 +605,7 @@ function UnaApp(o) {
                 {o.webview}
 
                 {o.state.data.loggedin && (
-                    <UnaFooter bubblesNum={o.state.data.bubbles_num} bubbles={o.state.data.bubbles} onMainMenu={o.onMainMenu} onNotificationsMenu={o.onNotificationsMenu} onVideoCallToggle={o.onVideoCallToggle} onAddMenu={o.onAddMenu} onMessengerMenu={o.onMessengerMenu} onProfileMenu={o.onProfileMenu} />
+                    <UnaFooter bubblesNum={o.state.data.bubbles_num} bubbles={o.state.data.bubbles} onMainMenu={o.onMainMenu} onNotificationsMenu={o.onNotificationsMenu} onVideoCallToggle={o.onVideoCallToggle} onRequestPurchase={o.onRequestPurchase} onRequestSubscription={o.onRequestSubscription} onAddMenu={o.onAddMenu} onMessengerMenu={o.onMessengerMenu} onProfileMenu={o.onProfileMenu} />
                 )}
 
                 </Container>
@@ -583,7 +684,7 @@ function UnaToolbar(o) {
                 )
             )}
             </Left>
-            <Body>
+            <Body style={Platform.OS === 'android' && {flexDirection: 'row', justifyContent: 'flex-start'}}>
                 <Title style={styles.headerTitle} onPress={o.onHomeMenu}>{TITLE}</Title>
             </Body>
             <Right>

+ 2 - 2
android/app/build.gradle

@@ -134,8 +134,8 @@ android {
         applicationId "com.una.android"
         minSdkVersion rootProject.ext.minSdkVersion
         targetSdkVersion rootProject.ext.targetSdkVersion
-        versionCode 1410
-        versionName "1.4.1"
+        versionCode 1500
+        versionName "1.5.0"
     }
     signingConfigs {
         release {

+ 1 - 0
android/app/src/main/AndroidManifest.xml

@@ -6,6 +6,7 @@
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
 	<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
 	<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
+    <!-- <uses-permission android:name="com.android.vending.BILLING" /> -->
 
     <application
       android:name=".MainApplication"

+ 1 - 0
android/build.gradle

@@ -6,6 +6,7 @@ buildscript {
         minSdkVersion = 21
         compileSdkVersion = 29
         targetSdkVersion = 29
+        supportLibVersion = "29.0.0"
     }
     repositories {
         google()

+ 6 - 0
ios/Podfile.lock

@@ -306,6 +306,8 @@ PODS:
     - ReactCommon/callinvoker (= 0.62.2)
   - ReactNativeDarkMode (0.2.2):
     - React
+  - RNIap (5.2.0):
+    - React-Core
   - Yoga (1.14.0)
   - YogaKit (1.18.1):
     - Yoga (~> 1.14)
@@ -362,6 +364,7 @@ DEPENDENCIES:
   - ReactCommon/callinvoker (from `../node_modules/react-native/ReactCommon`)
   - ReactCommon/turbomodule/core (from `../node_modules/react-native/ReactCommon`)
   - ReactNativeDarkMode (from `../node_modules/react-native-dark-mode`)
+  - RNIap (from `../node_modules/react-native-iap`)
   - Yoga (from `../node_modules/react-native/ReactCommon/yoga`)
 
 SPEC REPOS:
@@ -440,6 +443,8 @@ EXTERNAL SOURCES:
     :path: "../node_modules/react-native/ReactCommon"
   ReactNativeDarkMode:
     :path: "../node_modules/react-native-dark-mode"
+  RNIap:
+    :path: "../node_modules/react-native-iap"
   Yoga:
     :path: "../node_modules/react-native/ReactCommon/yoga"
 
@@ -486,6 +491,7 @@ SPEC CHECKSUMS:
   React-RCTVibration: 4356114dbcba4ce66991096e51a66e61eda51256
   ReactCommon: ed4e11d27609d571e7eee8b65548efc191116eb3
   ReactNativeDarkMode: 0178ffca3b10f6a7c9f49d6f9810232b328fa949
+  RNIap: befdb651dc9afe583ccf1982fa68bbe3e8f071cd
   Yoga: 3ebccbdd559724312790e7742142d062476b698e
   YogaKit: f782866e155069a2cca2517aafea43200b01fd5a
 

+ 14 - 4
ios/una.xcodeproj/project.pbxproj

@@ -39,6 +39,9 @@
 		E0D59055301843CC9814F379 /* Roboto.ttf in Resources */ = {isa = PBXBuildFile; fileRef = A1B68D8B04A04539A20D70FE /* Roboto.ttf */; };
 		E71F5582DD036D39895B4CF7 /* libPods-una.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 37BD084A5A6D3EA9652736F4 /* libPods-una.a */; };
 		E997B8610E8FE0B417C6279C /* libPods-una-unaTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 53E516E4D0B01D0D957C9829 /* libPods-una-unaTests.a */; };
+		FA21B161257FA6B900E0BF68 /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = FA21B160257FA6B900E0BF68 /* StoreKit.framework */; };
+		FA21B1672583755300E0BF68 /* SplashScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FA21B1662583755300E0BF68 /* SplashScreen.storyboard */; };
+		FA21B1682583755300E0BF68 /* SplashScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = FA21B1662583755300E0BF68 /* SplashScreen.storyboard */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXContainerItemProxy section */
@@ -104,6 +107,8 @@
 		F27F5945B35A48D38AB626F9 /* rubicon-icon-font.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = "rubicon-icon-font.ttf"; path = "../node_modules/native-base/Fonts/rubicon-icon-font.ttf"; sourceTree = "<group>"; };
 		F2BBC9217F857021596B12A2 /* libPods-una-tvOSTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-una-tvOSTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
 		F5A2D7DEDE81460EACAF2602 /* Octicons.ttf */ = {isa = PBXFileReference; explicitFileType = undefined; fileEncoding = 9; includeInIndex = 0; lastKnownFileType = unknown; name = Octicons.ttf; path = "../node_modules/native-base/Fonts/Octicons.ttf"; sourceTree = "<group>"; };
+		FA21B160257FA6B900E0BF68 /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = System/Library/Frameworks/StoreKit.framework; sourceTree = SDKROOT; };
+		FA21B1662583755300E0BF68 /* SplashScreen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = SplashScreen.storyboard; sourceTree = "<group>"; };
 		FAFFDF72246EA89300F78556 /* una.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; name = una.entitlements; path = una/una.entitlements; sourceTree = "<group>"; };
 		FBDD1932E2DF906FFEE3F913 /* Pods-una-tvOS.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-una-tvOS.release.xcconfig"; path = "Target Support Files/Pods-una-tvOS/Pods-una-tvOS.release.xcconfig"; sourceTree = "<group>"; };
 /* End PBXFileReference section */
@@ -122,6 +127,7 @@
 			buildActionMask = 2147483647;
 			files = (
 				E71F5582DD036D39895B4CF7 /* libPods-una.a in Frameworks */,
+				FA21B161257FA6B900E0BF68 /* StoreKit.framework in Frameworks */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -164,6 +170,7 @@
 		13B07FAE1A68108700A75B9A /* una */ = {
 			isa = PBXGroup;
 			children = (
+				FA21B1662583755300E0BF68 /* SplashScreen.storyboard */,
 				FAFFDF72246EA89300F78556 /* una.entitlements */,
 				008F07F21AC5B25A0029DE68 /* main.jsbundle */,
 				13B07FAF1A68108700A75B9A /* AppDelegate.h */,
@@ -179,6 +186,7 @@
 		2D16E6871FA4F8E400B85C8A /* Frameworks */ = {
 			isa = PBXGroup;
 			children = (
+				FA21B160257FA6B900E0BF68 /* StoreKit.framework */,
 				ED297162215061F000B7C4FE /* JavaScriptCore.framework */,
 				ED2971642150620600B7C4FE /* JavaScriptCore.framework */,
 				37BD084A5A6D3EA9652736F4 /* libPods-una.a */,
@@ -416,6 +424,7 @@
 				5DCD7E52A59F4569938E3FB7 /* FontAwesome.ttf in Resources */,
 				A473796212FE4B72857D13B7 /* FontAwesome5_Brands.ttf in Resources */,
 				B81DB4918CB64E7EA48634B5 /* FontAwesome5_Regular.ttf in Resources */,
+				FA21B1672583755300E0BF68 /* SplashScreen.storyboard in Resources */,
 				6659C1EBBA694C9FA2816285 /* FontAwesome5_Solid.ttf in Resources */,
 				6504BB5429DD4CE2B3DFD02D /* Fontisto.ttf in Resources */,
 				222400D7EBA04F2C96DB08BE /* Foundation.ttf in Resources */,
@@ -436,6 +445,7 @@
 			buildActionMask = 2147483647;
 			files = (
 				2D02E4BD1E0B4A84006451C7 /* Images.xcassets in Resources */,
+				FA21B1682583755300E0BF68 /* SplashScreen.storyboard in Resources */,
 			);
 			runOnlyForDeploymentPostprocessing = 0;
 		};
@@ -758,7 +768,7 @@
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				CLANG_ENABLE_MODULES = YES;
 				CODE_SIGN_ENTITLEMENTS = una/una.entitlements;
-				CURRENT_PROJECT_VERSION = 1410;
+				CURRENT_PROJECT_VERSION = 1500;
 				DEVELOPMENT_TEAM = 235MTP3MJ2;
 				ENABLE_BITCODE = NO;
 				GCC_PREPROCESSOR_DEFINITIONS = (
@@ -767,7 +777,7 @@
 				);
 				INFOPLIST_FILE = una/Info.plist;
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
-				MARKETING_VERSION = 1.4.1;
+				MARKETING_VERSION = 1.5.0;
 				OTHER_LDFLAGS = (
 					"$(inherited)",
 					"-ObjC",
@@ -789,12 +799,12 @@
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				CLANG_ENABLE_MODULES = YES;
 				CODE_SIGN_ENTITLEMENTS = una/una.entitlements;
-				CURRENT_PROJECT_VERSION = 1410;
+				CURRENT_PROJECT_VERSION = 1500;
 				DEVELOPMENT_TEAM = 235MTP3MJ2;
 				ENABLE_BITCODE = NO;
 				INFOPLIST_FILE = una/Info.plist;
 				LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
-				MARKETING_VERSION = 1.4.1;
+				MARKETING_VERSION = 1.5.0;
 				OTHER_LDFLAGS = (
 					"$(inherited)",
 					"-ObjC",

+ 18 - 9
ios/una/Base.lproj/LaunchScreen.xib

@@ -1,9 +1,10 @@
-<?xml version="1.0" encoding="UTF-8" standalone="no"?>
-<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="7702" systemVersion="14D136" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES">
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="17156" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" colorMatched="YES">
+    <device id="retina6_1" orientation="portrait" appearance="light"/>
     <dependencies>
         <deployment identifier="iOS"/>
-        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="7701"/>
-        <capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
+        <plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="17126"/>
+        <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
     </dependencies>
     <objects>
         <placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
@@ -12,20 +13,20 @@
             <rect key="frame" x="0.0" y="0.0" width="480" height="480"/>
             <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
             <subviews>
-                <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Powered by React Native" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="9" translatesAutoresizingMaskIntoConstraints="NO" id="8ie-xW-0ye">
+                <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" misplaced="YES" text="Powered by React Native" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="9" translatesAutoresizingMaskIntoConstraints="NO" id="8ie-xW-0ye">
                     <rect key="frame" x="20" y="439" width="441" height="21"/>
                     <fontDescription key="fontDescription" type="system" pointSize="17"/>
-                    <color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
+                    <color key="textColor" systemColor="darkTextColor"/>
                     <nil key="highlightedColor"/>
                 </label>
-                <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="una" textAlignment="center" lineBreakMode="middleTruncation" baselineAdjustment="alignBaselines" minimumFontSize="18" translatesAutoresizingMaskIntoConstraints="NO" id="kId-c2-rCX">
+                <label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" misplaced="YES" text="una" textAlignment="center" lineBreakMode="middleTruncation" baselineAdjustment="alignBaselines" minimumFontSize="18" translatesAutoresizingMaskIntoConstraints="NO" id="kId-c2-rCX">
                     <rect key="frame" x="20" y="140" width="441" height="43"/>
                     <fontDescription key="fontDescription" type="boldSystem" pointSize="36"/>
-                    <color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
+                    <color key="textColor" systemColor="darkTextColor"/>
                     <nil key="highlightedColor"/>
                 </label>
             </subviews>
-            <color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
+            <color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
             <constraints>
                 <constraint firstItem="kId-c2-rCX" firstAttribute="centerY" secondItem="iN0-l3-epB" secondAttribute="bottom" multiplier="1/3" constant="1" id="5cJ-9S-tgC"/>
                 <constraint firstAttribute="centerX" secondItem="kId-c2-rCX" secondAttribute="centerX" id="Koa-jz-hwk"/>
@@ -39,4 +40,12 @@
             <point key="canvasLocation" x="548" y="455"/>
         </view>
     </objects>
+    <resources>
+        <systemColor name="darkTextColor">
+            <color white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+        </systemColor>
+        <systemColor name="darkTextColor">
+            <color white="0.0" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
+        </systemColor>
+    </resources>
 </document>

+ 14 - 1
package-lock.json

@@ -1,6 +1,6 @@
 {
   "name": "UNA",
-  "version": "1.4.0",
+  "version": "1.4.1",
   "lockfileVersion": 1,
   "requires": true,
   "dependencies": {
@@ -2653,6 +2653,11 @@
         "webidl-conversions": "^4.0.2"
       }
     },
+    "dooboolab-welcome": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/dooboolab-welcome/-/dooboolab-welcome-1.2.0.tgz",
+      "integrity": "sha512-XgEbjJHAsKLge/e8jtuHsv84Ovg1Gx0GvnOTOmOlVGV1643nduTAkT3fyo/mxKbzvyXBr47RHaJAY4R2toI0UA=="
+    },
     "ecc-jsbn": {
       "version": "0.1.2",
       "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
@@ -6955,6 +6960,14 @@
         "lodash": "^4.17.15"
       }
     },
+    "react-native-iap": {
+      "version": "5.2.0",
+      "resolved": "https://registry.npmjs.org/react-native-iap/-/react-native-iap-5.2.0.tgz",
+      "integrity": "sha512-OSABKsp+SuEY1iX1L0y3XZdUskfkJ/7vgo44sPQibJoOu7sBcUUoWWxDRaDYJoNf2Lz9Fn5hnVCcxEgzY+zcDw==",
+      "requires": {
+        "dooboolab-welcome": "^1.2.0"
+      }
+    },
     "react-native-iphone-x-helper": {
       "version": "1.2.1",
       "resolved": "https://registry.npmjs.org/react-native-iphone-x-helper/-/react-native-iphone-x-helper-1.2.1.tgz",

+ 2 - 1
package.json

@@ -1,6 +1,6 @@
 {
   "name": "UNA",
-  "version": "1.4.1",
+  "version": "1.5.0",
   "private": true,
   "scripts": {
     "android": "react-native run-android",
@@ -14,6 +14,7 @@
     "react": "16.11.0",
     "react-native": "0.62.2",
     "react-native-dark-mode": "^0.2.2",
+    "react-native-iap": "^5.2.0",
     "react-native-jitsi-meet": "^2.1.1",
     "react-native-onesignal": "^3.7.3",
     "react-native-splash-screen": "^3.2.0",