From 433e556641640053341582334cafbee46dcefd49 Mon Sep 17 00:00:00 2001 From: Kamal Kishor Joshi Date: Mon, 21 Oct 2024 00:36:20 +0530 Subject: [PATCH] Add Ads (#257) * basic ads implementation on android * add revoke consent button * add ads to pro version screen * add com.google.android.gms.ads.APPLICATION_ID to manifest * add permission instead of application id to fix play store error while releasing * fix bug in displaying ad * remove console.error --- android/app/build.gradle | 2 +- android/app/proguard-rules.pro | 1 + android/app/src/main/AndroidManifest.xml | 2 +- app.json | 9 ++- components/HamburgerMenu.js | 19 ++++- package.json | 1 + screens/ChatSessionScreen.js | 91 +++++++++++++++-------- screens/ProVersionScreen.js | 18 ++--- screens/UserProfileScreen.js | 94 +++++++++++++++++++----- yarn.lock | 51 +++++++++++++ 10 files changed, 228 insertions(+), 60 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index fad8e255c..702efd3df 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -119,7 +119,7 @@ dependencies { implementation 'androidx.navigation:navigation-fragment:2.0.0' implementation 'androidx.navigation:navigation-ui:2.0.0' implementation 'com.google.firebase:firebase-config:19.2.0' - implementation 'com.google.firebase:firebase-analytics:17.2.2' + implementation 'com.google.firebase:firebase-analytics:20.1.2' implementation 'com.google.firebase:firebase-ml-vision:24.0.2' implementation "com.google.guava:guava:29.0-android" implementation 'com.google.android.flexbox:flexbox:3.0.0' diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro index 11b025724..83e97b433 100644 --- a/android/app/proguard-rules.pro +++ b/android/app/proguard-rules.pro @@ -8,3 +8,4 @@ # http://developer.android.com/guide/developing/tools/proguard.html # Add any project specific keep options here: +-keep class com.google.android.gms.internal.consent_sdk.** { *; } diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 5132b6cc0..d44f02e0d 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -72,12 +72,12 @@ - + { {t('Explore Palettes')} - { logEvent('hm_palette_library'); @@ -113,6 +113,7 @@ const HamburgerMenu = (props) => { {t('Palette Library')} + } { { )} + {(Platform.OS == 'android') && ( + { + logEvent('hm_setting'); + navigate('UserProfile'); + props.toggleSideMenu(); + }}> + + + + + {t('Setting')} + + + )} diff --git a/package.json b/package.json index c4241906a..f484c6c13 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "react-native-fs": "^2.20.0", "react-native-gesture-handler": "^2.19.0", "react-native-get-random-values": "^1.11.0", + "react-native-google-mobile-ads": "^14.2.5", "react-native-iap": "^12.15.2", "react-native-image-picker": "^7.1.2", "react-native-linear-gradient": "^2.8.3", diff --git a/screens/ChatSessionScreen.js b/screens/ChatSessionScreen.js index ff31ebecf..5e83ff1ea 100644 --- a/screens/ChatSessionScreen.js +++ b/screens/ChatSessionScreen.js @@ -7,7 +7,8 @@ import { TextInput, ActivityIndicator, Text, - ImageBackground + ImageBackground, + Platform } from 'react-native'; import Colors from '../constants/Styles'; import React, { useState, useEffect, useRef } from 'react'; @@ -18,16 +19,15 @@ import CromaButton from '../components/CromaButton'; import useChatSession from '../hooks/useChatSession'; import useApplicationStore from '../hooks/useApplicationStore'; import GridActionButton from '../components/GridActionButton'; +import mobileAds, { BannerAd, BannerAdSize, TestIds, AdsConsent } from 'react-native-google-mobile-ads'; -// eslint-disable-next-line no-undef const bgImage = require('../assets/images/colorful_background.jpg'); const ChatSessionScreen = (props) => { const { route, navigation } = props; - const [inputText, setInputText] = useState(''); const scrollViewRef = useRef(); - + const [mobileAdConsent, setMobileAdConsent] = useState(false); const { pro } = useApplicationStore(); const { messages, isLoading, isCreatingSession, error, createSession, followUpSession } = useChatSession(route.params?.messages); @@ -36,6 +36,27 @@ const ChatSessionScreen = (props) => { logEvent('chat_session_screen'); }, []); + // Request ads consent when the component mounts + useEffect(() => { + async function requestConsent() { + const consentInfo = await AdsConsent.requestInfoUpdate(); + var canRequestAds = false; + if (consentInfo.status === 'REQUIRED') { + const adsConsentInfo = await AdsConsent.loadAndShowConsentFormIfRequired(); + canRequestAds = adsConsentInfo.canRequestAds; + } else { + canRequestAds = consentInfo.canRequestAds; + } + // Initialize ads only if consent is obtained + if (canRequestAds) { + await mobileAds().initialize(); + } + setMobileAdConsent(canRequestAds); + } + + requestConsent(); + }, []); + const handleSendMessage = async () => { const message = { chat_session: { @@ -65,8 +86,9 @@ const ChatSessionScreen = (props) => { clearInterval(interval); }; }, [messages]); + function showUnlockPro() { - return pro.plan == 'starter' && messages.length >= 2 + return pro.plan == 'starter' && messages.length >= 2; } return ( @@ -162,6 +184,17 @@ const ChatSessionScreen = (props) => { )} )} + { pro.plan != 'proPlus' && mobileAdConsent && Platform.OS == 'android' && + + + + } {messages.length == 0 && } @@ -240,45 +273,45 @@ const styles = StyleSheet.create({ display: 'flex', flexDirection: 'row', alignItems: 'center', - backgroundColor: 'transparent', - padding: 10, - height: 60, - paddingTop: 12, - position: 'absolute', - bottom: 0, - left: 0, - right: 0 + margin: 10 }, sendButton: { - padding: 8, backgroundColor: Colors.primary, - borderRadius: 8 + borderRadius: 8, + padding: 8, + marginLeft: 12 }, disableSendButton: { - padding: 8, backgroundColor: 'gray', - borderRadius: 8 + borderRadius: 8, + padding: 8, + marginLeft: 12 }, textSend: { color: 'white', fontSize: 16, - fontWeight: 'bold', - textAlign: 'center' + fontWeight: 'bold' }, errorMessageContainer: { - marginLeft: 10, - marginRight: 10, - backgroundColor: Colors.primary, - borderRadius: 8 + padding: 20, + backgroundColor: 'rgba(255,0,0,0.2)', + borderRadius: 8, + margin: 10 + }, + errorMessageTitle: { + fontWeight: 'bold' }, errorMessageText: { - color: 'white', - padding: 5 + marginTop: 5 }, - errorMessageTitle: { - color: 'white', - paddingLeft: 5, - paddingRight: 5 + loadingContainer: { + justifyContent: 'center', + alignItems: 'center', + flex: 1 + }, + loadingText: { + marginTop: 10, + fontSize: 18 } }); diff --git a/screens/ProVersionScreen.js b/screens/ProVersionScreen.js index 0895bfd50..a9dc6f674 100644 --- a/screens/ProVersionScreen.js +++ b/screens/ProVersionScreen.js @@ -1,5 +1,5 @@ import React, { useEffect, useState } from 'react'; -import { ScrollView, StyleSheet, Text, TouchableOpacity, View, Linking } from 'react-native'; +import { ScrollView, StyleSheet, Text, TouchableOpacity, View, Linking, Platform } from 'react-native'; import FontAwesome from 'react-native-vector-icons/FontAwesome'; import { useTranslation } from 'react-i18next'; import Colors, { Spacing } from '../constants/Styles'; @@ -71,7 +71,7 @@ export default function ProVersionScreen({ route }) { }, { id: 2, - feature: t('Extract colors from images and camera'), + feature: t('Extract colors from images' + Platform.OS == 'android' ? ' and camera' : ''), starter: true, pro: true, proPlus: true @@ -85,13 +85,6 @@ export default function ProVersionScreen({ route }) { proPlus: true }, { id: 5, feature: t('View and convert color codes'), starter: true, pro: true, proPlus: true }, - { - id: 6, - feature: t('Access Material Design, CSS, and Tailwind palettes'), - starter: true, - pro: true, - proPlus: true - }, { id: 7, feature: t('Download palettes as PNG'), starter: true, pro: true, proPlus: true }, { id: 8, @@ -114,6 +107,13 @@ export default function ProVersionScreen({ route }) { pro: true, proPlus: true }, + { + id: 12, + feature: t('Ads free experience'), + starter: false, + pro: false, + proPlus: true + }, { id: 11, feature: t('AI color picker'), starter: false, pro: false, proPlus: true }, { id: 12, diff --git a/screens/UserProfileScreen.js b/screens/UserProfileScreen.js index 7f06d564e..2bab07a84 100644 --- a/screens/UserProfileScreen.js +++ b/screens/UserProfileScreen.js @@ -1,5 +1,5 @@ -import React, { useCallback, useEffect } from 'react'; -import { ScrollView, StyleSheet, Text, Image } from 'react-native'; +import React, { useCallback, useEffect, useState } from 'react'; +import { ScrollView, StyleSheet, Text, Image, TouchableOpacity } from 'react-native'; import { View } from 'react-native-animatable'; import CromaButton from '../components/CromaButton'; import { useTranslation } from 'react-i18next'; @@ -12,25 +12,51 @@ import useApplicationStore from '../hooks/useApplicationStore'; import { logout } from '../network/login-and-signup'; import { logEvent } from '../libs/Helpers'; import Colors from '../constants/Styles'; +import useAuth from '../hooks/useAuth'; +import { AdsConsent, AdsConsentStatus } from 'react-native-google-mobile-ads'; function UserProfile(props) { const applicationState = useApplicationStore(); const { loadInitPaletteFromStore } = applicationState; const { userData, loadUserData } = useUserData(); + const { openAuthOverlay } = useAuth(); + const [consentInfo, setConsentInfo] = useState(false); useEffect(() => { logEvent('user_profile_screen'); }, []); const { t } = useTranslation(); + useEffect(() => { GoogleSignin.configure({ webClientId: '865618605576-j2tb9toevqc7tonmbp01dim1ddvod7r0.apps.googleusercontent.com', forceCodeForRefreshToken: true, offlineAccess: false }); + + // Fetch current ad consent status + async function fetchConsentStatus() { + const consentInfo = await AdsConsent.requestInfoUpdate(); + setConsentInfo(consentInfo); + } + + fetchConsentStatus(); }, []); + // Function to handle ad consent + const handleAdConsent = useCallback(async () => { + if (consentInfo.canRequestAds) { + // Withdraw consent + await AdsConsent.reset(); + setConsentInfo(null); + } else { + // Request consent + const consentInfo = await AdsConsent.loadAndShowConsentFormIfRequired(); + setAdConsent(consentInfo); + } + }, [consentInfo]); + const onLogout = useCallback(async () => { await logout(); await removeUserSession(); @@ -42,29 +68,50 @@ function UserProfile(props) { } catch (error) { console.error(error); } - // Google Account disconnected from your app. - // Perform clean-up actions, such as deleting data associated with the disconnected account. }, [loadInitPaletteFromStore, loadUserData, props.navigation]); return ( - - - - {t('Name: ')} - {userData?.fullName} - - - {t('Email: ')} - {userData?.email} - + {userData && + + + + {t('Name: ')} + {userData?.fullName} + + + {t('Email: ')} + {userData?.email} + + + {t('Logout')} + + + } + + {!userData && - {t('Logout')} + onPressWithLoader={() => { + openAuthOverlay(); + }}> + {t('Sign In / Sign Up')} - + } + + { consentInfo.status == 'REQUIRED' && + + + {consentInfo.canRequestAds ? t('Withdraw Ad Consent') : t('Give Ad Consent')} + + + } ); } @@ -73,7 +120,7 @@ UserProfile.propTypes = { navigation: PropTypes.any, hideWelcomeMessage: PropTypes.bool, reloadScreen: PropTypes.func, - signupMessage: PropTypes.string | undefined + signupMessage: PropTypes.string }; const styles = StyleSheet.create({ @@ -94,6 +141,17 @@ const styles = StyleSheet.create({ height: 50, width: 50, marginTop: 30 + }, + consentButton: { + marginTop: 20, + backgroundColor: Colors.lightGrey, + padding: 10, + borderRadius: 5, + alignItems: 'center' + }, + consentText: { + color: Colors.white, + fontSize: 16 } }); diff --git a/yarn.lock b/yarn.lock index df999ce6b..b20e165ff 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1242,6 +1242,15 @@ __metadata: languageName: node linkType: hard +"@babel/runtime@npm:^7.12.5": + version: 7.25.7 + resolution: "@babel/runtime@npm:7.25.7" + dependencies: + regenerator-runtime: ^0.14.0 + checksum: 1d6133ed1cf1de1533cfe84a4a8f94525271a0d93f6af4f2cdae14884ec3c8a7148664ddf7fd2a14f82cc4485904a1761821a55875ad241c8b4034e95e7134b2 + languageName: node + linkType: hard + "@babel/template@npm:^7.0.0, @babel/template@npm:^7.24.7, @babel/template@npm:^7.25.0, @babel/template@npm:^7.3.3": version: 7.25.0 resolution: "@babel/template@npm:7.25.0" @@ -1424,6 +1433,13 @@ __metadata: languageName: node linkType: hard +"@iabtcf/core@npm:^1.5.3": + version: 1.5.6 + resolution: "@iabtcf/core@npm:1.5.6" + checksum: 561626b626ee476dea5dd3a6ed0ebc26caac2475fc9509583f0e66c92ad67c3a2c2816b3516a4f043904b40607c9a4d44cbacbf09a5897740fd273e057c02c2c + languageName: node + linkType: hard + "@isaacs/cliui@npm:^8.0.2": version: 8.0.2 resolution: "@isaacs/cliui@npm:8.0.2" @@ -3524,6 +3540,7 @@ __metadata: react-native-fs: ^2.20.0 react-native-gesture-handler: ^2.19.0 react-native-get-random-values: ^1.11.0 + react-native-google-mobile-ads: ^14.2.5 react-native-iap: ^12.15.2 react-native-image-picker: ^7.1.2 react-native-linear-gradient: ^2.8.3 @@ -5255,6 +5272,13 @@ __metadata: languageName: node linkType: hard +"dequal@npm:^2.0.2": + version: 2.0.3 + resolution: "dequal@npm:2.0.3" + checksum: 8679b850e1a3d0ebbc46ee780d5df7b478c23f335887464023a631d1b9af051ad4a6595a44220f9ff8ff95a8ddccf019b5ad778a976fd7bbf77383d36f412f90 + languageName: node + linkType: hard + "des.js@npm:^1.0.0": version: 1.1.0 resolution: "des.js@npm:1.1.0" @@ -10289,6 +10313,21 @@ __metadata: languageName: node linkType: hard +"react-native-google-mobile-ads@npm:^14.2.5": + version: 14.2.5 + resolution: "react-native-google-mobile-ads@npm:14.2.5" + dependencies: + "@iabtcf/core": ^1.5.3 + use-deep-compare-effect: ^1.8.1 + peerDependencies: + expo: ">=47.0.0" + peerDependenciesMeta: + expo: + optional: true + checksum: c2d57a1a1ab931e8f4aa06807cb15d35edaf17baf67031609107394b3b511866fc455d911d9579b8ccfa2e4434f2e418d073b11089a9e98c92b95245caf32d55 + languageName: node + linkType: hard + "react-native-iap@npm:^12.15.2": version: 12.15.3 resolution: "react-native-iap@npm:12.15.3" @@ -12242,6 +12281,18 @@ __metadata: languageName: node linkType: hard +"use-deep-compare-effect@npm:^1.8.1": + version: 1.8.1 + resolution: "use-deep-compare-effect@npm:1.8.1" + dependencies: + "@babel/runtime": ^7.12.5 + dequal: ^2.0.2 + peerDependencies: + react: ">=16.13" + checksum: 2b9b6291df3f772f44d259b352e5d998963ccee0db2efeb76bb05525d928064aeeb69bb0dee5a5e428fea7cf3db67c097a770ebd30caa080662b565f6ef02b2e + languageName: node + linkType: hard + "use-latest-callback@npm:^0.2.1": version: 0.2.1 resolution: "use-latest-callback@npm:0.2.1"