├── .bundle └── config ├── .commitlintrc.js ├── .detoxrc.json ├── .eslintrc.js ├── .github ├── CODEOWNERS ├── slack.yml └── workflows │ └── CICD.yaml ├── .gitignore ├── .husky ├── commit-msg └── pre-commit ├── .prettierrc.js ├── .ruby-version ├── .watchmanconfig ├── Gemfile ├── Gemfile.lock ├── LICENSE.header ├── LICENSE.txt ├── README.md ├── android ├── app │ ├── build.gradle │ ├── proguard-rules.pro │ └── src │ │ ├── androidTest │ │ └── java │ │ │ └── io │ │ │ └── ice │ │ │ └── app │ │ │ └── DetoxTest.java │ │ ├── debug │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── io │ │ │ └── ice │ │ │ └── app │ │ │ └── ReactNativeFlipper.java │ │ ├── main │ │ ├── AndroidManifest.xml │ │ ├── assets │ │ │ └── fonts │ │ │ │ ├── Lato-Black.ttf │ │ │ │ ├── Lato-Bold.ttf │ │ │ │ ├── Lato-Hairline.ttf │ │ │ │ ├── Lato-Heavy.ttf │ │ │ │ ├── Lato-Light.ttf │ │ │ │ ├── Lato-Medium.ttf │ │ │ │ ├── Lato-Regular.ttf │ │ │ │ ├── Lato-Semibold.ttf │ │ │ │ └── Lato-Thin.ttf │ │ ├── java │ │ │ └── io │ │ │ │ └── ice │ │ │ │ └── app │ │ │ │ ├── MainActivity.java │ │ │ │ └── MainApplication.java │ │ └── res │ │ │ ├── drawable-hdpi │ │ │ └── ic_stat_notification.png │ │ │ ├── drawable-mdpi │ │ │ └── ic_stat_notification.png │ │ │ ├── drawable-xhdpi │ │ │ └── ic_stat_notification.png │ │ │ ├── drawable-xxhdpi │ │ │ └── ic_stat_notification.png │ │ │ ├── drawable-xxxhdpi │ │ │ └── ic_stat_notification.png │ │ │ ├── drawable │ │ │ └── rn_edit_text_material.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher_background.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── transparent_logo.png │ │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher_background.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── transparent_logo.png │ │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher_background.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── transparent_logo.png │ │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher_background.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── transparent_logo.png │ │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher_background.png │ │ │ ├── ic_launcher_foreground.png │ │ │ └── transparent_logo.png │ │ │ ├── raw │ │ │ ├── bonus_received.wav │ │ │ ├── extend_mining.wav │ │ │ └── start_mining.wav │ │ │ └── values │ │ │ ├── colors.xml │ │ │ ├── strings.xml │ │ │ └── styles.xml │ │ ├── release │ │ └── java │ │ │ └── io │ │ │ └── ice │ │ │ └── app │ │ │ └── ReactNativeFlipper.java │ │ └── releasestaging │ │ └── java │ │ └── io │ │ └── ice │ │ └── app │ │ └── ReactNativeFlipper.java ├── build.gradle ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── link-assets-manifest.json └── settings.gradle ├── app.json ├── babel.config.js ├── fastlane ├── Fastfile ├── Matchfile ├── Pluginfile └── README.md ├── index.js ├── ios ├── Configurations │ ├── ice.debug.xcconfig │ └── ice.release.xcconfig ├── File.swift ├── Ice-Bridging-Header.h ├── NotificationService │ ├── Info.plist │ ├── NotificationService.debug.xcconfig │ ├── NotificationService.h │ ├── NotificationService.m │ └── NotificationService.release.xcconfig ├── Podfile ├── Podfile.lock ├── audio │ ├── bonus_received.wav │ ├── extend_mining.wav │ └── start_mining.wav ├── ice.xcodeproj │ ├── project.pbxproj │ └── xcshareddata │ │ └── xcschemes │ │ └── ice.xcscheme ├── ice.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── ice │ ├── AppDelegate.h │ ├── AppDelegate.mm │ ├── Images.xcassets │ │ ├── AppIcon.appiconset │ │ │ ├── Contents.json │ │ │ ├── Icon-167x167.png │ │ │ ├── Icon-20x20.png │ │ │ ├── Icon-20x20@2x.png │ │ │ ├── Icon-20x20@3x.png │ │ │ ├── Icon-29x29.png │ │ │ ├── Icon-29x29@2x-1.png │ │ │ ├── Icon-29x29@2x.png │ │ │ ├── Icon-29x29@3x.png │ │ │ ├── Icon-40x40-1.png │ │ │ ├── Icon-40x40.png │ │ │ ├── Icon-40x40@2x-1.png │ │ │ ├── Icon-40x40@2x.png │ │ │ ├── Icon-40x40@3x.png │ │ │ ├── Icon-60x60@2x.png │ │ │ ├── Icon-60x60@3x.png │ │ │ ├── Icon-76x76.png │ │ │ ├── Icon-76x76@2x.png │ │ │ └── Icon-marketing-1024x1024.png │ │ └── Contents.json │ ├── LaunchScreen.storyboard │ └── main.m └── link-assets-manifest.json ├── jest.config.js ├── metro.config.js ├── package.json ├── patches └── @react-native-firebase+auth+18.8.0.patch ├── react-native.config.js ├── scripts ├── check-git-email.sh ├── compress-staged-images.sh ├── deleteLocalizations.js ├── generateTranslationTypes │ ├── index.mjs │ └── utils │ │ ├── buildFlatTranslationsType.mjs │ │ ├── buildParamsType.mjs │ │ ├── getParamsFromString.mjs │ │ └── isPluralizationNode.mjs ├── license │ ├── addlicense.sh │ └── checklicense.sh ├── run-e2e-android-release.sh ├── run-e2e-ios-release.sh └── syncLocalizations.js ├── src ├── App.tsx ├── assets │ ├── fonts │ │ ├── Lato-Black.ttf │ │ ├── Lato-Bold.ttf │ │ ├── Lato-Hairline.ttf │ │ ├── Lato-Heavy.ttf │ │ ├── Lato-Light.ttf │ │ ├── Lato-Medium.ttf │ │ ├── Lato-Regular.ttf │ │ ├── Lato-Semibold.ttf │ │ └── Lato-Thin.ttf │ ├── images │ │ ├── backgrounds │ │ │ ├── linesBg.png │ │ │ ├── linesBg@2x.png │ │ │ ├── linesBg@3x.png │ │ │ ├── roundedStroke.png │ │ │ ├── roundedStroke@2x.png │ │ │ └── roundedStroke@3x.png │ │ ├── card │ │ │ ├── joinMainnet.png │ │ │ ├── joinMainnet@2x.png │ │ │ └── joinMainnet@3x.png │ │ ├── index.ts │ │ ├── platforms │ │ │ ├── bifinance.png │ │ │ ├── bifinance@2x.png │ │ │ ├── bifinance@3x.png │ │ │ ├── bingx.png │ │ │ ├── bingx@2x.png │ │ │ ├── bingx@3x.png │ │ │ ├── bitconomy.png │ │ │ ├── bitconomy@2x.png │ │ │ ├── bitconomy@3x.png │ │ │ ├── bitget.png │ │ │ ├── bitget@2x.png │ │ │ ├── bitget@3x.png │ │ │ ├── bitmart.png │ │ │ ├── bitmart@2x.png │ │ │ ├── bitmart@3x.png │ │ │ ├── bitrue.png │ │ │ ├── bitrue@2x.png │ │ │ ├── bitrue@3x.png │ │ │ ├── gate.png │ │ │ ├── gate@2x.png │ │ │ ├── gate@3x.png │ │ │ ├── htx.png │ │ │ ├── htx@2x.png │ │ │ ├── htx@3x.png │ │ │ ├── jupiter.png │ │ │ ├── jupiter@2x.png │ │ │ ├── jupiter@3x.png │ │ │ ├── kucoin.png │ │ │ ├── kucoin@2x.png │ │ │ ├── kucoin@3x.png │ │ │ ├── mexc.png │ │ │ ├── mexc@2x.png │ │ │ ├── mexc@3x.png │ │ │ ├── okx.png │ │ │ ├── okx@2x.png │ │ │ ├── okx@3x.png │ │ │ ├── onus.png │ │ │ ├── onus@2x.png │ │ │ ├── onus@3x.png │ │ │ ├── pancake.png │ │ │ ├── pancake@2x.png │ │ │ ├── pancake@3x.png │ │ │ ├── poloneix.png │ │ │ ├── poloneix@2x.png │ │ │ ├── poloneix@3x.png │ │ │ ├── raydium.png │ │ │ ├── raydium@2x.png │ │ │ ├── raydium@3x.png │ │ │ ├── tokero.png │ │ │ ├── tokero@2x.png │ │ │ ├── tokero@3x.png │ │ │ ├── uniswap_arbitrum.png │ │ │ ├── uniswap_arbitrum@2x.png │ │ │ ├── uniswap_arbitrum@3x.png │ │ │ ├── uniswap_ethereum.png │ │ │ ├── uniswap_ethereum@2x.png │ │ │ ├── uniswap_ethereum@3x.png │ │ │ ├── xtcom.png │ │ │ ├── xtcom@2x.png │ │ │ └── xtcom@3x.png │ │ └── popup │ │ │ ├── oops.png │ │ │ ├── oops@2x.png │ │ │ └── oops@3x.png │ ├── lottie │ │ ├── index.ts │ │ ├── loader.json │ │ ├── splash-logo.json │ │ └── white_loader.json │ └── svg │ │ ├── Align.tsx │ │ ├── ChevronSmallIcon.tsx │ │ ├── Diamond.tsx │ │ ├── Graph.tsx │ │ ├── LogoIcon.tsx │ │ ├── PaperIcon.tsx │ │ ├── Pie.tsx │ │ ├── Refresh.tsx │ │ ├── Rocket.tsx │ │ ├── RoundedTriangle.tsx │ │ └── Structure.tsx ├── components │ ├── ActivityIndicator │ │ └── index.tsx │ ├── Buttons │ │ └── PrimaryButton │ │ │ └── index.tsx │ ├── LinesBackground │ │ └── index.tsx │ ├── ListItems │ │ └── ActionListItem │ │ │ └── index.tsx │ ├── LottieView │ │ ├── hooks │ │ │ └── useHandleLottieBackground.ts │ │ └── index.tsx │ ├── PullToRefreshContainer │ │ └── index.tsx │ ├── RefreshControl │ │ └── index.tsx │ ├── SectionHeader │ │ └── index.tsx │ └── Touchable │ │ └── index.tsx ├── constants │ ├── colors.ts │ ├── env.ts │ ├── fonts.ts │ ├── links.ts │ ├── mainnet.ts │ ├── styles.ts │ └── timeouts.ts ├── hooks │ ├── useCountdown.ts │ └── useSafeAreaInsets.ts ├── navigation │ ├── Router.tsx │ ├── components │ │ ├── AnimatedSplash │ │ │ └── index.tsx │ │ └── InitializationError │ │ │ └── index.tsx │ ├── hooks │ │ ├── useFocusStatusBar.ts │ │ └── useTopOffsetStyle.ts │ ├── options.ts │ └── theme.ts ├── screens │ └── MainnetLanding │ │ ├── components │ │ ├── IceLogo │ │ │ └── index.tsx │ │ ├── JoinMainnet │ │ │ └── index.tsx │ │ ├── LaunchCountdown │ │ │ ├── components │ │ │ │ └── TimerPart.tsx │ │ │ ├── hooks │ │ │ │ └── useContainerOffset.ts │ │ │ └── index.tsx │ │ ├── Notice │ │ │ └── index.tsx │ │ ├── Platforms │ │ │ └── index.tsx │ │ ├── Roadmap │ │ │ └── index.tsx │ │ └── Stats │ │ │ ├── components │ │ │ └── StatListItem.tsx │ │ │ └── index.tsx │ │ ├── hooks │ │ ├── useRefresh.ts │ │ └── useRequestNotificationPermissions.tsx │ │ └── index.tsx ├── services │ ├── dayjs │ │ └── index.ts │ └── logging │ │ └── index.ts ├── store │ ├── configureStore.ts │ ├── modules │ │ ├── AppCommon │ │ │ ├── actions │ │ │ │ └── index.ts │ │ │ ├── constants.ts │ │ │ ├── hooks │ │ │ │ └── useAppStateListener.ts │ │ │ ├── reducer │ │ │ │ └── index.ts │ │ │ ├── sagas │ │ │ │ ├── appInitializedHandler.ts │ │ │ │ ├── appLoadedHandlerSaga.ts │ │ │ │ └── index.ts │ │ │ └── selectors │ │ │ │ └── index.ts │ │ ├── Linking │ │ │ ├── actions │ │ │ │ └── index.ts │ │ │ ├── hooks │ │ │ │ └── useOpenUrlListener.ts │ │ │ ├── reducer │ │ │ │ └── index.ts │ │ │ ├── sagas │ │ │ │ ├── handleUrlSaga.ts │ │ │ │ └── index.ts │ │ │ └── selectors │ │ │ │ └── index.ts │ │ ├── Permissions │ │ │ ├── actions │ │ │ │ └── index.ts │ │ │ ├── reducer │ │ │ │ └── index.ts │ │ │ ├── sagas │ │ │ │ ├── checkAllPermissionsSaga.ts │ │ │ │ ├── getPermissionsSaga.ts │ │ │ │ └── index.ts │ │ │ └── selectors │ │ │ │ └── index.ts │ │ ├── PushNotifications │ │ │ ├── actions │ │ │ │ └── index.ts │ │ │ ├── constants.ts │ │ │ ├── hooks │ │ │ │ └── useSubscribeToPushNotifications.tsx │ │ │ ├── sagas │ │ │ │ ├── handleNotificationArrive.ts │ │ │ │ ├── handleNotificationPress.ts │ │ │ │ └── index.ts │ │ │ └── types.ts │ │ ├── Stats │ │ │ ├── actions │ │ │ │ └── index.ts │ │ │ ├── constants.ts │ │ │ ├── reducer │ │ │ │ └── index.ts │ │ │ ├── sagas │ │ │ │ ├── getIceCoinStats.ts │ │ │ │ └── index.ts │ │ │ ├── selectors │ │ │ │ └── index.ts │ │ │ └── types.ts │ │ └── UtilityProcessStatuses │ │ │ ├── reducer │ │ │ └── index.ts │ │ │ └── selectors │ │ │ └── index.ts │ ├── rootReducer.ts │ ├── rootSaga.ts │ └── utils │ │ ├── actions │ │ └── createAction.ts │ │ └── sagas │ │ └── effects.ts ├── translations │ ├── getPluralizer.ts │ ├── i18n.ts │ ├── localeConfig.ts │ └── locales │ │ ├── af.json │ │ ├── am.json │ │ ├── ar.json │ │ ├── az.json │ │ ├── bg.json │ │ ├── bn.json │ │ ├── cs.json │ │ ├── de.json │ │ ├── el.json │ │ ├── en.json │ │ ├── en.json.d.ts │ │ ├── es.json │ │ ├── fa.json │ │ ├── fr.json │ │ ├── gu.json │ │ ├── he.json │ │ ├── hi.json │ │ ├── hu.json │ │ ├── id.json │ │ ├── it.json │ │ ├── ja.json │ │ ├── jv.json │ │ ├── kn.json │ │ ├── ko.json │ │ ├── mr.json │ │ ├── ms.json │ │ ├── nb.json │ │ ├── nn.json │ │ ├── pa.json │ │ ├── pl.json │ │ ├── pt.json │ │ ├── ro.json │ │ ├── ru.json │ │ ├── sk.json │ │ ├── sl.json │ │ ├── sq.json │ │ ├── sv.json │ │ ├── te.json │ │ ├── th.json │ │ ├── tr.json │ │ ├── uk.json │ │ ├── ur.json │ │ ├── vi.json │ │ ├── zh-hant.json │ │ └── zh.json └── utils │ ├── array.ts │ ├── contacts.ts │ ├── date.ts │ ├── device.ts │ ├── email.ts │ ├── errors.tsx │ ├── file.ts │ ├── guards.ts │ ├── network.ts │ ├── numbers.ts │ ├── promise.ts │ ├── string.ts │ ├── styles.ts │ ├── ui.ts │ ├── units.ts │ ├── uri.ts │ └── username.ts ├── test ├── e2e │ ├── config.json │ ├── environment.js │ ├── firstTest.e2e.js │ └── tools.js └── mock-data.js ├── tsconfig.json ├── types ├── cldr-compact-number.d.ts ├── images.d.ts ├── react-native-slider.d.ts └── react-native.d.ts └── yarn.lock /.bundle/config: -------------------------------------------------------------------------------- 1 | BUNDLE_PATH: "vendor/bundle" 2 | BUNDLE_FORCE_RUBY_PLATFORM: 1 3 | -------------------------------------------------------------------------------- /.commitlintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = {extends: ['@commitlint/config-conventional']}; 2 | -------------------------------------------------------------------------------- /.detoxrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "testRunner": "jest", 3 | "runnerConfig": "test/e2e/config.json", 4 | "skipLegacyWorkersInjection": true, 5 | "apps": { 6 | "ios.release": { 7 | "type": "ios.app", 8 | "binaryPath": "ios/build/Build/Products/Release-iphonesimulator/ice.app", 9 | "build": "export RCT_NO_LAUNCH_PACKAGER=true && xcodebuild -workspace ios/ice.xcworkspace -scheme ice -UseNewBuildSystem=NO -configuration Release -destination 'platform=iOS Simulator,name=iPhone 13' -sdk iphonesimulator -derivedDataPath ios/build -quiet" 10 | }, 11 | "ios.debug": { 12 | "type": "ios.app", 13 | "binaryPath": "ios/build/Build/Products/Debug-iphonesimulator/ice.app", 14 | "build": "xcodebuild -workspace ios/ice.xcworkspace -scheme ice -configuration Debug -destination 'platform=iOS Simulator,name=iPhone 13' -derivedDataPath ios/build" 15 | }, 16 | "android.debug": { 17 | "type": "android.apk", 18 | "binaryPath": "android/app/build/outputs/apk/debug/app-debug.apk", 19 | "testBinaryPath": "android/app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk", 20 | "build": "cd android ; ./gradlew assembleDebug && ./gradlew assembleDebugAndroidTest -DtestBuildType=debug ; cd -" 21 | }, 22 | "android.release": { 23 | "type": "android.apk", 24 | "binaryPath": "android/app/build/outputs/apk/releasestagingE2E/app-releasestagingE2E.apk", 25 | "testBinaryPath": "android/app/build/outputs/apk/androidTest/releasestagingE2E/app-releasestagingE2E-androidTest.apk", 26 | "build": "cd android ; ./gradlew assembleReleasestagingE2E && ./gradlew assembleAndroidTest -DtestBuildType=releasestagingE2E ; cd -" 27 | } 28 | }, 29 | "devices": { 30 | "simulator": { 31 | "type": "ios.simulator", 32 | "device": { 33 | "type": "iPhone 13" 34 | } 35 | }, 36 | "emulator": { 37 | "type": "android.emulator", 38 | "device": { 39 | "avdName": "test" 40 | } 41 | } 42 | }, 43 | "configurations": { 44 | "ios.debug": { 45 | "device": "simulator", 46 | "app": "ios.debug" 47 | }, 48 | "ios.release": { 49 | "device": "simulator", 50 | "app": "ios.release", 51 | "binaryPath": "ios/build/Build/Products/Release-iphonesimulator/ice.app" 52 | }, 53 | "android.debug": { 54 | "device": "emulator", 55 | "app": "android.debug" 56 | }, 57 | "android.release": { 58 | "device": "emulator", 59 | "app": "android.release" 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @ice-blockchain/react-native @ice-blockchain/testers 2 | -------------------------------------------------------------------------------- /.github/slack.yml: -------------------------------------------------------------------------------- 1 | username: mobile-app-CI-CD 2 | icon_url: https://avatars.githubusercontent.com/u/102382658?s=400&u=62e73f4cb845c48a07a19e03c6f84b721e40c0a6&v=4 3 | 4 | pretext: "{{env.SLACK_MESSAGE_TITLE}}" 5 | title: Mobile App CI/CD 6 | title_link: https://github.com/ice-blockchain/mobile-app/actions 7 | 8 | text: | 9 | {{#if description}}<{{diffUrl}}|`{{diffRef}}`> - {{description}}{{/if}} 10 | 11 | fallback: |- 12 | [GitHub] {{workflow}} #{{runNumber}} {{jobName}} is {{jobStatus}} 13 | 14 | fields: 15 | - title: Event 16 | value: "`{{eventName}}`" 17 | short: true 18 | - title: Status 19 | value: "{{jobStatus}}" 20 | short: true 21 | - title: Actor 22 | value: "" 23 | short: true 24 | - title: Git Ref 25 | value: "<{{refUrl}}|`{{ref}}`> ({{refType}})" 26 | short: true 27 | - title: Environment 28 | value: "`{{env.APP_ENV}}`" 29 | short: true 30 | - title: Deployment 31 | value: "`{{env.DEPLOY_TARGET }}`" 32 | short: true 33 | - title: Workflow 34 | value: "<{{workflowUrl}}|{{workflow}}>" 35 | short: true 36 | - title: Job 37 | value: "<{{workflowRunUrl}}|{{jobName}}>" 38 | short: true 39 | - title: Run Number 40 | value: "{{runNumber}}" 41 | short: true 42 | - title: Run ID 43 | value: |- 44 | <{{workflowRunUrl}}|{{runId}}> 45 | short: true 46 | - title: Commits 47 | value: "{{#if payload.commits}}{{#each payload.commits}}<{{this.url}}|`{{truncate this.id 8}}`>{{/each}}{{/if}}" 48 | short: false 49 | 50 | footer: >- 51 | <{{repositoryUrl}}|{{repositoryName}}> {{workflow}} #{{runNumber}} 52 | 53 | colors: 54 | success: 'good' 55 | failure: 'danger' 56 | cancelled: 'warning' 57 | default: 'warning' 58 | 59 | icons: 60 | success: ':white_check_mark:' 61 | failure: ':x:' 62 | cancelled: ':no_entry_sign:' 63 | skipped: ':heavy_minus_sign:' 64 | default: ':interrobang:' 65 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # OSX 2 | # 3 | .DS_Store 4 | 5 | # Xcode 6 | # 7 | build/ 8 | *.pbxuser 9 | !default.pbxuser 10 | *.mode1v3 11 | !default.mode1v3 12 | *.mode2v3 13 | !default.mode2v3 14 | *.perspectivev3 15 | !default.perspectivev3 16 | xcuserdata 17 | *.xccheckout 18 | *.moved-aside 19 | DerivedData 20 | *.hmap 21 | *.ipa 22 | *.xcuserstate 23 | ios/.xcode.env.local 24 | WorkspaceSettings.xcsettings 25 | 26 | # VSCode 27 | # 28 | .vscode 29 | 30 | # Android/IntelliJ 31 | # 32 | build/ 33 | .idea 34 | .gradle 35 | local.properties 36 | *.iml 37 | *.hprof 38 | .cxx/ 39 | 40 | # node.js 41 | # 42 | node_modules/ 43 | npm-debug.log 44 | yarn-error.log 45 | 46 | # BUCK 47 | buck-out/ 48 | \.buckd/ 49 | *.keystore 50 | 51 | # fastlane 52 | # 53 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 54 | # screenshots whenever they are needed. 55 | # For more information about the recommended setup visit: 56 | # https://docs.fastlane.tools/best-practices/source-control/ 57 | 58 | **/fastlane/report.xml 59 | **/fastlane/Preview.html 60 | **/fastlane/screenshots 61 | **/fastlane/test_output 62 | 63 | # Bundle artifact 64 | *.jsbundle 65 | artifacts/ 66 | 67 | # Ruby / CocoaPods 68 | /ios/Pods/ 69 | /vendor/bundle/ 70 | 71 | # Environments 72 | 73 | *.env* 74 | configuration/ 75 | android/gradle.properties 76 | android/sentry.properties 77 | gradle.properties 78 | google-services.json 79 | GoogleService-Info.plist 80 | ios/ice/Info.plist 81 | ios/ice/ice.entitlements 82 | ios/Configurations/ice.custom.xcconfig 83 | ios/NotificationService/NotificationService.entitlements 84 | ios/NotificationService/NotificationService.custom.xcconfig 85 | 86 | sentry.properties 87 | 88 | # Build related files 89 | android/app/src/main/assets/app.bundle 90 | android/app/src/main/assets/index.android.bundle 91 | android/app/release 92 | android/app/src/main/assets/app.manifest 93 | *.jks 94 | 95 | coverage/ 96 | .history 97 | 98 | # 99 | *.dSYM.zip 100 | 101 | # Temporary files created by Metro to check the health of the file watcher 102 | .metro-health-check* 103 | -------------------------------------------------------------------------------- /.husky/commit-msg: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | npx --no-install commitlint --edit $1 -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn run check-license 5 | yarn run generate-translation-types 6 | yarn run lint 7 | yarn run check-types 8 | yarn run check-git-email 9 | yarn pretty-staged 10 | yarn compress-staged-images 11 | -------------------------------------------------------------------------------- /.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | arrowParens: 'avoid', 3 | bracketSameLine: true, 4 | bracketSpacing: false, 5 | singleQuote: true, 6 | trailingComma: 'all', 7 | }; 8 | -------------------------------------------------------------------------------- /.ruby-version: -------------------------------------------------------------------------------- 1 | 3.1.3 2 | -------------------------------------------------------------------------------- /.watchmanconfig: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | ruby '3.1.3' 4 | 5 | gem 'fastlane' 6 | gem 'cocoapods', '~> 1.12' 7 | 8 | plugins_path = File.join(File.dirname(__FILE__), 'fastlane', 'Pluginfile') 9 | eval_gemfile(plugins_path) if File.exist?(plugins_path) 10 | -------------------------------------------------------------------------------- /LICENSE.header: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | ice License 2 | 3 | Version 1.0, January 2023 4 | 5 | ----------------------------------------------------------------------------- 6 | 7 | 8 | Licensor: ice Labs Limited 9 | 10 | Licensed Work: ice Network 11 | The Licensed Work is (c) 2023 ice Labs Limited 12 | 13 | ----------------------------------------------------------------------------- 14 | 15 | 16 | Permission is hereby granted by the application Software Developer, ice Labs 17 | Limited, free of charge, to any person obtaining a copy of this application, 18 | software, and associated documentation files (the Software), which was 19 | developed by the Software Developer (ice Labs Limited) for use on ice Network 20 | whereby the purpose of this license is to permit the development of 21 | derivative works based on the Software, including the right to use, copy, 22 | modify, merge, publish, distribute, sub-license, and/or sell copies of such 23 | derivative works and any Software components incorporated therein, and to 24 | permit persons to whom such derivative works are furnished to do so, in each 25 | case, solely to develop, use, and market applications for the official ice 26 | Network. 27 | 28 | All Derivative Works developed under this License for use on the ice Network 29 | may only be released after the official launch of the ice Network’s Mainnet. 30 | 31 | For purposes of this license, ice Network shall mean any application, 32 | software, or another present or future platform developed, owned, or managed 33 | by ice Labs Limited, and its parents, affiliates, or subsidiaries. 34 | 35 | Disclaimer of Warranty. Unless required by applicable law or agreed to in 36 | writing, Licensor provides the Software on an "AS IS" BASIS, WITHOUT 37 | WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, 38 | without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, 39 | MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely 40 | responsible for determining the appropriateness of using or redistributing 41 | the Software and assume any risks associated with Your exercise of 42 | permissions under this License. 43 | 44 | Limitation of Liability. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 45 | BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY, WHETHER IN AN ACTION 46 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF, OR IN CONNECTION WITH 47 | THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 48 | 49 | The above copyright notice and this permission notice shall be included in 50 | all copies or substantial portions of the Software. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Ice Blockchain Mobile App 2 | 3 | React Native App for the mining of ice coins 4 | 5 | ## Project Structure 6 | 7 | ``` 8 | - src: Main container of all the code inside the application 9 | - api: Methods to communicate with the backend 10 | - assets: Assets folder to store all the images, icons, fonts, etc 11 | - components: Folder to store common components that are used throughout the app (such as Button, Switch, Avatar etc) 12 | - hooks: Common hooks 13 | - navigation: App navigation components. Those are stacks, tabs and so on 14 | - screens: Screen components. Each screen contains `index.tsx` with the main screen markdown plus optional `components` `assets` `hooks` subfolders with the corresponding content 15 | - services: Abstraction layer over some common services (e.g. keychain, analytics, logging and so on) 16 | - store: Redux configuration files (store, middlewares etc) and business logic modules. Each module might consist of selectors, actions, sagas, reducers and hooks 17 | - translations: Everything related to the app l10n 18 | - utils: Reusable functions which are helpful for accomplishing routine tasks 19 | - App.tsx: Main component that starts the whole app 20 | - ios android: Folders, containing native iOS and Android projects and everything related 21 | - index.js: Entry point of the application as per React-Native standards 22 | ``` 23 | 24 | ## Run the App locally 25 | 26 | - Follow the [steps to setup the environment](https://reactnative.dev/docs/environment-setup) 27 | 28 | - Install dependencies 29 | 30 | ``` 31 | yarn install 32 | 33 | ## Optional: for iOS only 34 | npx pod-install 35 | ``` 36 | 37 | - Set the App environment 38 | 39 | ``` 40 | yarn configure:staging 41 | ## or 42 | yarn configure:production 43 | ``` 44 | 45 | > [!NOTE] 46 | > The commands above take the app env files from the [mobile-app-secrets](https://github.com/ice-blockchain/mobile-app-secrets) folder that should be downloaded beforehand and placed on the same level as the mobile-app project 47 | 48 | - Run the App 49 | ``` 50 | yarn run:android 51 | ## or 52 | yarn run:ios 53 | ``` 54 | 55 | ## Localization rules 56 | 57 | We’re keeping translations in `src/translations/locales/**.json`. 58 | The translation types are generated with `yarn generate-translation-types` script (should be applied after any locale modification). 59 | The types are generated based on `en.json` - that is the main locale and the fallback for other locales. 60 | 61 | ## Download the App 62 | 63 | Download the App at https://ice.io 64 | -------------------------------------------------------------------------------- /android/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | -keep class com.facebook.hermes.unicode.** { *; } 13 | -keep class com.facebook.jni.** { *; } -------------------------------------------------------------------------------- /android/app/src/androidTest/java/io/ice/app/DetoxTest.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | package io.ice.app; 4 | 5 | import com.wix.detox.Detox; 6 | import com.wix.detox.config.DetoxConfig; 7 | 8 | import org.junit.Rule; 9 | import org.junit.Test; 10 | import org.junit.runner.RunWith; 11 | 12 | import androidx.test.ext.junit.runners.AndroidJUnit4; 13 | import androidx.test.filters.LargeTest; 14 | import androidx.test.rule.ActivityTestRule; 15 | 16 | @RunWith(AndroidJUnit4.class) 17 | @LargeTest 18 | public class DetoxTest { 19 | // Replace 'MainActivity' with the value of android:name entry in 20 | // in AndroidManifest.xml 21 | @Rule 22 | public ActivityTestRule mActivityRule = new ActivityTestRule<>(MainActivity.class, false, false); 23 | 24 | @Test 25 | public void runDetoxTests() { 26 | DetoxConfig detoxConfig = new DetoxConfig(); 27 | detoxConfig.idlePolicyConfig.masterTimeoutSec = 900; 28 | detoxConfig.idlePolicyConfig.idleResourceTimeoutSec = 600; 29 | detoxConfig.rnContextLoadTimeoutSec = (io.ice.app.BuildConfig.DEBUG ? 2000 : 600); 30 | 31 | Detox.runTests(mActivityRule, detoxConfig); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /android/app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Lato-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/android/app/src/main/assets/fonts/Lato-Black.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Lato-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/android/app/src/main/assets/fonts/Lato-Bold.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Lato-Hairline.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/android/app/src/main/assets/fonts/Lato-Hairline.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Lato-Heavy.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/android/app/src/main/assets/fonts/Lato-Heavy.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Lato-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/android/app/src/main/assets/fonts/Lato-Light.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Lato-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/android/app/src/main/assets/fonts/Lato-Medium.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Lato-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/android/app/src/main/assets/fonts/Lato-Regular.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Lato-Semibold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/android/app/src/main/assets/fonts/Lato-Semibold.ttf -------------------------------------------------------------------------------- /android/app/src/main/assets/fonts/Lato-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/android/app/src/main/assets/fonts/Lato-Thin.ttf -------------------------------------------------------------------------------- /android/app/src/main/java/io/ice/app/MainActivity.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | package io.ice.app; 4 | import expo.modules.ReactActivityDelegateWrapper; 5 | 6 | import android.os.Bundle; 7 | import com.facebook.react.ReactActivity; 8 | import com.facebook.react.ReactActivityDelegate; 9 | import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint; 10 | import com.facebook.react.defaults.DefaultReactActivityDelegate; 11 | import com.facebook.react.modules.i18nmanager.I18nUtil; 12 | import com.zoontek.rnbootsplash.RNBootSplash; 13 | 14 | public class MainActivity extends ReactActivity { 15 | 16 | /** 17 | * Returns the name of the main component registered from JavaScript. This is used to schedule 18 | * rendering of the component. 19 | */ 20 | @Override 21 | protected String getMainComponentName() { 22 | return "ice"; 23 | } 24 | 25 | /** 26 | * react-native-screens override 27 | * https://github.com/software-mansion/react-native-screens#android 28 | */ 29 | @Override 30 | protected void onCreate(Bundle savedInstanceState) { 31 | RNBootSplash.init(this, R.style.BootTheme); 32 | super.onCreate(null); 33 | I18nUtil.getInstance().allowRTL(this, false); 34 | } 35 | 36 | /** 37 | * Returns the instance of the {@link ReactActivityDelegate}. Here we use a util class {@link 38 | * DefaultReactActivityDelegate} which allows you to easily enable Fabric and Concurrent React 39 | * (aka React 18) with two boolean flags. 40 | */ 41 | @Override 42 | protected ReactActivityDelegate createReactActivityDelegate() { 43 | return new ReactActivityDelegateWrapper(this, BuildConfig.IS_NEW_ARCHITECTURE_ENABLED, new DefaultReactActivityDelegate( 44 | this, 45 | getMainComponentName(), 46 | // If you opted-in for the New Architecture, we enable the Fabric Renderer. 47 | DefaultNewArchitectureEntryPoint.getFabricEnabled())); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /android/app/src/main/java/io/ice/app/MainApplication.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | package io.ice.app; 4 | import android.content.res.Configuration; 5 | import expo.modules.ApplicationLifecycleDispatcher; 6 | import expo.modules.ReactNativeHostWrapper; 7 | 8 | import android.app.Application; 9 | import com.facebook.react.PackageList; 10 | import com.facebook.react.ReactApplication; 11 | import com.facebook.react.ReactNativeHost; 12 | import com.facebook.react.ReactPackage; 13 | import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint; 14 | import com.facebook.react.defaults.DefaultReactNativeHost; 15 | import com.facebook.soloader.SoLoader; 16 | import java.util.List; 17 | 18 | public class MainApplication extends Application implements ReactApplication { 19 | 20 | private final ReactNativeHost mReactNativeHost = 21 | new ReactNativeHostWrapper(this, new DefaultReactNativeHost(this) { 22 | @Override 23 | public boolean getUseDeveloperSupport() { 24 | return BuildConfig.DEBUG; 25 | } 26 | 27 | @Override 28 | protected List getPackages() { 29 | @SuppressWarnings("UnnecessaryLocalVariable") 30 | List packages = new PackageList(this).getPackages(); 31 | // Packages that cannot be autolinked yet can be added manually here, for example: 32 | // packages.add(new MyReactNativePackage()); 33 | return packages; 34 | } 35 | 36 | @Override 37 | protected String getJSMainModuleName() { 38 | return "index"; 39 | } 40 | 41 | @Override 42 | protected boolean isNewArchEnabled() { 43 | return BuildConfig.IS_NEW_ARCHITECTURE_ENABLED; 44 | } 45 | 46 | @Override 47 | protected Boolean isHermesEnabled() { 48 | return BuildConfig.IS_HERMES_ENABLED; 49 | } 50 | }); 51 | 52 | @Override 53 | public ReactNativeHost getReactNativeHost() { 54 | return mReactNativeHost; 55 | } 56 | 57 | @Override 58 | public void onCreate() { 59 | super.onCreate(); 60 | SoLoader.init(this, /* native exopackage */ false); 61 | if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) { 62 | // If you opted-in for the New Architecture, we load the native entry point for this app. 63 | DefaultNewArchitectureEntryPoint.load(); 64 | } 65 | ReactNativeFlipper.initializeFlipper(this, getReactNativeHost().getReactInstanceManager()); 66 | 67 | ApplicationLifecycleDispatcher.onApplicationCreate(this); 68 | } 69 | 70 | @Override 71 | public void onConfigurationChanged(Configuration newConfig) { 72 | super.onConfigurationChanged(newConfig); 73 | ApplicationLifecycleDispatcher.onConfigurationChanged(this, newConfig); 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-hdpi/ic_stat_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/android/app/src/main/res/drawable-hdpi/ic_stat_notification.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-mdpi/ic_stat_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/android/app/src/main/res/drawable-mdpi/ic_stat_notification.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xhdpi/ic_stat_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/android/app/src/main/res/drawable-xhdpi/ic_stat_notification.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxhdpi/ic_stat_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/android/app/src/main/res/drawable-xxhdpi/ic_stat_notification.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable-xxxhdpi/ic_stat_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/android/app/src/main/res/drawable-xxxhdpi/ic_stat_notification.png -------------------------------------------------------------------------------- /android/app/src/main/res/drawable/rn_edit_text_material.xml: -------------------------------------------------------------------------------- 1 | 2 | 16 | 21 | 22 | 23 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/android/app/src/main/res/mipmap-hdpi/ic_launcher_background.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/android/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-hdpi/transparent_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/android/app/src/main/res/mipmap-hdpi/transparent_logo.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/android/app/src/main/res/mipmap-mdpi/ic_launcher_background.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/android/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-mdpi/transparent_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/android/app/src/main/res/mipmap-mdpi/transparent_logo.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/android/app/src/main/res/mipmap-xhdpi/ic_launcher_background.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/android/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xhdpi/transparent_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/android/app/src/main/res/mipmap-xhdpi/transparent_logo.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_background.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/android/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxhdpi/transparent_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/android/app/src/main/res/mipmap-xxhdpi/transparent_logo.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_background.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /android/app/src/main/res/mipmap-xxxhdpi/transparent_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/android/app/src/main/res/mipmap-xxxhdpi/transparent_logo.png -------------------------------------------------------------------------------- /android/app/src/main/res/raw/bonus_received.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/android/app/src/main/res/raw/bonus_received.wav -------------------------------------------------------------------------------- /android/app/src/main/res/raw/extend_mining.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/android/app/src/main/res/raw/extend_mining.wav -------------------------------------------------------------------------------- /android/app/src/main/res/raw/start_mining.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/android/app/src/main/res/raw/start_mining.wav -------------------------------------------------------------------------------- /android/app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #1B47C3 4 | 5 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Ice 3 | 4 | -------------------------------------------------------------------------------- /android/app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 14 | 15 | -------------------------------------------------------------------------------- /android/app/src/release/java/io/ice/app/ReactNativeFlipper.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | /** 4 | * Copyright (c) Meta Platforms, Inc. and affiliates. 5 | * 6 | *

This source code is licensed under the MIT license found in the LICENSE file in the root 7 | * directory of this source tree. 8 | */ 9 | package io.ice.app; 10 | import android.content.Context; 11 | import com.facebook.react.ReactInstanceManager; 12 | /** 13 | * Class responsible of loading Flipper inside your React Native application. This is the release 14 | * flavor of it so it's empty as we don't want to load Flipper. 15 | */ 16 | public class ReactNativeFlipper { 17 | public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) { 18 | // Do nothing as we don't want to initialize Flipper on Release. 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /android/app/src/releasestaging/java/io/ice/app/ReactNativeFlipper.java: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | /** 4 | * Copyright (c) Meta Platforms, Inc. and affiliates. 5 | * 6 | *

This source code is licensed under the MIT license found in the LICENSE file in the root 7 | * directory of this source tree. 8 | */ 9 | package io.ice.app; 10 | import android.content.Context; 11 | import com.facebook.react.ReactInstanceManager; 12 | /** 13 | * Class responsible of loading Flipper inside your React Native application. This is the release 14 | * flavor of it so it's empty as we don't want to load Flipper. 15 | */ 16 | public class ReactNativeFlipper { 17 | public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) { 18 | // Do nothing as we don't want to initialize Flipper on Release. 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 4 | 5 | buildscript { 6 | ext { 7 | buildToolsVersion = "33.0.0" 8 | minSdkVersion = 23 9 | compileSdkVersion = 33 10 | targetSdkVersion = 33 11 | kotlinVersion = '1.8.10' 12 | // We use NDK 23 which has both M1 support and is the side-by-side NDK version from AGP. 13 | ndkVersion = "23.1.7779620" 14 | androidXAnnotation = "1.5.0" 15 | androidXBrowser = "1.4.0" 16 | } 17 | repositories { 18 | google() 19 | mavenCentral() 20 | } 21 | dependencies { 22 | classpath("com.facebook.react:react-native-gradle-plugin") 23 | classpath 'com.google.gms:google-services:4.3.14' 24 | classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion") 25 | } 26 | } 27 | 28 | allprojects { 29 | repositories { 30 | jcenter() 31 | maven { url "$rootDir/../node_modules/detox/Detox-android" } 32 | maven { url 'https://maven.google.com' } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.1-all.zip 4 | networkTimeout=10000 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /android/link-assets-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "migIndex": 1, 3 | "data": [ 4 | { 5 | "path": "src/assets/fonts/Lato-Black.ttf", 6 | "sha1": "b9c952639741f5b7479e1ff6d1561a3df7e8f83a" 7 | }, 8 | { 9 | "path": "src/assets/fonts/Lato-Bold.ttf", 10 | "sha1": "6b2c7b124cbf0aaeba48d57fb0fa19f2c6c69683" 11 | }, 12 | { 13 | "path": "src/assets/fonts/Lato-Hairline.ttf", 14 | "sha1": "79c1dd6a9c740ef36272a9742504864a6f912be7" 15 | }, 16 | { 17 | "path": "src/assets/fonts/Lato-Heavy.ttf", 18 | "sha1": "068af974d1346b61d9ee1e097374f2f3cac6c442" 19 | }, 20 | { 21 | "path": "src/assets/fonts/Lato-Light.ttf", 22 | "sha1": "a405c8288a8a90881407f93b6ee02b29e26a8735" 23 | }, 24 | { 25 | "path": "src/assets/fonts/Lato-Medium.ttf", 26 | "sha1": "c78e94b7cc0b782eef4f9f2be371c3cf9c3f6eaf" 27 | }, 28 | { 29 | "path": "src/assets/fonts/Lato-Regular.ttf", 30 | "sha1": "f59f9e4f3cbee981a5e6f58a279f9b9613f22599" 31 | }, 32 | { 33 | "path": "src/assets/fonts/Lato-Semibold.ttf", 34 | "sha1": "96569e2cfcc3a298bb1aea21103d0d1e3c7e2ed4" 35 | }, 36 | { 37 | "path": "src/assets/fonts/Lato-Thin.ttf", 38 | "sha1": "dbe1f622faef3ea3f286d848da6b10f104405060" 39 | } 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | rootProject.name = 'ice' 4 | apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings) 5 | include ':app' 6 | includeBuild('../node_modules/@react-native/gradle-plugin') 7 | 8 | apply from: new File(["node", "--print", "require.resolve('expo/package.json')"].execute(null, rootDir).text.trim(), "../scripts/autolinking.gradle") 9 | useExpoModules() -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ice", 3 | "displayName": "ice" 4 | } 5 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = api => { 2 | api.cache(true); 3 | return { 4 | presets: ['module:metro-react-native-babel-preset'], 5 | env: { 6 | production: { 7 | plugins: ['transform-remove-console'], 8 | }, 9 | }, 10 | plugins: [ 11 | '@babel/plugin-transform-runtime', 12 | [ 13 | 'module-resolver', 14 | { 15 | root: ['.'], 16 | alias: { 17 | '@components': './src/components', 18 | '@api': './src/api', 19 | '@services': './src/services', 20 | '@navigation': './src/navigation', 21 | '@images': './src/assets/images', 22 | '@lottie': './src/assets/lottie', 23 | '@flags': './src/assets/flags', 24 | '@svg': './src/assets/svg', 25 | '@audio': './src/assets/audio', 26 | '@constants': './src/constants', 27 | '@screens': './src/screens', 28 | '@store': './src/store', 29 | '@translations': './src/translations', 30 | '@utils': './src/utils', 31 | '@hooks': './src/hooks', 32 | }, 33 | }, 34 | ], 35 | ['react-native-reanimated/plugin'], 36 | ], 37 | exclude: ['**/*.png', '**/*.jpg', '**/*.gif'], 38 | }; 39 | }; 40 | -------------------------------------------------------------------------------- /fastlane/Matchfile: -------------------------------------------------------------------------------- 1 | # SPDX-License-Identifier: ice License 1.0 2 | 3 | type("appstore") 4 | 5 | app_identifier(["io.ice.app.staging", "io.ice.app", "io.ice.app.staging.NotificationService", "io.ice.app.NotificationService"]) 6 | -------------------------------------------------------------------------------- /fastlane/Pluginfile: -------------------------------------------------------------------------------- 1 | # Autogenerated by fastlane 2 | # 3 | # Ensure this file is checked in to source control! 4 | 5 | gem 'fastlane-plugin-firebase_app_distribution' 6 | gem 'fastlane-plugin-increment_version_code' 7 | gem 'fastlane-plugin-increment_version_name' 8 | gem 'fastlane-plugin-xcconfig' 9 | -------------------------------------------------------------------------------- /fastlane/README.md: -------------------------------------------------------------------------------- 1 | fastlane documentation 2 | ---- 3 | 4 | # Installation 5 | 6 | Make sure you have the latest version of the Xcode command line tools installed: 7 | 8 | ```sh 9 | xcode-select --install 10 | ``` 11 | 12 | For _fastlane_ installation instructions, see [Installing _fastlane_](https://docs.fastlane.tools/#installing-fastlane) 13 | 14 | # Available Actions 15 | 16 | ## iOS 17 | 18 | ### ios bump_version 19 | 20 | ```sh 21 | [bundle exec] fastlane ios bump_version 22 | ``` 23 | 24 | Bump iOS version 25 | 26 | ### ios build 27 | 28 | ```sh 29 | [bundle exec] fastlane ios build 30 | ``` 31 | 32 | Build iOS app 33 | 34 | ### ios deploy 35 | 36 | ```sh 37 | [bundle exec] fastlane ios deploy 38 | ``` 39 | 40 | Deploy iOS app 41 | 42 | ---- 43 | 44 | 45 | ## Android 46 | 47 | ### android bump_version 48 | 49 | ```sh 50 | [bundle exec] fastlane android bump_version 51 | ``` 52 | 53 | Bump Android version 54 | 55 | ### android build 56 | 57 | ```sh 58 | [bundle exec] fastlane android build 59 | ``` 60 | 61 | Build Android app 62 | 63 | ### android deploy 64 | 65 | ```sh 66 | [bundle exec] fastlane android deploy 67 | ``` 68 | 69 | Deploy Android app 70 | 71 | ---- 72 | 73 | This README.md is auto-generated and will be re-generated every time [_fastlane_](https://fastlane.tools) is run. 74 | 75 | More information about _fastlane_ can be found on [fastlane.tools](https://fastlane.tools). 76 | 77 | The documentation of _fastlane_ can be found on [docs.fastlane.tools](https://docs.fastlane.tools). 78 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {AppRegistry} from 'react-native'; 4 | import 'react-native-url-polyfill/auto'; 5 | import {name as appName} from './app.json'; 6 | import {App} from './src/App'; 7 | 8 | AppRegistry.registerComponent(appName, () => App); 9 | -------------------------------------------------------------------------------- /ios/Configurations/ice.debug.xcconfig: -------------------------------------------------------------------------------- 1 | 2 | #include "Pods/Target Support Files/Pods-ice/Pods-ice.debug.xcconfig" 3 | #include "./ice.custom.xcconfig" 4 | -------------------------------------------------------------------------------- /ios/Configurations/ice.release.xcconfig: -------------------------------------------------------------------------------- 1 | 2 | #include "Pods/Target Support Files/Pods-ice/Pods-ice.release.xcconfig" 3 | #include "./ice.custom.xcconfig" 4 | -------------------------------------------------------------------------------- /ios/File.swift: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import Foundation 4 | -------------------------------------------------------------------------------- /ios/Ice-Bridging-Header.h: -------------------------------------------------------------------------------- 1 | #import 2 | // SPDX-License-Identifier: ice License 1.0 3 | 4 | // 5 | // Use this file to import your target's public headers that you would like to expose to Swift. 6 | // 7 | 8 | -------------------------------------------------------------------------------- /ios/NotificationService/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSExtension 6 | 7 | NSExtensionPointIdentifier 8 | com.apple.usernotifications.service 9 | NSExtensionPrincipalClass 10 | NotificationService 11 | 12 | CFBundleIdentifier 13 | ${PRODUCT_BUNDLE_IDENTIFIER} 14 | 15 | 16 | -------------------------------------------------------------------------------- /ios/NotificationService/NotificationService.debug.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-NotificationService/Pods-NotificationService.debug.xcconfig" 2 | #include "./NotificationService.custom.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/NotificationService/NotificationService.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | #import 4 | 5 | @interface NotificationService : UNNotificationServiceExtension 6 | 7 | @end 8 | -------------------------------------------------------------------------------- /ios/NotificationService/NotificationService.m: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | #import "NotificationService.h" 4 | #import "FirebaseMessaging.h" 5 | 6 | @interface NotificationService () 7 | 8 | @property (nonatomic, strong) void (^contentHandler)(UNNotificationContent *contentToDeliver); 9 | @property (nonatomic, strong) UNMutableNotificationContent *bestAttemptContent; 10 | 11 | @end 12 | 13 | @implementation NotificationService 14 | 15 | - (void)didReceiveNotificationRequest:(UNNotificationRequest *)request withContentHandler:(void (^)(UNNotificationContent * _Nonnull))contentHandler { 16 | self.contentHandler = contentHandler; 17 | self.bestAttemptContent = [request.content mutableCopy]; 18 | [[FIRMessaging extensionHelper] populateNotificationContent:self.bestAttemptContent withContentHandler:contentHandler]; 19 | } 20 | 21 | - (void)serviceExtensionTimeWillExpire { 22 | // Called just before the extension will be terminated by the system. 23 | // Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used. 24 | self.contentHandler(self.bestAttemptContent); 25 | } 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /ios/NotificationService/NotificationService.release.xcconfig: -------------------------------------------------------------------------------- 1 | #include "Pods/Target Support Files/Pods-NotificationService/Pods-NotificationService.release.xcconfig" 2 | #include "./NotificationService.custom.xcconfig" 3 | -------------------------------------------------------------------------------- /ios/audio/bonus_received.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/ios/audio/bonus_received.wav -------------------------------------------------------------------------------- /ios/audio/extend_mining.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/ios/audio/extend_mining.wav -------------------------------------------------------------------------------- /ios/audio/start_mining.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/ios/audio/start_mining.wav -------------------------------------------------------------------------------- /ios/ice.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/ice.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/ice/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | #import 4 | #import 5 | #import 6 | #import 7 | #import 8 | 9 | @interface AppDelegate : EXAppDelegateWrapper 10 | 11 | // add this line to the @interface section of AppDelegate 12 | @property (nonatomic, strong) NSString *moeDeeplink; 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /ios/ice/Images.xcassets/AppIcon.appiconset/Icon-167x167.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/ios/ice/Images.xcassets/AppIcon.appiconset/Icon-167x167.png -------------------------------------------------------------------------------- /ios/ice/Images.xcassets/AppIcon.appiconset/Icon-20x20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/ios/ice/Images.xcassets/AppIcon.appiconset/Icon-20x20.png -------------------------------------------------------------------------------- /ios/ice/Images.xcassets/AppIcon.appiconset/Icon-20x20@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/ios/ice/Images.xcassets/AppIcon.appiconset/Icon-20x20@2x.png -------------------------------------------------------------------------------- /ios/ice/Images.xcassets/AppIcon.appiconset/Icon-20x20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/ios/ice/Images.xcassets/AppIcon.appiconset/Icon-20x20@3x.png -------------------------------------------------------------------------------- /ios/ice/Images.xcassets/AppIcon.appiconset/Icon-29x29.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/ios/ice/Images.xcassets/AppIcon.appiconset/Icon-29x29.png -------------------------------------------------------------------------------- /ios/ice/Images.xcassets/AppIcon.appiconset/Icon-29x29@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/ios/ice/Images.xcassets/AppIcon.appiconset/Icon-29x29@2x-1.png -------------------------------------------------------------------------------- /ios/ice/Images.xcassets/AppIcon.appiconset/Icon-29x29@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/ios/ice/Images.xcassets/AppIcon.appiconset/Icon-29x29@2x.png -------------------------------------------------------------------------------- /ios/ice/Images.xcassets/AppIcon.appiconset/Icon-29x29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/ios/ice/Images.xcassets/AppIcon.appiconset/Icon-29x29@3x.png -------------------------------------------------------------------------------- /ios/ice/Images.xcassets/AppIcon.appiconset/Icon-40x40-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/ios/ice/Images.xcassets/AppIcon.appiconset/Icon-40x40-1.png -------------------------------------------------------------------------------- /ios/ice/Images.xcassets/AppIcon.appiconset/Icon-40x40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/ios/ice/Images.xcassets/AppIcon.appiconset/Icon-40x40.png -------------------------------------------------------------------------------- /ios/ice/Images.xcassets/AppIcon.appiconset/Icon-40x40@2x-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/ios/ice/Images.xcassets/AppIcon.appiconset/Icon-40x40@2x-1.png -------------------------------------------------------------------------------- /ios/ice/Images.xcassets/AppIcon.appiconset/Icon-40x40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/ios/ice/Images.xcassets/AppIcon.appiconset/Icon-40x40@2x.png -------------------------------------------------------------------------------- /ios/ice/Images.xcassets/AppIcon.appiconset/Icon-40x40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/ios/ice/Images.xcassets/AppIcon.appiconset/Icon-40x40@3x.png -------------------------------------------------------------------------------- /ios/ice/Images.xcassets/AppIcon.appiconset/Icon-60x60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/ios/ice/Images.xcassets/AppIcon.appiconset/Icon-60x60@2x.png -------------------------------------------------------------------------------- /ios/ice/Images.xcassets/AppIcon.appiconset/Icon-60x60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/ios/ice/Images.xcassets/AppIcon.appiconset/Icon-60x60@3x.png -------------------------------------------------------------------------------- /ios/ice/Images.xcassets/AppIcon.appiconset/Icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/ios/ice/Images.xcassets/AppIcon.appiconset/Icon-76x76.png -------------------------------------------------------------------------------- /ios/ice/Images.xcassets/AppIcon.appiconset/Icon-76x76@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/ios/ice/Images.xcassets/AppIcon.appiconset/Icon-76x76@2x.png -------------------------------------------------------------------------------- /ios/ice/Images.xcassets/AppIcon.appiconset/Icon-marketing-1024x1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/ios/ice/Images.xcassets/AppIcon.appiconset/Icon-marketing-1024x1024.png -------------------------------------------------------------------------------- /ios/ice/Images.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /ios/ice/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /ios/ice/main.m: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | #import 4 | 5 | #import "AppDelegate.h" 6 | 7 | int main(int argc, char *argv[]) 8 | { 9 | @autoreleasepool { 10 | return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /ios/link-assets-manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "migIndex": 1, 3 | "data": [ 4 | { 5 | "path": "src/assets/fonts/Lato-Black.ttf", 6 | "sha1": "b9c952639741f5b7479e1ff6d1561a3df7e8f83a" 7 | }, 8 | { 9 | "path": "src/assets/fonts/Lato-Bold.ttf", 10 | "sha1": "6b2c7b124cbf0aaeba48d57fb0fa19f2c6c69683" 11 | }, 12 | { 13 | "path": "src/assets/fonts/Lato-Hairline.ttf", 14 | "sha1": "79c1dd6a9c740ef36272a9742504864a6f912be7" 15 | }, 16 | { 17 | "path": "src/assets/fonts/Lato-Heavy.ttf", 18 | "sha1": "068af974d1346b61d9ee1e097374f2f3cac6c442" 19 | }, 20 | { 21 | "path": "src/assets/fonts/Lato-Light.ttf", 22 | "sha1": "a405c8288a8a90881407f93b6ee02b29e26a8735" 23 | }, 24 | { 25 | "path": "src/assets/fonts/Lato-Medium.ttf", 26 | "sha1": "c78e94b7cc0b782eef4f9f2be371c3cf9c3f6eaf" 27 | }, 28 | { 29 | "path": "src/assets/fonts/Lato-Regular.ttf", 30 | "sha1": "f59f9e4f3cbee981a5e6f58a279f9b9613f22599" 31 | }, 32 | { 33 | "path": "src/assets/fonts/Lato-Semibold.ttf", 34 | "sha1": "96569e2cfcc3a298bb1aea21103d0d1e3c7e2ed4" 35 | }, 36 | { 37 | "path": "src/assets/fonts/Lato-Thin.ttf", 38 | "sha1": "dbe1f622faef3ea3f286d848da6b10f104405060" 39 | } 40 | ] 41 | } 42 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'react-native', 3 | transformIgnorePatterns: [ 4 | 'node_modules/(?!@react-native|react-native|react-native-sha256|react-native-device-info)', 5 | ], 6 | setupFilesAfterEnv: ['@testing-library/jest-native/extend-expect'], 7 | moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], 8 | }; 9 | -------------------------------------------------------------------------------- /metro.config.js: -------------------------------------------------------------------------------- 1 | const {getDefaultConfig, mergeConfig} = require('@react-native/metro-config'); 2 | 3 | /** 4 | * Metro configuration 5 | * https://facebook.github.io/metro/docs/configuration 6 | * 7 | * @type {import('metro-config').MetroConfig} 8 | */ 9 | const config = {}; 10 | 11 | module.exports = mergeConfig(getDefaultConfig(__dirname), config); 12 | -------------------------------------------------------------------------------- /patches/@react-native-firebase+auth+18.8.0.patch: -------------------------------------------------------------------------------- 1 | diff --git a/node_modules/@react-native-firebase/auth/lib/index.d.ts b/node_modules/@react-native-firebase/auth/lib/index.d.ts 2 | index 8041c2d..cdb8189 100644 3 | --- a/node_modules/@react-native-firebase/auth/lib/index.d.ts 4 | +++ b/node_modules/@react-native-firebase/auth/lib/index.d.ts 5 | @@ -113,12 +113,15 @@ export namespace FirebaseAuthTypes { 6 | * Interface that represents an OAuth provider. Implemented by other providers. 7 | */ 8 | export interface OAuthProvider { 9 | + /** 10 | + * The provider ID of the provider. 11 | + */ 12 | + PROVIDER_ID: string; 13 | /** 14 | * The provider ID of the provider. 15 | * @param providerId 16 | */ 17 | - // eslint-disable-next-line @typescript-eslint/no-misused-new 18 | - new (providerId: string): AuthProvider; 19 | + new (providerId: string): OAuthProvider; 20 | /** 21 | * Creates a new `AuthCredential`. 22 | * 23 | -------------------------------------------------------------------------------- /react-native.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | project: { 3 | ios: {}, 4 | android: {}, // grouped into "project" 5 | }, 6 | 7 | /** 8 | * No longer supported by react-native cli. 9 | * Need for `react-native-asset` 10 | * Run `npx react-native-asset` to link fonts 11 | * */ 12 | assets: ['./src/assets/fonts/'], 13 | }; 14 | -------------------------------------------------------------------------------- /scripts/check-git-email.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | EMAIL=$(git config user.email) 4 | if [[ $EMAIL != *"@users.noreply.github.com"* ]]; then 5 | echo "[INFO] Invalid email: $EMAIL" 6 | exit 1; 7 | fi; -------------------------------------------------------------------------------- /scripts/compress-staged-images.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | source .env 4 | 5 | ExecuteImageCompression (){ 6 | output=$(./node_modules/.bin/tinypng $file -k "${TINY_PNG_API_KEY}") 7 | } 8 | 9 | IterateStagedImages (){ 10 | for file in $(git diff --diff-filter=d --staged --name-only | grep ".png\|.jpg\|.jpeg") 11 | do 12 | echo -e "\xf0\x9f\x96\xbc Compressing $file" 13 | ExecuteImageCompression 14 | git add $file; 15 | done 16 | } 17 | 18 | IterateStagedImages -------------------------------------------------------------------------------- /scripts/deleteLocalizations.js: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | 6 | function deletePropertyPath(obj, propPath) { 7 | if (!propPath) { 8 | return; 9 | } 10 | 11 | const props = propPath.split('.'); 12 | for (let i = 0; i < props.length - 1; i++) { 13 | obj = obj[props[i]]; 14 | if (typeof obj === 'undefined') { 15 | return; 16 | } 17 | } 18 | delete obj[props.pop()]; 19 | } 20 | 21 | function deleteFromJsonFiles(propPath, jsonFilesDirectory) { 22 | if ( 23 | !fs.existsSync(jsonFilesDirectory) || 24 | !fs.statSync(jsonFilesDirectory).isDirectory() 25 | ) { 26 | console.error(`Error: Directory does not exist at ${jsonFilesDirectory}`); 27 | return; 28 | } 29 | 30 | fs.readdirSync(jsonFilesDirectory).forEach(file => { 31 | if (file.endsWith('.json')) { 32 | const jsonFilePath = path.join(jsonFilesDirectory, file); 33 | let jsonData = JSON.parse(fs.readFileSync(jsonFilePath, 'utf8') || '{}'); 34 | deletePropertyPath(jsonData, propPath); 35 | 36 | fs.writeFileSync(jsonFilePath, JSON.stringify(jsonData, null, 2) + '\n'); 37 | console.log(`Updated ${file}`); 38 | } 39 | }); 40 | 41 | console.log('JSON files updated successfully.'); 42 | } 43 | 44 | // Example usage: node deleteLocalizations.js 'bsc_address.walletAction' 45 | const args = process.argv.slice(2); 46 | 47 | if (args.length !== 2) { 48 | console.log( 49 | 'Usage: node deleteLocalizations.js ', 50 | ); 51 | } else { 52 | const propPath = args[0]; 53 | const jsonFilesDirectory = args[1]; 54 | deleteFromJsonFiles(propPath, jsonFilesDirectory); 55 | } 56 | -------------------------------------------------------------------------------- /scripts/generateTranslationTypes/index.mjs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import fs from 'fs'; 4 | import {buildFlatTranslationsType} from './utils/buildFlatTranslationsType.mjs'; 5 | import {PARAMS_TYPE, COUNT_TYPE} from './utils/buildParamsType.mjs'; 6 | 7 | const translationsPath = process.argv[2]; 8 | 9 | if (!translationsPath) { 10 | throw new Error('Path to translations.json is not specified'); 11 | } 12 | 13 | const translations = JSON.parse(fs.readFileSync(translationsPath)); 14 | 15 | const flatTranslationsType = buildFlatTranslationsType(translations); 16 | 17 | fs.writeFileSync( 18 | `${translationsPath}.d.ts`, 19 | `export type Translations = ${JSON.stringify(flatTranslationsType) 20 | .replaceAll(`"${PARAMS_TYPE}"`, PARAMS_TYPE) 21 | .replaceAll(`"${COUNT_TYPE}"`, COUNT_TYPE)}`, 22 | ); 23 | -------------------------------------------------------------------------------- /scripts/generateTranslationTypes/utils/buildFlatTranslationsType.mjs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {buildParamsType, COUNT_TYPE} from './buildParamsType.mjs'; 4 | import {getParamsFromString} from './getParamsFromString.mjs'; 5 | import {isPluralizationNode} from './isPluralizationNode.mjs'; 6 | 7 | export const buildFlatTranslationsType = (node, parentPath = '') => { 8 | return Object.entries(node).reduce((results, [nodeKey, nodeContent]) => { 9 | const currentPath = parentPath ? `${parentPath}.${nodeKey}` : nodeKey; 10 | 11 | if (typeof nodeContent === 'string') { 12 | results[currentPath] = buildParamsType(getParamsFromString(nodeContent)); 13 | } else if (isPluralizationNode(nodeContent)) { 14 | results[currentPath] = 15 | buildParamsType( 16 | getParamsFromString(Object.values(nodeContent).join('')), 17 | ) ?? {}; 18 | results[currentPath].count = COUNT_TYPE; 19 | } else { 20 | results = { 21 | ...results, 22 | ...buildFlatTranslationsType(nodeContent, currentPath), 23 | }; 24 | } 25 | 26 | return results; 27 | }, {}); 28 | }; 29 | -------------------------------------------------------------------------------- /scripts/generateTranslationTypes/utils/buildParamsType.mjs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | export const PARAMS_TYPE = 'number | string'; 4 | export const COUNT_TYPE = 'number'; 5 | 6 | export const buildParamsType = params => { 7 | return params.reduce( 8 | (result, param) => ({...(result ?? {}), [param]: PARAMS_TYPE}), 9 | null, 10 | ); 11 | }; 12 | -------------------------------------------------------------------------------- /scripts/generateTranslationTypes/utils/getParamsFromString.mjs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | export const getParamsFromString = input => { 4 | const params = [...input.matchAll(/{{(\w+)}}/g)].map(match => match[1]); 5 | return [...new Set(params)]; 6 | }; 7 | -------------------------------------------------------------------------------- /scripts/generateTranslationTypes/utils/isPluralizationNode.mjs: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | const PLURALIZATION_KEYS = ['zero', 'one', 'two', 'few', 'many', 'other']; 4 | 5 | export const isPluralizationNode = node => { 6 | if (typeof node !== 'object') { 7 | return false; 8 | } 9 | return Object.keys(node).every( 10 | key => PLURALIZATION_KEYS.includes(key) && typeof node[key] === 'string', 11 | ); 12 | }; 13 | -------------------------------------------------------------------------------- /scripts/license/addlicense.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | for x in $*; 3 | do head -n 1 $x | diff LICENSE.header - || ( ( cat LICENSE.header; echo; cat $x) > /tmp/file; mv /tmp/file $x; echo $x >> files.cnt; ) 4 | done 5 | -------------------------------------------------------------------------------- /scripts/license/checklicense.sh: -------------------------------------------------------------------------------- 1 | touch files.cnt 2 | (find . -type f \( -name "*.h" -or -name "*.cpp" -or -name "*.mm" -or -name "*.m" -or -name "*.swift" \) -path "./ios/ice/*" && \ 3 | find . -type f \( -name "*.h" -or -name "*.cpp" -or -name "*.java" -or -name "*.kt" -or -name "*.kts" -or -name "*.ktm" -or -name "*.gradle" \) -path "./android/app*" | grep -v './android/app/src/debug/java/io/ice/app/ReactNativeFlipper.java' && \ 4 | find . -type f \( -name "*.js" -or -name "*.ts" -or -name "*.tsx" \) -path "./src/*" | grep -v -E '.*\.json\.d\.ts' && \ 5 | find . -type f \( -name "*.js" -or -name "*.mjs" -or -name "*.tsx" \) -path "./scripts/*" 6 | find . -type f \( -name "*.js" -or -name "*.ts" -or -name "*.tsx" \) -path "./test/*" ) | xargs -0 $(dirname -- "$0")/addlicense.sh 7 | CNT_VALUE="$(wc -l < files.cnt | tr -d ' \t\n\r' )" 8 | if [ $((CNT_VALUE)) -gt 0 ] 9 | then 10 | echo "There were $CNT_VALUE files without license" 11 | rm -rf files.cnt 12 | exit 1 13 | fi 14 | rm -rf files.cnt 15 | -------------------------------------------------------------------------------- /scripts/run-e2e-android-release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | source .env 3 | yarn start & 4 | 5 | METRO_BUNDLER_PID=$! 6 | 7 | yarn e2e-test-android-release 8 | 9 | DETOX_EXIT_CODE=$? 10 | 11 | kill $METRO_BUNDLER_PID 12 | 13 | exit $DETOX_EXIT_CODE 14 | -------------------------------------------------------------------------------- /scripts/run-e2e-ios-release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | source .env 3 | yarn start & 4 | 5 | METRO_BUNDLER_PID=$! 6 | 7 | yarn e2e-test-ios-release 8 | 9 | DETOX_EXIT_CODE=$? 10 | 11 | kill $METRO_BUNDLER_PID 12 | 13 | exit $DETOX_EXIT_CODE 14 | -------------------------------------------------------------------------------- /scripts/syncLocalizations.js: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | 6 | function deepMerge(target, source) { 7 | for (const key in source) { 8 | if (source.hasOwnProperty(key)) { 9 | if ( 10 | target[key] && 11 | typeof target[key] === 'object' && 12 | typeof source[key] === 'object' 13 | ) { 14 | deepMerge(target[key], source[key]); 15 | } else { 16 | target[key] = source[key]; 17 | } 18 | } 19 | } 20 | } 21 | 22 | function ensureValidJsonFile(filePath) { 23 | if (!fs.existsSync(filePath)) { 24 | fs.writeFileSync(filePath, '{}'); 25 | } 26 | } 27 | 28 | function checkAndAddNewline(filePath) { 29 | const fileContents = fs.readFileSync(filePath, 'utf8'); 30 | if (!fileContents.endsWith('\n')) { 31 | fs.appendFileSync(filePath, '\n'); 32 | } 33 | } 34 | 35 | function updateJsonFiles(newJsonPath, jsonFilesDirectory) { 36 | if (!fs.existsSync(newJsonPath)) { 37 | console.error(`Error: new.json does not exist at ${newJsonPath}`); 38 | return; 39 | } 40 | 41 | if (!fs.statSync(jsonFilesDirectory).isDirectory()) { 42 | console.error(`Error: Directory does not exist at ${jsonFilesDirectory}`); 43 | return; 44 | } 45 | 46 | const newJson = JSON.parse(fs.readFileSync(newJsonPath, 'utf8')); 47 | 48 | fs.readdirSync(jsonFilesDirectory).forEach(file => { 49 | if (file.endsWith('.json')) { 50 | const jsonFilePath = path.join(jsonFilesDirectory, file); 51 | const languageCode = path.basename(file, '.json'); 52 | ensureValidJsonFile(jsonFilePath); 53 | const value = 54 | newJson[languageCode] || newJson[`${newJson[languageCode]}.json`]; 55 | if (value) { 56 | let existingJson = JSON.parse( 57 | fs.readFileSync(jsonFilePath, 'utf8') || '{}', 58 | ); 59 | deepMerge(existingJson, value); 60 | 61 | fs.writeFileSync( 62 | jsonFilePath, 63 | JSON.stringify(existingJson, null, 2) + '\n', 64 | ); 65 | checkAndAddNewline(jsonFilePath); 66 | } 67 | } 68 | }); 69 | 70 | console.log('JSON files updated successfully.'); 71 | } 72 | 73 | // Example usage: node syncLocalizations.js 74 | const args = process.argv.slice(2); 75 | 76 | if (args.length !== 2) { 77 | console.log( 78 | 'Usage: node syncLocalizations.js ', 79 | ); 80 | } else { 81 | const newJsonPath = args[0]; 82 | const jsonFilesDirectory = args[1]; 83 | updateJsonFiles(newJsonPath, jsonFilesDirectory); 84 | } 85 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {commonStyles} from '@constants/styles'; 4 | import {AnimatedSplash} from '@navigation/components/AnimatedSplash'; 5 | import {Router} from '@navigation/Router'; 6 | import {persistor, store} from '@store/configureStore'; 7 | import {disableFontScaling} from '@utils/ui'; 8 | import React from 'react'; 9 | import {StatusBar} from 'react-native'; 10 | import {GestureHandlerRootView} from 'react-native-gesture-handler'; 11 | import {SafeAreaProvider} from 'react-native-safe-area-context'; 12 | import {Provider} from 'react-redux'; 13 | import {PersistGate} from 'redux-persist/integration/react'; 14 | 15 | disableFontScaling(); 16 | 17 | export function App() { 18 | return ( 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | ); 31 | } 32 | -------------------------------------------------------------------------------- /src/assets/fonts/Lato-Black.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/fonts/Lato-Black.ttf -------------------------------------------------------------------------------- /src/assets/fonts/Lato-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/fonts/Lato-Bold.ttf -------------------------------------------------------------------------------- /src/assets/fonts/Lato-Hairline.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/fonts/Lato-Hairline.ttf -------------------------------------------------------------------------------- /src/assets/fonts/Lato-Heavy.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/fonts/Lato-Heavy.ttf -------------------------------------------------------------------------------- /src/assets/fonts/Lato-Light.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/fonts/Lato-Light.ttf -------------------------------------------------------------------------------- /src/assets/fonts/Lato-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/fonts/Lato-Medium.ttf -------------------------------------------------------------------------------- /src/assets/fonts/Lato-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/fonts/Lato-Regular.ttf -------------------------------------------------------------------------------- /src/assets/fonts/Lato-Semibold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/fonts/Lato-Semibold.ttf -------------------------------------------------------------------------------- /src/assets/fonts/Lato-Thin.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/fonts/Lato-Thin.ttf -------------------------------------------------------------------------------- /src/assets/images/backgrounds/linesBg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/images/backgrounds/linesBg.png -------------------------------------------------------------------------------- /src/assets/images/backgrounds/linesBg@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/images/backgrounds/linesBg@2x.png -------------------------------------------------------------------------------- /src/assets/images/backgrounds/linesBg@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/images/backgrounds/linesBg@3x.png -------------------------------------------------------------------------------- /src/assets/images/backgrounds/roundedStroke.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/images/backgrounds/roundedStroke.png -------------------------------------------------------------------------------- /src/assets/images/backgrounds/roundedStroke@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/images/backgrounds/roundedStroke@2x.png -------------------------------------------------------------------------------- /src/assets/images/backgrounds/roundedStroke@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/images/backgrounds/roundedStroke@3x.png -------------------------------------------------------------------------------- /src/assets/images/card/joinMainnet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/images/card/joinMainnet.png -------------------------------------------------------------------------------- /src/assets/images/card/joinMainnet@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/images/card/joinMainnet@2x.png -------------------------------------------------------------------------------- /src/assets/images/card/joinMainnet@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/images/card/joinMainnet@3x.png -------------------------------------------------------------------------------- /src/assets/images/index.ts: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | export const Images = { 4 | backgrounds: { 5 | roundedStroke: require('./backgrounds/roundedStroke.png'), 6 | linesBg: require('./backgrounds/linesBg.png'), 7 | }, 8 | platforms: { 9 | okx: require('./platforms/okx.png'), 10 | kucoin: require('./platforms/kucoin.png'), 11 | gate: require('./platforms/gate.png'), 12 | mexc: require('./platforms/mexc.png'), 13 | bitget: require('./platforms/bitget.png'), 14 | bitmart: require('./platforms/bitmart.png'), 15 | bingx: require('./platforms/bingx.png'), 16 | bitrue: require('./platforms/bitrue.png'), 17 | pancake: require('./platforms/pancake.png'), 18 | uniswapArbitrum: require('./platforms/uniswap_arbitrum.png'), 19 | uniswapEthereum: require('./platforms/uniswap_ethereum.png'), 20 | poloneix: require('./platforms/poloneix.png'), 21 | tokero: require('./platforms/tokero.png'), 22 | raydium: require('./platforms/raydium.png'), 23 | jupiter: require('./platforms/jupiter.png'), 24 | htx: require('./platforms/htx.png'), 25 | bitconomy: require('./platforms/bitconomy.png'), 26 | onus: require('./platforms/onus.png'), 27 | bifinance: require('./platforms/bifinance.png'), 28 | xtcom: require('./platforms/xtcom.png'), 29 | }, 30 | card: { 31 | joinMainnet: require('./card/joinMainnet.png'), 32 | }, 33 | popUp: { 34 | error: require('./popup/oops.png'), 35 | }, 36 | } as const; 37 | -------------------------------------------------------------------------------- /src/assets/images/platforms/bifinance.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/images/platforms/bifinance.png -------------------------------------------------------------------------------- /src/assets/images/platforms/bifinance@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/images/platforms/bifinance@2x.png -------------------------------------------------------------------------------- /src/assets/images/platforms/bifinance@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/images/platforms/bifinance@3x.png -------------------------------------------------------------------------------- /src/assets/images/platforms/bingx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/images/platforms/bingx.png -------------------------------------------------------------------------------- /src/assets/images/platforms/bingx@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/images/platforms/bingx@2x.png -------------------------------------------------------------------------------- /src/assets/images/platforms/bingx@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/images/platforms/bingx@3x.png -------------------------------------------------------------------------------- /src/assets/images/platforms/bitconomy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/images/platforms/bitconomy.png -------------------------------------------------------------------------------- /src/assets/images/platforms/bitconomy@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/images/platforms/bitconomy@2x.png -------------------------------------------------------------------------------- /src/assets/images/platforms/bitconomy@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/images/platforms/bitconomy@3x.png -------------------------------------------------------------------------------- /src/assets/images/platforms/bitget.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/images/platforms/bitget.png -------------------------------------------------------------------------------- /src/assets/images/platforms/bitget@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/images/platforms/bitget@2x.png -------------------------------------------------------------------------------- /src/assets/images/platforms/bitget@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/images/platforms/bitget@3x.png -------------------------------------------------------------------------------- /src/assets/images/platforms/bitmart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/images/platforms/bitmart.png -------------------------------------------------------------------------------- /src/assets/images/platforms/bitmart@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/images/platforms/bitmart@2x.png -------------------------------------------------------------------------------- /src/assets/images/platforms/bitmart@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/images/platforms/bitmart@3x.png -------------------------------------------------------------------------------- /src/assets/images/platforms/bitrue.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/images/platforms/bitrue.png -------------------------------------------------------------------------------- /src/assets/images/platforms/bitrue@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/images/platforms/bitrue@2x.png -------------------------------------------------------------------------------- /src/assets/images/platforms/bitrue@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/images/platforms/bitrue@3x.png -------------------------------------------------------------------------------- /src/assets/images/platforms/gate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/images/platforms/gate.png -------------------------------------------------------------------------------- /src/assets/images/platforms/gate@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/images/platforms/gate@2x.png -------------------------------------------------------------------------------- /src/assets/images/platforms/gate@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/images/platforms/gate@3x.png -------------------------------------------------------------------------------- /src/assets/images/platforms/htx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/images/platforms/htx.png -------------------------------------------------------------------------------- /src/assets/images/platforms/htx@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/images/platforms/htx@2x.png -------------------------------------------------------------------------------- /src/assets/images/platforms/htx@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/images/platforms/htx@3x.png -------------------------------------------------------------------------------- /src/assets/images/platforms/jupiter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/images/platforms/jupiter.png -------------------------------------------------------------------------------- /src/assets/images/platforms/jupiter@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/images/platforms/jupiter@2x.png -------------------------------------------------------------------------------- /src/assets/images/platforms/jupiter@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/images/platforms/jupiter@3x.png -------------------------------------------------------------------------------- /src/assets/images/platforms/kucoin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/images/platforms/kucoin.png -------------------------------------------------------------------------------- /src/assets/images/platforms/kucoin@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/images/platforms/kucoin@2x.png -------------------------------------------------------------------------------- /src/assets/images/platforms/kucoin@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/images/platforms/kucoin@3x.png -------------------------------------------------------------------------------- /src/assets/images/platforms/mexc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/images/platforms/mexc.png -------------------------------------------------------------------------------- /src/assets/images/platforms/mexc@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/images/platforms/mexc@2x.png -------------------------------------------------------------------------------- /src/assets/images/platforms/mexc@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/images/platforms/mexc@3x.png -------------------------------------------------------------------------------- /src/assets/images/platforms/okx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/images/platforms/okx.png -------------------------------------------------------------------------------- /src/assets/images/platforms/okx@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/images/platforms/okx@2x.png -------------------------------------------------------------------------------- /src/assets/images/platforms/okx@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/images/platforms/okx@3x.png -------------------------------------------------------------------------------- /src/assets/images/platforms/onus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/images/platforms/onus.png -------------------------------------------------------------------------------- /src/assets/images/platforms/onus@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/images/platforms/onus@2x.png -------------------------------------------------------------------------------- /src/assets/images/platforms/onus@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/images/platforms/onus@3x.png -------------------------------------------------------------------------------- /src/assets/images/platforms/pancake.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/images/platforms/pancake.png -------------------------------------------------------------------------------- /src/assets/images/platforms/pancake@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/images/platforms/pancake@2x.png -------------------------------------------------------------------------------- /src/assets/images/platforms/pancake@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/images/platforms/pancake@3x.png -------------------------------------------------------------------------------- /src/assets/images/platforms/poloneix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/images/platforms/poloneix.png -------------------------------------------------------------------------------- /src/assets/images/platforms/poloneix@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/images/platforms/poloneix@2x.png -------------------------------------------------------------------------------- /src/assets/images/platforms/poloneix@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/images/platforms/poloneix@3x.png -------------------------------------------------------------------------------- /src/assets/images/platforms/raydium.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/images/platforms/raydium.png -------------------------------------------------------------------------------- /src/assets/images/platforms/raydium@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/images/platforms/raydium@2x.png -------------------------------------------------------------------------------- /src/assets/images/platforms/raydium@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/images/platforms/raydium@3x.png -------------------------------------------------------------------------------- /src/assets/images/platforms/tokero.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/images/platforms/tokero.png -------------------------------------------------------------------------------- /src/assets/images/platforms/tokero@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/images/platforms/tokero@2x.png -------------------------------------------------------------------------------- /src/assets/images/platforms/tokero@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/images/platforms/tokero@3x.png -------------------------------------------------------------------------------- /src/assets/images/platforms/uniswap_arbitrum.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/images/platforms/uniswap_arbitrum.png -------------------------------------------------------------------------------- /src/assets/images/platforms/uniswap_arbitrum@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/images/platforms/uniswap_arbitrum@2x.png -------------------------------------------------------------------------------- /src/assets/images/platforms/uniswap_arbitrum@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/images/platforms/uniswap_arbitrum@3x.png -------------------------------------------------------------------------------- /src/assets/images/platforms/uniswap_ethereum.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/images/platforms/uniswap_ethereum.png -------------------------------------------------------------------------------- /src/assets/images/platforms/uniswap_ethereum@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/images/platforms/uniswap_ethereum@2x.png -------------------------------------------------------------------------------- /src/assets/images/platforms/uniswap_ethereum@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/images/platforms/uniswap_ethereum@3x.png -------------------------------------------------------------------------------- /src/assets/images/platforms/xtcom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/images/platforms/xtcom.png -------------------------------------------------------------------------------- /src/assets/images/platforms/xtcom@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/images/platforms/xtcom@2x.png -------------------------------------------------------------------------------- /src/assets/images/platforms/xtcom@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/images/platforms/xtcom@3x.png -------------------------------------------------------------------------------- /src/assets/images/popup/oops.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/images/popup/oops.png -------------------------------------------------------------------------------- /src/assets/images/popup/oops@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/images/popup/oops@2x.png -------------------------------------------------------------------------------- /src/assets/images/popup/oops@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ice-blockchain/mobile-app/da95250cb269583e4e843479f6601a01d79b2afd/src/assets/images/popup/oops@3x.png -------------------------------------------------------------------------------- /src/assets/lottie/index.ts: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | export const LottieAnimations = { 4 | loader: require('./loader.json'), 5 | whiteLoader: require('./white_loader.json'), 6 | splashLogo: require('./splash-logo.json'), 7 | }; 8 | -------------------------------------------------------------------------------- /src/assets/svg/Align.tsx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {COLORS} from '@constants/colors'; 4 | import * as React from 'react'; 5 | import Svg, {Path, SvgProps} from 'react-native-svg'; 6 | import {rem} from 'rn-units'; 7 | 8 | export const AlignIcon = ({ 9 | color = COLORS.primaryLight, 10 | ...props 11 | }: SvgProps) => ( 12 | 13 | 20 | 25 | 26 | ); 27 | -------------------------------------------------------------------------------- /src/assets/svg/ChevronSmallIcon.tsx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {COLORS} from '@constants/colors'; 4 | import * as React from 'react'; 5 | import {Path, Svg, SvgProps} from 'react-native-svg'; 6 | import {rem} from 'rn-units'; 7 | 8 | export const ChevronSmallIcon = (props: SvgProps) => { 9 | return ( 10 | 16 | 23 | 24 | ); 25 | }; 26 | -------------------------------------------------------------------------------- /src/assets/svg/Diamond.tsx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {COLORS} from '@constants/colors'; 4 | import * as React from 'react'; 5 | import Svg, {Path, SvgProps} from 'react-native-svg'; 6 | import {rem} from 'rn-units'; 7 | 8 | export const DiamondIcon = ({ 9 | color = COLORS.primaryLight, 10 | ...props 11 | }: SvgProps) => ( 12 | 13 | 20 | 21 | ); 22 | -------------------------------------------------------------------------------- /src/assets/svg/Graph.tsx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {COLORS} from '@constants/colors'; 4 | import * as React from 'react'; 5 | import Svg, {Path, SvgProps} from 'react-native-svg'; 6 | import {rem} from 'rn-units'; 7 | 8 | export const GraphIcon = ({ 9 | color = COLORS.primaryLight, 10 | ...props 11 | }: SvgProps) => ( 12 | 13 | 18 | 25 | 26 | ); 27 | -------------------------------------------------------------------------------- /src/assets/svg/LogoIcon.tsx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {COLORS} from '@constants/colors'; 4 | import * as React from 'react'; 5 | import {Path, Svg, SvgProps} from 'react-native-svg'; 6 | 7 | export const LogoIcon = ({ 8 | color = COLORS.primaryLight, 9 | width = 18, 10 | height = 18, 11 | ...props 12 | }: SvgProps) => { 13 | return ( 14 | 15 | 19 | 20 | ); 21 | }; 22 | -------------------------------------------------------------------------------- /src/assets/svg/PaperIcon.tsx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {COLORS} from '@constants/colors'; 4 | import * as React from 'react'; 5 | import Svg, {Path, SvgProps} from 'react-native-svg'; 6 | import {rem} from 'rn-units'; 7 | 8 | export const PaperIcon = ({color = COLORS.white, ...props}: SvgProps) => ( 9 | 15 | 22 | 23 | ); 24 | -------------------------------------------------------------------------------- /src/assets/svg/Pie.tsx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {COLORS} from '@constants/colors'; 4 | import * as React from 'react'; 5 | import Svg, {Path, SvgProps} from 'react-native-svg'; 6 | import {rem} from 'rn-units'; 7 | 8 | export const PieIcon = ({color = COLORS.primaryLight, ...props}: SvgProps) => ( 9 | 10 | 16 | 21 | 22 | ); 23 | -------------------------------------------------------------------------------- /src/assets/svg/Refresh.tsx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {COLORS} from '@constants/colors'; 4 | import * as React from 'react'; 5 | import Svg, {Path, SvgProps} from 'react-native-svg'; 6 | import {rem} from 'rn-units'; 7 | 8 | export const RefreshIcon = ({ 9 | color = COLORS.primaryLight, 10 | ...props 11 | }: SvgProps) => ( 12 | 13 | 20 | 21 | ); 22 | -------------------------------------------------------------------------------- /src/assets/svg/Rocket.tsx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {COLORS} from '@constants/colors'; 4 | import * as React from 'react'; 5 | import Svg, {Path, SvgProps} from 'react-native-svg'; 6 | import {rem} from 'rn-units'; 7 | 8 | export const RocketIcon = ({color = COLORS.white, ...props}: SvgProps) => ( 9 | 10 | 14 | 20 | 21 | ); 22 | -------------------------------------------------------------------------------- /src/assets/svg/RoundedTriangle.tsx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import * as React from 'react'; 4 | import Svg, {Path, SvgProps} from 'react-native-svg'; 5 | 6 | export const RoundedTriangle = (props: SvgProps) => ( 7 | 8 | 9 | 10 | ); 11 | -------------------------------------------------------------------------------- /src/assets/svg/Structure.tsx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {COLORS} from '@constants/colors'; 4 | import * as React from 'react'; 5 | import Svg, {Path, SvgProps} from 'react-native-svg'; 6 | import {rem} from 'rn-units'; 7 | 8 | export const StructureIcon = ({ 9 | color = COLORS.primaryLight, 10 | ...props 11 | }: SvgProps) => ( 12 | 13 | 19 | 20 | ); 21 | -------------------------------------------------------------------------------- /src/components/ActivityIndicator/index.tsx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {LottieView} from '@components/LottieView'; 4 | import {LottieAnimations} from '@lottie'; 5 | import React from 'react'; 6 | import {StyleProp, StyleSheet, ViewStyle} from 'react-native'; 7 | import {rem} from 'rn-units'; 8 | 9 | export type ActivityIndicatorTheme = 'light-content' | 'dark-content'; 10 | 11 | interface Props { 12 | style?: StyleProp; 13 | theme?: ActivityIndicatorTheme; 14 | } 15 | 16 | export const ActivityIndicator = ({style, theme}: Props) => ( 17 | 27 | ); 28 | 29 | const styles = StyleSheet.create({ 30 | animation: { 31 | width: rem(32), 32 | height: rem(32), 33 | }, 34 | }); 35 | -------------------------------------------------------------------------------- /src/components/LinesBackground/index.tsx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {windowWidth} from '@constants/styles'; 4 | import {Images} from '@images'; 5 | import React from 'react'; 6 | import {Image, ImageStyle, StyleProp, StyleSheet} from 'react-native'; 7 | 8 | type Props = { 9 | style?: StyleProp; 10 | }; 11 | 12 | const RATIO = 1.12; 13 | 14 | export const LinesBackground = ({style}: Props) => { 15 | return ( 16 | 21 | ); 22 | }; 23 | 24 | const styles = StyleSheet.create({ 25 | background: { 26 | position: 'absolute', 27 | width: windowWidth, 28 | height: windowWidth * RATIO, 29 | }, 30 | }); 31 | -------------------------------------------------------------------------------- /src/components/LottieView/hooks/useHandleLottieBackground.ts: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {LottieViewMethods} from '@components/LottieView'; 4 | import {RefObject, useEffect} from 'react'; 5 | import {AppState} from 'react-native'; 6 | 7 | export const useHandleLottieBackground = ( 8 | lottieRef: RefObject, 9 | ) => { 10 | /** 11 | * Lottie stops playing if the app goes background so we resume it manually 12 | */ 13 | useEffect(() => { 14 | const listener = AppState.addEventListener('change', nextAppState => { 15 | if (nextAppState === 'active') { 16 | lottieRef.current?.play(); 17 | } 18 | }); 19 | return listener.remove; 20 | }, [lottieRef]); 21 | }; 22 | -------------------------------------------------------------------------------- /src/components/LottieView/index.tsx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | /* eslint-disable no-restricted-imports */ 4 | import {useHandleLottieBackground} from '@components/LottieView/hooks/useHandleLottieBackground'; 5 | import AnimatedLottieView, { 6 | LottieViewProps as ImportedLottieViewProps, 7 | } from 'lottie-react-native'; 8 | import React, {useRef} from 'react'; 9 | 10 | export type LottieViewProps = ImportedLottieViewProps; 11 | 12 | export type LottieViewMethods = AnimatedLottieView; 13 | 14 | export const LottieView = (props: LottieViewProps) => { 15 | const ref = useRef(null); 16 | 17 | useHandleLottieBackground(ref); 18 | 19 | return ; 20 | }; 21 | -------------------------------------------------------------------------------- /src/components/SectionHeader/index.tsx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {Touchable} from '@components/Touchable'; 4 | import {MIDDLE_BUTTON_HIT_SLOP, SCREEN_SIDE_OFFSET} from '@constants/styles'; 5 | import {font} from '@utils/styles'; 6 | import React, {memo, ReactNode} from 'react'; 7 | import {StyleProp, StyleSheet, Text, View, ViewStyle} from 'react-native'; 8 | import {rem} from 'rn-units'; 9 | 10 | type Props = { 11 | title: string; 12 | action?: string | ReactNode; 13 | onActionPress?: () => void; 14 | style?: StyleProp; 15 | }; 16 | 17 | export const SECTION_HEADER_HEIGHT = rem(42); 18 | 19 | export const SectionHeader = memo( 20 | ({title, action, onActionPress, style}: Props) => { 21 | return ( 22 | 23 | {title.toUpperCase()} 24 | {typeof action === 'string' ? ( 25 | 26 | {action} 27 | 28 | ) : ( 29 | action 30 | )} 31 | 32 | ); 33 | }, 34 | ); 35 | 36 | const styles = StyleSheet.create({ 37 | container: { 38 | flexDirection: 'row', 39 | marginHorizontal: SCREEN_SIDE_OFFSET, 40 | justifyContent: 'space-between', 41 | paddingTop: rem(24), 42 | height: SECTION_HEADER_HEIGHT, 43 | }, 44 | titleText: { 45 | ...font(15, 20, 'black', 'primaryDark'), 46 | }, 47 | actionText: { 48 | ...font(12.5, 15, 'medium', 'primaryDark'), 49 | }, 50 | }); 51 | -------------------------------------------------------------------------------- /src/components/Touchable/index.tsx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import React, {forwardRef, Ref} from 'react'; 4 | import {TouchableOpacity, TouchableOpacityProps} from 'react-native'; 5 | 6 | export type TouchableProps = TouchableOpacityProps; 7 | 8 | export const Touchable = forwardRef( 9 | ( 10 | {onPress = () => {}, children, ...rest}: TouchableOpacityProps, 11 | forwardedRef: Ref, 12 | ) => { 13 | return ( 14 | 19 | {children} 20 | 21 | ); 22 | }, 23 | ); 24 | -------------------------------------------------------------------------------- /src/constants/colors.ts: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | export const COLORS = { 4 | black: '#000000', 5 | white: '#FFFFFF', 6 | white02opacity: 'rgba(255,255,255, .2)', 7 | black01opacity: 'rgba(0,0,0,.1)', 8 | primary: '#073e91', 9 | primaryDark: '#0D265E', 10 | primaryDark09opacity: 'rgba(13,38,94,0.9)', 11 | primaryLight: '#1B47C3', 12 | primaryLight05opacity: 'rgba(27, 71, 195, 0.5)', 13 | primaryLight01opacity: 'rgba(27, 71, 195, 0.1)', 14 | secondary: '#707489', 15 | socialLink: '#3366CC', 16 | paleSkyBlue: '#F3F7FC', 17 | secondaryLight: '#B6B4BA', 18 | secondaryPale: '#A3A5AA', 19 | secondaryFaint: '#E3EBF8', 20 | attention: '#FD4E4E', 21 | attentionDark: '#F53333', 22 | gulfBlue: '#080754', 23 | midnightSapphire: '#061B4B', 24 | madison: '#0D256B', 25 | mariner: '#2D62D9', 26 | periwinkleGray: '#C1CDE1', 27 | heather: '#C1C8D6', 28 | shamrock: '#35D487', 29 | emperor: '#535353', 30 | alabaster: '#F8F8F8', 31 | cornflowerBlue: '#4A86EF', 32 | dodgerBlue: '#256FF8', 33 | downriver: '#0A2155', 34 | gullGray: '#98A4BB', 35 | royalBlue: '#6556EE', 36 | toreaBay: '#11359C', 37 | blueViolet: '#5F5DB1', 38 | gallery: '#EBEBEB', 39 | scorpion: '#575757', 40 | cadetBlue: '#A6B0C2', 41 | deepKoamaru: '#113081', 42 | spindle: '#A9BCE7', 43 | foam: '#F0F9FE', 44 | aliceBlue: '#EFF9FF', 45 | linkWater: '#F1F4FC', 46 | transparentBackground: 'rgba(8, 21, 50, .7)', 47 | wildSand: '#F5F5F5', 48 | primaryButtonGradientStart: '#02337C', 49 | primaryButtonGradientEnd: '#0946A1', 50 | gunmetalGrey: '#0E0E0E', 51 | congressBlue: '#063C8C', 52 | catalinaBlue: '#033580', 53 | transparent: 'rgba(255, 255, 255, 0)', 54 | adoptionGradient: 'rgba(21, 57, 144, 1)', 55 | adoptionGradient07: 'rgba(21, 57, 144, 0.7)', 56 | adoptionGradient001: 'rgba(21, 57, 144, 0.01)', 57 | completed: '#B0DEC8', 58 | koromiko: '#FFBE6C', 59 | neonCarrot: '#FEA43A', 60 | sunshineGold: '#FFC444', 61 | goldenZest: '#F3BA2F', 62 | danube: '#5c7cd4', 63 | }; 64 | -------------------------------------------------------------------------------- /src/constants/env.ts: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | // eslint-disable-next-line no-restricted-imports 4 | import config from 'react-native-config'; 5 | import {isAndroid} from 'rn-units'; 6 | 7 | export const ENV = { 8 | APP_ID: config.APP_ID, 9 | RATE_THE_ADD_TIMEOUT_MINUTES: config.RATE_THE_ADD_TIMEOUT_MINUTES 10 | ? config.RATE_THE_ADD_TIMEOUT_MINUTES.split(',').map(Number) 11 | : null, 12 | QUIZ_NOTIFICATION_TIMEOUT_MINUTES: config.QUIZ_NOTIFICATION_TIMEOUT_MINUTES 13 | ? config.QUIZ_NOTIFICATION_TIMEOUT_MINUTES.split(',').map(Number) 14 | : null, 15 | APPSTORE_APP_ID: config.APPSTORE_APP_ID, 16 | BASE_READ_API_URL: config.BASE_READ_API_URL, 17 | BASE_WRITE_API_URL: config.BASE_WRITE_API_URL, 18 | SENTRY_KEY: config.SENTRY_KEY, 19 | REQUIRE_REFERRAL_REGISTRATION_STEP: 20 | config.REQUIRE_REFERRAL_REGISTRATION_STEP === 'true', 21 | GETSTREAM_API_KEY: config.GETSTREAM_API_KEY, 22 | GETSTREAM_APP_ID: config.GETSTREAM_APP_ID, 23 | GETSTREAM_TOKEN: config.GETSTREAM_TOKEN, 24 | GETSTREAM_NOTIFICATIONS_USER_TOKEN: config.GETSTREAM_NOTIFICATIONS_USER_TOKEN, 25 | GETSTREAM_ANNOUNCEMENTS_USER_TOKEN: config.GETSTREAM_ANNOUNCEMENTS_USER_TOKEN, 26 | DEEPLINK_SCHEME: config.DEEPLINK_SCHEME, 27 | DEEPLINK_DOMAIN: config.DEEPLINK_DOMAIN, 28 | GOOGLE_WEB_CLIENT_ID: isAndroid 29 | ? config.GOOGLE_WEB_CLIENT_ID_ANDROID 30 | : config.GOOGLE_WEB_CLIENT_ID_IOS, 31 | FACEBOOK_APP_ID: config.FACEBOOK_APP_ID, 32 | FACEBOOK_PAGE_ID: config.FACEBOOK_PAGE_ID, 33 | MO_ENGAGE_APP_ID: config.MO_ENGAGE_APP_ID, 34 | APP_AUTO_UPDATE_INTERVAL_SEC: Number(config.APP_AUTO_UPDATE_INTERVAL_SEC), 35 | HOME_REFRESH_MIN_INTERVAL_SEC: Number(config.HOME_REFRESH_MIN_INTERVAL_SEC), 36 | YOUTUBE_CHANNEL_ID: config.YOUTUBE_CHANNEL_ID, 37 | MINING_RATE_INTERVAL_SEC: Number(config.MINING_RATE_INTERVAL_SEC), 38 | STATUS_NOTICE_JSON: config.STATUS_NOTICE_JSON, 39 | AUTH_CONFIG_URL: config.AUTH_CONFIG_URL, 40 | SOCIALS_POPUP_INTERVAL_SEC: Number(config.SOCIALS_POPUP_INTERVAL_SEC), 41 | }; 42 | 43 | /** 44 | * Check if all the ENV variables are successfully picked from the .env.app 45 | */ 46 | Object.entries(ENV).forEach(([key, value]) => { 47 | if (value == null || (typeof value === 'number' && isNaN(value))) { 48 | throw new Error(`Incorrect ENV variable for ${key}`); 49 | } 50 | }); 51 | -------------------------------------------------------------------------------- /src/constants/fonts.ts: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | export type FontWight = keyof typeof FONT_WEIGHTS; 4 | 5 | export const FONT_WEIGHTS = { 6 | hairline: '100', 7 | thin: '200', 8 | light: '300', 9 | regular: '400', 10 | medium: '500', 11 | semibold: '600', 12 | bold: '700', 13 | heavy: '800', 14 | black: '900', 15 | } as const; 16 | 17 | export type FontFamily = 'primary'; 18 | 19 | export const FONTS: {[font in FontFamily]: {[width in FontWight]: string}} = { 20 | primary: { 21 | hairline: 'Lato-Hairline', // 100 22 | thin: 'Lato-Thin', // 200 23 | light: 'Lato-Light', // 300 24 | regular: 'Lato-Regular', // 400 25 | medium: 'Lato-Medium', // 500 26 | semibold: 'Lato-Semibold', // 600 27 | bold: 'Lato-Bold', // 700 28 | heavy: 'Lato-Heavy', // 800 29 | black: 'Lato-Black', // 900 30 | }, 31 | }; 32 | -------------------------------------------------------------------------------- /src/constants/mainnet.ts: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | export const MAINNET_LAUNCH_DATE = '2024-10-07T07:07:07Z'; 4 | -------------------------------------------------------------------------------- /src/constants/styles.ts: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {COLORS} from '@constants/colors'; 4 | import {Dimensions, StyleSheet} from 'react-native'; 5 | import {isIOS, rem, screenHeight} from 'rn-units'; 6 | 7 | export const SCREEN_SIDE_OFFSET = rem(20); 8 | export const POPUP_SIDE_OFFSET = rem(16); 9 | 10 | export const commonStyles = StyleSheet.create({ 11 | baseSubScreen: { 12 | backgroundColor: COLORS.white, 13 | borderTopLeftRadius: rem(30), 14 | borderTopRightRadius: rem(30), 15 | flexGrow: 1, 16 | }, 17 | shadow: isIOS 18 | ? { 19 | shadowColor: COLORS.mariner, 20 | shadowOffset: { 21 | width: 0, 22 | height: 3, 23 | }, 24 | shadowRadius: 6, 25 | shadowOpacity: 0.15, 26 | } 27 | : {elevation: 4}, 28 | flexOne: {flex: 1}, 29 | darkText: {color: COLORS.primaryDark}, 30 | }); 31 | 32 | export const SMALL_BUTTON_HIT_SLOP = {top: 4, left: 4, bottom: 4, right: 4}; 33 | export const MIDDLE_BUTTON_HIT_SLOP = { 34 | top: 12, 35 | left: 32, 36 | bottom: 12, 37 | right: 32, 38 | }; 39 | 40 | export const windowWidth = Dimensions.get('window').width; 41 | export const windowHeight = Dimensions.get('window').height; 42 | 43 | export const smallHeightDevice = screenHeight < 680; 44 | -------------------------------------------------------------------------------- /src/constants/timeouts.ts: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {ENV} from '@constants/env'; 4 | 5 | export const SMS_EMAIL_RESEND_TIMEOUT_SEC = 30; 6 | 7 | export const DEVICE_METADATA_UPDATE_TIMEOUT_HOURS = 8; 8 | 9 | export const MINING_LONG_PRESS_ACTIVATION_SEC = 1; 10 | 11 | export const APP_AUTO_UPDATE_INTERVAL_SEC = ENV.APP_AUTO_UPDATE_INTERVAL_SEC; 12 | 13 | export const HOME_REFRESH_MIN_INTERVAL_SEC = ENV.HOME_REFRESH_MIN_INTERVAL_SEC; 14 | 15 | export const EMAIL_CODE_GET_STATUS_INTERVAL_SEC = 2; 16 | 17 | export const SOCIALS_POPUP_INTERVAL_SEC = ENV.SOCIALS_POPUP_INTERVAL_SEC; 18 | 19 | export const DEVICE_SENSORS_UPDATE_INTERVAL_MS = 500; 20 | 21 | export const DEVICE_ANGLE_WARNING_FEEDBACK_INTERVAL_MS = 300; 22 | -------------------------------------------------------------------------------- /src/hooks/useCountdown.ts: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {dayjs} from '@services/dayjs'; 4 | import {Duration} from 'dayjs/plugin/duration'; 5 | import {useEffect, useMemo, useRef, useState} from 'react'; 6 | 7 | export const useCountdown = (duration: Duration) => { 8 | const initialized = useRef(false); 9 | // Using endTime instead of subtraction from the initial duration 10 | // to handle app background case when setInterval stops 11 | const endTime = useMemo(() => dayjs().add(duration), [duration]); 12 | const [durationLeft, setDurationLeft] = useState(duration.clone()); 13 | const isCountdownOver = durationLeft.asMilliseconds() <= 0; 14 | 15 | useEffect(() => { 16 | if (initialized.current) { 17 | setDurationLeft(duration.clone()); 18 | } else { 19 | initialized.current = true; 20 | } 21 | }, [duration]); 22 | 23 | useEffect(() => { 24 | if (!isCountdownOver) { 25 | const interval = setInterval(() => { 26 | setDurationLeft(dayjs.duration(endTime.diff())); 27 | }, 1000); 28 | return () => clearInterval(interval); 29 | } 30 | }, [endTime, isCountdownOver]); 31 | 32 | const resetTimer = () => { 33 | setDurationLeft(duration.clone()); 34 | }; 35 | 36 | return {durationLeft, isCountdownOver, resetTimer}; 37 | }; 38 | -------------------------------------------------------------------------------- /src/hooks/useSafeAreaInsets.ts: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | // eslint-disable-next-line no-restricted-imports 4 | import {useSafeAreaInsets as useSafeAreaInsetsOriginal} from 'react-native-safe-area-context'; 5 | 6 | export function useSafeAreaInsets() { 7 | return useSafeAreaInsetsOriginal(); 8 | } 9 | -------------------------------------------------------------------------------- /src/navigation/Router.tsx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {InitializationError} from '@navigation/components/InitializationError'; 4 | import {theme} from '@navigation/theme'; 5 | import {NavigationContainer} from '@react-navigation/native'; 6 | import {MainnetLanding} from '@screens/MainnetLanding'; 7 | import {useAppStateListener} from '@store/modules/AppCommon/hooks/useAppStateListener'; 8 | import {appInitStateSelector} from '@store/modules/AppCommon/selectors'; 9 | import {useOpenUrlListener} from '@store/modules/Linking/hooks/useOpenUrlListener'; 10 | import {useSubscribeToPushNotifications} from '@store/modules/PushNotifications/hooks/useSubscribeToPushNotifications'; 11 | import React from 'react'; 12 | import {useSelector} from 'react-redux'; 13 | 14 | function ActiveNavigator() { 15 | const appInitState = useSelector(appInitStateSelector); 16 | 17 | if (appInitState === 'loading') { 18 | return null; 19 | } 20 | 21 | if (appInitState === 'error') { 22 | return ; 23 | } 24 | 25 | return ; 26 | } 27 | 28 | export function Router() { 29 | useOpenUrlListener(); 30 | useAppStateListener(); 31 | useSubscribeToPushNotifications(); 32 | return ( 33 | 34 | 35 | 36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /src/navigation/components/AnimatedSplash/index.tsx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {LottieView} from '@components/LottieView'; 4 | import {COLORS} from '@constants/colors'; 5 | import {windowWidth} from '@constants/styles'; 6 | import {LottieAnimations} from '@lottie'; 7 | import {AppCommonActions} from '@store/modules/AppCommon/actions'; 8 | import { 9 | appInitStateSelector, 10 | isSplashHiddenSelector, 11 | } from '@store/modules/AppCommon/selectors'; 12 | import React, {useCallback, useEffect, useState} from 'react'; 13 | import {StyleSheet, View} from 'react-native'; 14 | import BootSplash from 'react-native-bootsplash'; 15 | import {useDispatch, useSelector} from 'react-redux'; 16 | 17 | export const AnimatedSplash = () => { 18 | const appInitState = useSelector(appInitStateSelector); 19 | const [animationFinished, setAnimationFinished] = useState(false); 20 | const isSplashHidden = useSelector(isSplashHiddenSelector); 21 | const dispatch = useDispatch(); 22 | 23 | useEffect(() => { 24 | if (appInitState !== 'loading' && animationFinished && !isSplashHidden) { 25 | dispatch(AppCommonActions.UPDATE_SPLASH_VISIBLE_STATE.HIDE.create()); 26 | } 27 | }, [animationFinished, appInitState, isSplashHidden, dispatch]); 28 | 29 | const finishAnimation = useCallback((isCancelled: boolean) => { 30 | if (!isCancelled) { 31 | setAnimationFinished(true); 32 | } 33 | }, []); 34 | 35 | if (isSplashHidden) { 36 | return null; 37 | } 38 | 39 | return ( 40 | BootSplash.hide()} 45 | style={[styles.container, StyleSheet.absoluteFill]}> 46 | 53 | 54 | ); 55 | }; 56 | 57 | const styles = StyleSheet.create({ 58 | container: { 59 | backgroundColor: COLORS.primaryLight, 60 | justifyContent: 'center', 61 | alignItems: 'center', 62 | }, 63 | animation: { 64 | width: windowWidth, 65 | height: windowWidth * 2.165, 66 | }, 67 | }); 68 | -------------------------------------------------------------------------------- /src/navigation/components/InitializationError/index.tsx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {PrimaryButton} from '@components/Buttons/PrimaryButton'; 4 | import {COLORS} from '@constants/colors'; 5 | import {Images} from '@images'; 6 | import {AppCommonActions} from '@store/modules/AppCommon/actions'; 7 | import {failedReasonSelector} from '@store/modules/UtilityProcessStatuses/selectors'; 8 | import {t} from '@translations/i18n'; 9 | import {font} from '@utils/styles'; 10 | import React from 'react'; 11 | import {Image, StyleSheet, Text, View} from 'react-native'; 12 | import RNRestart from 'react-native-restart'; 13 | import {useSelector} from 'react-redux'; 14 | import {rem} from 'rn-units'; 15 | 16 | export const InitializationError = () => { 17 | const onTryAgainPress = () => { 18 | RNRestart.restart(); 19 | }; 20 | 21 | const errorMessage = useSelector( 22 | failedReasonSelector.bind(null, AppCommonActions.APP_INITIALIZED), 23 | ); 24 | 25 | return ( 26 | 27 | 32 | {t('errors.general_error_title')} 33 | {errorMessage} 34 | 39 | 40 | ); 41 | }; 42 | 43 | const styles = StyleSheet.create({ 44 | container: { 45 | justifyContent: 'center', 46 | alignItems: 'center', 47 | flex: 1, 48 | backgroundColor: COLORS.white, 49 | paddingHorizontal: rem(40), 50 | }, 51 | image: { 52 | width: rem(250), 53 | height: rem(230), 54 | }, 55 | titleText: { 56 | marginTop: rem(12), 57 | ...font(24, 29, 'black', 'primaryDark', 'center'), 58 | }, 59 | messageText: { 60 | ...font(14, 20, 'medium', 'secondary', 'center'), 61 | marginTop: rem(16), 62 | }, 63 | button: { 64 | height: rem(52), 65 | paddingHorizontal: rem(54), 66 | marginTop: rem(26), 67 | }, 68 | }); 69 | -------------------------------------------------------------------------------- /src/navigation/hooks/useFocusStatusBar.ts: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {useFocusEffect} from '@react-navigation/native'; 4 | import {useCallback} from 'react'; 5 | import {Keyboard, StatusBar, StatusBarStyle} from 'react-native'; 6 | import {isAndroid, isIOS} from 'rn-units'; 7 | 8 | type Props = { 9 | style: StatusBarStyle; 10 | animated?: boolean | undefined; 11 | }; 12 | 13 | export const useFocusStatusBar = ({style, animated}: Props) => { 14 | useFocusEffect( 15 | useCallback(() => { 16 | StatusBar.setBarStyle(style, animated); 17 | 18 | if (isAndroid) { 19 | StatusBar.setBackgroundColor('transparent', animated); 20 | StatusBar.setTranslucent(true); 21 | } 22 | 23 | if (isIOS) { 24 | // the status-bar changes color to default on iOS when the keyboard is shown 25 | const subscription = Keyboard.addListener('keyboardWillShow', () => { 26 | StatusBar.setBarStyle(style, animated); 27 | }); 28 | 29 | return () => { 30 | subscription.remove(); 31 | }; 32 | } 33 | }, [animated, style]), 34 | ); 35 | }; 36 | -------------------------------------------------------------------------------- /src/navigation/hooks/useTopOffsetStyle.ts: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {useSafeAreaInsets} from '@hooks/useSafeAreaInsets'; 4 | import {useMemo} from 'react'; 5 | import {StyleSheet} from 'react-native'; 6 | import {rem} from 'rn-units'; 7 | 8 | type Params = { 9 | extraOffset?: number; 10 | }; 11 | 12 | export const useTopOffsetStyle = ({extraOffset = rem(0)}: Params = {}) => { 13 | const {top: topInset} = useSafeAreaInsets(); 14 | return useMemo( 15 | () => 16 | StyleSheet.create({ 17 | current: {paddingTop: topInset + extraOffset}, 18 | }), 19 | [extraOffset, topInset], 20 | ); 21 | }; 22 | -------------------------------------------------------------------------------- /src/navigation/options.ts: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {BottomTabNavigationOptions} from '@react-navigation/bottom-tabs'; 4 | import {NativeStackNavigationOptions} from '@react-navigation/native-stack'; 5 | 6 | export const tabOptions: BottomTabNavigationOptions = { 7 | headerShown: false, 8 | lazy: true, 9 | tabBarHideOnKeyboard: true, 10 | }; 11 | 12 | export const screenOptions = { 13 | headerShown: false, 14 | gestureEnabled: false, 15 | }; 16 | 17 | export const modalOptions: NativeStackNavigationOptions = { 18 | presentation: 'containedTransparentModal', 19 | animation: 'fade', 20 | } as const; 21 | -------------------------------------------------------------------------------- /src/navigation/theme.ts: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {COLORS} from '@constants/colors'; 4 | import {DefaultTheme} from '@react-navigation/native'; 5 | 6 | export const theme = { 7 | ...DefaultTheme, 8 | colors: { 9 | ...DefaultTheme.colors, 10 | background: COLORS.white, 11 | }, 12 | }; 13 | -------------------------------------------------------------------------------- /src/screens/MainnetLanding/components/IceLogo/index.tsx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {COLORS} from '@constants/colors'; 4 | import {LogoIcon} from '@svg/LogoIcon'; 5 | import {t} from '@translations/i18n'; 6 | import {font} from '@utils/styles'; 7 | import React from 'react'; 8 | import {StyleSheet, Text, View} from 'react-native'; 9 | import {rem} from 'rn-units'; 10 | 11 | export const IceLogo = () => { 12 | return ( 13 | 14 | 15 | {t('general.ice').toLowerCase()} 16 | 17 | ); 18 | }; 19 | 20 | const styles = StyleSheet.create({ 21 | container: { 22 | marginTop: rem(12), 23 | flexDirection: 'row', 24 | alignItems: 'center', 25 | justifyContent: 'center', 26 | }, 27 | labelText: { 28 | ...font(26, undefined, 'bold'), 29 | marginStart: rem(4), 30 | }, 31 | }); 32 | -------------------------------------------------------------------------------- /src/screens/MainnetLanding/components/JoinMainnet/index.tsx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {Touchable} from '@components/Touchable'; 4 | import {COLORS} from '@constants/colors'; 5 | import {LINKS} from '@constants/links'; 6 | import {commonStyles, SCREEN_SIDE_OFFSET} from '@constants/styles'; 7 | import {Images} from '@images'; 8 | import {t} from '@translations/i18n'; 9 | import {openLinkWithInAppBrowser} from '@utils/device'; 10 | import {font} from '@utils/styles'; 11 | import React, {memo} from 'react'; 12 | import {Image, StyleSheet, Text, View} from 'react-native'; 13 | import {rem} from 'rn-units'; 14 | 15 | export const JoinMainnet = memo(() => { 16 | const onPress = () => 17 | openLinkWithInAppBrowser({url: LINKS.JOIN_DECENTRALIZED_FUTURE}); 18 | 19 | return ( 20 | 23 | 24 | 25 | 26 | {t('mainnet_landing.join_mainnet.title')} 27 | 28 | 29 | {t('mainnet_landing.join_mainnet.subtitle')} 30 | 31 | 32 | 33 | ); 34 | }); 35 | 36 | const styles = StyleSheet.create({ 37 | container: { 38 | marginHorizontal: SCREEN_SIDE_OFFSET, 39 | backgroundColor: COLORS.white, 40 | borderRadius: rem(16), 41 | marginTop: rem(24), 42 | }, 43 | image: { 44 | width: '100%', 45 | // height:undefined is required to make it work 46 | height: undefined, 47 | aspectRatio: 335 / 140, 48 | }, 49 | body: { 50 | marginTop: rem(10), 51 | marginHorizontal: rem(12), 52 | marginBottom: rem(12), 53 | }, 54 | titleText: { 55 | ...font(13, 16, 'black', 'primaryDark'), 56 | }, 57 | subtitleText: { 58 | marginTop: rem(4), 59 | ...font(12, 16, 'medium', 'secondary'), 60 | }, 61 | }); 62 | -------------------------------------------------------------------------------- /src/screens/MainnetLanding/components/LaunchCountdown/components/TimerPart.tsx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {font} from '@utils/styles'; 4 | import React from 'react'; 5 | import {StyleSheet, Text, View} from 'react-native'; 6 | import {rem} from 'rn-units'; 7 | 8 | type Props = { 9 | label: string; 10 | value: string | number; 11 | }; 12 | 13 | export const TimerPart = ({label, value}: Props) => { 14 | return ( 15 | 16 | {value} 17 | {label} 18 | 19 | ); 20 | }; 21 | 22 | const styles = StyleSheet.create({ 23 | container: { 24 | alignItems: 'center', 25 | marginHorizontal: rem(10), 26 | }, 27 | labelText: { 28 | ...font(12, undefined, 'regular', 'periwinkleGray'), 29 | }, 30 | valueText: { 31 | ...font(15, undefined, 'bold', 'white'), 32 | }, 33 | }); 34 | -------------------------------------------------------------------------------- /src/screens/MainnetLanding/components/LaunchCountdown/hooks/useContainerOffset.ts: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {useSafeAreaInsets} from '@hooks/useSafeAreaInsets'; 4 | import {useMemo} from 'react'; 5 | import {StyleSheet} from 'react-native'; 6 | import {rem} from 'rn-units'; 7 | 8 | const verticalOffset = rem(10); 9 | 10 | export const useContainerOffset = () => { 11 | const {bottom: bottomInset} = useSafeAreaInsets(); 12 | 13 | return useMemo( 14 | () => 15 | StyleSheet.create({ 16 | current: { 17 | paddingTop: verticalOffset, 18 | paddingBottom: Math.max(bottomInset, verticalOffset), 19 | }, 20 | }), 21 | [bottomInset], 22 | ); 23 | }; 24 | -------------------------------------------------------------------------------- /src/screens/MainnetLanding/components/LaunchCountdown/index.tsx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {COLORS} from '@constants/colors'; 4 | import {MAINNET_LAUNCH_DATE} from '@constants/mainnet'; 5 | import {useCountdown} from '@hooks/useCountdown'; 6 | import {TimerPart} from '@screens/MainnetLanding/components/LaunchCountdown/components/TimerPart'; 7 | import {useContainerOffset} from '@screens/MainnetLanding/components/LaunchCountdown/hooks/useContainerOffset'; 8 | import {dayjs} from '@services/dayjs'; 9 | import {RocketIcon} from '@svg/Rocket'; 10 | import {t} from '@translations/i18n'; 11 | import {font} from '@utils/styles'; 12 | import React, {memo, useMemo} from 'react'; 13 | import {StyleSheet, Text, View} from 'react-native'; 14 | import LinearGradient from 'react-native-linear-gradient'; 15 | import {rem} from 'rn-units'; 16 | 17 | export const LaunchCountdown = memo(() => { 18 | const launchDuration = useMemo(() => { 19 | return dayjs.duration(dayjs(MAINNET_LAUNCH_DATE).diff()); 20 | }, []); 21 | 22 | const {durationLeft, isCountdownOver} = useCountdown(launchDuration); 23 | 24 | const containerOffset = useContainerOffset(); 25 | 26 | const days = isCountdownOver ? 0 : Math.floor(durationLeft.asDays()); 27 | const hours = isCountdownOver ? 0 : durationLeft.hours(); 28 | const minutes = isCountdownOver ? 0 : durationLeft.minutes(); 29 | const seconds = isCountdownOver ? 0 : durationLeft.seconds(); 30 | 31 | return ( 32 | 33 | 37 | 38 | 39 | 40 | {t('mainnet_landing.countdown.header')} 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | ); 51 | }); 52 | 53 | const styles = StyleSheet.create({ 54 | header: { 55 | flexDirection: 'row', 56 | alignItems: 'center', 57 | justifyContent: 'center', 58 | }, 59 | headerText: { 60 | marginStart: rem(8), 61 | ...font(13, undefined, 'bold', 'white', 'center'), 62 | }, 63 | timer: { 64 | marginTop: rem(8), 65 | flexDirection: 'row', 66 | alignItems: 'center', 67 | justifyContent: 'center', 68 | }, 69 | }); 70 | -------------------------------------------------------------------------------- /src/screens/MainnetLanding/components/Notice/index.tsx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {COLORS} from '@constants/colors'; 4 | import {POPUP_SIDE_OFFSET} from '@constants/styles'; 5 | import {RoundedTriangle} from '@svg/RoundedTriangle'; 6 | import {t} from '@translations/i18n'; 7 | import {font} from '@utils/styles'; 8 | import React from 'react'; 9 | import {StyleSheet, Text, View} from 'react-native'; 10 | import {rem} from 'rn-units'; 11 | export const Notice = () => { 12 | return ( 13 | 14 | {t('mainnet_landing.header')} 15 | 21 | 22 | ); 23 | }; 24 | 25 | const styles = StyleSheet.create({ 26 | container: { 27 | marginTop: rem(26), 28 | marginBottom: rem(8), 29 | marginHorizontal: POPUP_SIDE_OFFSET, 30 | paddingHorizontal: rem(16), 31 | paddingTop: rem(10), 32 | paddingBottom: rem(12), 33 | backgroundColor: COLORS.primaryDark, 34 | borderRadius: rem(16), 35 | }, 36 | labelText: { 37 | ...font(15, undefined, 'semibold', 'white', 'center'), 38 | }, 39 | chevron: { 40 | position: 'absolute', 41 | bottom: -rem(8), 42 | alignSelf: 'center', 43 | transform: [{rotate: '180deg'}], 44 | }, 45 | }); 46 | -------------------------------------------------------------------------------- /src/screens/MainnetLanding/components/Roadmap/index.tsx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {ActionListItem} from '@components/ListItems/ActionListItem'; 4 | import {SectionHeader} from '@components/SectionHeader'; 5 | import {COLORS} from '@constants/colors'; 6 | import {LINKS} from '@constants/links'; 7 | import {commonStyles, SCREEN_SIDE_OFFSET} from '@constants/styles'; 8 | import {ChevronSmallIcon} from '@svg/ChevronSmallIcon'; 9 | import {PaperIcon} from '@svg/PaperIcon'; 10 | import {isRTL, t} from '@translations/i18n'; 11 | import {openLinkWithInAppBrowser} from '@utils/device'; 12 | import React, {memo} from 'react'; 13 | import {StyleSheet} from 'react-native'; 14 | import {rem} from 'rn-units'; 15 | 16 | export const Roadmap = memo(() => { 17 | const onPress = () => openLinkWithInAppBrowser({url: LINKS.WHITEPAPER}); 18 | 19 | return ( 20 | <> 21 | 22 | } 25 | title={t('mainnet_landing.roadmap.title')} 26 | subtitle={t('mainnet_landing.roadmap.subtitle')} 27 | TrailingIcon={ 28 | 29 | } 30 | containerStyle={[styles.container, commonStyles.shadow]} 31 | /> 32 | 33 | ); 34 | }); 35 | 36 | const styles = StyleSheet.create({ 37 | container: { 38 | marginHorizontal: SCREEN_SIDE_OFFSET, 39 | marginTop: rem(16), 40 | }, 41 | chevron: { 42 | transform: [{rotateZ: isRTL ? '90deg' : '-90deg'}], 43 | }, 44 | }); 45 | -------------------------------------------------------------------------------- /src/screens/MainnetLanding/components/Stats/components/StatListItem.tsx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {COLORS} from '@constants/colors'; 4 | import {font} from '@utils/styles'; 5 | import React, {memo, ReactNode} from 'react'; 6 | import {StyleSheet, Text, View} from 'react-native'; 7 | import {rem} from 'rn-units'; 8 | 9 | type Props = { 10 | Icon: ReactNode; 11 | label: string; 12 | value: string; 13 | }; 14 | 15 | export const StatListItem = memo(({Icon, label, value}: Props) => { 16 | return ( 17 | 18 | {Icon} 19 | 20 | {label} 21 | {value} 22 | 23 | 24 | ); 25 | }); 26 | 27 | const styles = StyleSheet.create({ 28 | container: { 29 | marginTop: rem(16), 30 | flexDirection: 'row', 31 | alignItems: 'center', 32 | }, 33 | icon: { 34 | backgroundColor: COLORS.aliceBlue, 35 | width: rem(44), 36 | height: rem(44), 37 | borderRadius: rem(12), 38 | justifyContent: 'center', 39 | alignItems: 'center', 40 | }, 41 | body: { 42 | flex: 1, 43 | marginStart: rem(12), 44 | }, 45 | labelText: { 46 | ...font(12, undefined, 'medium', 'secondary'), 47 | }, 48 | valueText: { 49 | ...font(17, undefined, 'bold', 'primaryDark'), 50 | marginTop: rem(4), 51 | }, 52 | }); 53 | -------------------------------------------------------------------------------- /src/screens/MainnetLanding/hooks/useRefresh.ts: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {StatsActions} from '@store/modules/Stats/actions'; 4 | import {isLoadingSelector} from '@store/modules/UtilityProcessStatuses/selectors'; 5 | import {useDispatch, useSelector} from 'react-redux'; 6 | 7 | export const useRefresh = () => { 8 | const dispatch = useDispatch(); 9 | 10 | const onRefresh = () => 11 | dispatch(StatsActions.GET_ICE_COIN_STATS.START.create()); 12 | 13 | const refreshing = useSelector( 14 | isLoadingSelector.bind(null, StatsActions.GET_ICE_COIN_STATS), 15 | ); 16 | 17 | return {onRefresh, refreshing}; 18 | }; 19 | -------------------------------------------------------------------------------- /src/screens/MainnetLanding/hooks/useRequestNotificationPermissions.tsx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {isSplashHiddenSelector} from '@store/modules/AppCommon/selectors'; 4 | import {PermissionsActions} from '@store/modules/Permissions/actions'; 5 | import {canAskPermissionSelector} from '@store/modules/Permissions/selectors'; 6 | import {useEffect} from 'react'; 7 | import {useDispatch, useSelector} from 'react-redux'; 8 | 9 | export function useRequestNotificationPermissions() { 10 | const dispatch = useDispatch(); 11 | const isSplashHidden = useSelector(isSplashHiddenSelector); 12 | const canAskNotificationPermission = useSelector( 13 | canAskPermissionSelector('pushNotifications'), 14 | ); 15 | 16 | useEffect(() => { 17 | if (canAskNotificationPermission && isSplashHidden) { 18 | dispatch( 19 | PermissionsActions.GET_PERMISSIONS.START.create('pushNotifications'), 20 | ); 21 | } 22 | }, [canAskNotificationPermission, dispatch, isSplashHidden]); 23 | } 24 | -------------------------------------------------------------------------------- /src/screens/MainnetLanding/index.tsx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {LinesBackground} from '@components/LinesBackground'; 4 | import {PullToRefreshContainer} from '@components/PullToRefreshContainer'; 5 | import {COLORS} from '@constants/colors'; 6 | import {commonStyles} from '@constants/styles'; 7 | import {useFocusStatusBar} from '@navigation/hooks/useFocusStatusBar'; 8 | import {IceLogo} from '@screens/MainnetLanding/components/IceLogo'; 9 | import {JoinMainnet} from '@screens/MainnetLanding/components/JoinMainnet'; 10 | import {LaunchCountdown} from '@screens/MainnetLanding/components/LaunchCountdown'; 11 | import {Notice} from '@screens/MainnetLanding/components/Notice'; 12 | import {Platforms} from '@screens/MainnetLanding/components/Platforms'; 13 | import {Roadmap} from '@screens/MainnetLanding/components/Roadmap'; 14 | import {Stats} from '@screens/MainnetLanding/components/Stats'; 15 | import {useRefresh} from '@screens/MainnetLanding/hooks/useRefresh'; 16 | import {useRequestNotificationPermissions} from '@screens/MainnetLanding/hooks/useRequestNotificationPermissions'; 17 | import React from 'react'; 18 | import {StyleSheet, View} from 'react-native'; 19 | import Animated from 'react-native-reanimated'; 20 | import {SafeAreaView} from 'react-native-safe-area-context'; 21 | import {rem} from 'rn-units'; 22 | 23 | export const MainnetLanding = () => { 24 | useFocusStatusBar({style: 'light-content'}); 25 | 26 | useRequestNotificationPermissions(); 27 | 28 | const {onRefresh, refreshing} = useRefresh(); 29 | 30 | return ( 31 | 32 | 33 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | ); 54 | }; 55 | 56 | const styles = StyleSheet.create({ 57 | container: { 58 | flex: 1, 59 | backgroundColor: COLORS.primaryLight, 60 | }, 61 | scrollContent: { 62 | flexGrow: 1, 63 | }, 64 | subScreen: { 65 | marginTop: rem(16), 66 | paddingBottom: rem(16), 67 | }, 68 | }); 69 | -------------------------------------------------------------------------------- /src/services/logging/index.ts: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {getErrorMessage} from '@utils/errors'; 4 | import {checkProp} from '@utils/guards'; 5 | import {LogBox} from 'react-native'; 6 | 7 | /** 8 | * We don't use state persistence or deep links to the screen which accepts functions in params, 9 | * so the warning doesn't affect us and we can safely ignore it 10 | * https://reactnavigation.org/docs/troubleshooting/#i-get-the-warning-non-serializable-values-were-found-in-the-navigation-state 11 | */ 12 | LogBox.ignoreLogs([ 13 | 'Non-serializable values were found in the navigation state', 14 | ]); 15 | export function logError(error: unknown, extra: Record = {}) { 16 | if (__DEV__) { 17 | console.error( 18 | 'logError', 19 | getErrorMessage(error), 20 | error, 21 | '\n\nstack:', 22 | checkProp(error, 'stack') && error.stack, 23 | extra, 24 | ); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/store/configureStore.ts: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import AsyncStorage from '@react-native-async-storage/async-storage'; 4 | import {configureStore} from '@reduxjs/toolkit'; 5 | import { 6 | FLUSH, 7 | PAUSE, 8 | PERSIST, 9 | persistReducer, 10 | persistStore, 11 | PURGE, 12 | REGISTER, 13 | REHYDRATE, 14 | } from 'redux-persist'; 15 | import createSagaMiddleware from 'redux-saga'; 16 | 17 | import {rootReducer} from './rootReducer'; 18 | import {rootSaga} from './rootSaga'; 19 | 20 | const persistConfig = { 21 | key: 'root', 22 | storage: AsyncStorage, 23 | whitelist: [], 24 | }; 25 | 26 | const sagaMiddleware = createSagaMiddleware(); 27 | 28 | const middlewares = [sagaMiddleware]; 29 | 30 | if (__DEV__) { 31 | const createDebugger = require('redux-flipper').default; 32 | middlewares.push(createDebugger()); 33 | } 34 | 35 | const persistedReducer = persistReducer(persistConfig, rootReducer); 36 | 37 | export const store = configureStore({ 38 | reducer: persistedReducer, 39 | middleware: getDefaultMiddleware => 40 | getDefaultMiddleware({ 41 | // https://redux-toolkit.js.org/usage/usage-guide#working-with-non-serializable-data 42 | serializableCheck: { 43 | ignoredActionPaths: [ 44 | 'payload.raceConditionStrategy', 45 | 'payload.elementData', 46 | 'payload.finishTask', 47 | ], 48 | ignoredPaths: [ 49 | 'utilityProcessStatuses.UPDATE_ACCOUNT.payload.raceConditionStrategy', 50 | 'utilityProcessStatuses.SET_WALKTHROUGH_STEP_ELEMENT_DATA.payload.elementData', 51 | 'walkthrough.stepElements', 52 | 'utilityProcessStatuses.SYNC_CONTACTS_BACKGROUND_TASK.payload.finishTask', 53 | 'utilityProcessStatuses.DATA_MESSAGE_ARRIVE.payload.finishTask', 54 | ], 55 | ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER], 56 | }, 57 | }).concat(middlewares), 58 | enhancers: [], 59 | }); 60 | 61 | export const persistor = persistStore(store); 62 | 63 | sagaMiddleware.run(rootSaga); 64 | -------------------------------------------------------------------------------- /src/store/modules/AppCommon/actions/index.ts: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {createAction} from '@store/utils/actions/createAction'; 4 | 5 | export type AppStateType = 6 | | 'active' 7 | | 'background' 8 | | 'inactive' 9 | | 'unknown' 10 | | 'extension'; 11 | 12 | const APP_LOADED = createAction('APP_LOADED', { 13 | STATE: true, 14 | }); 15 | 16 | const APP_INITIALIZED = createAction('APP_INITIALIZED', { 17 | SUCCESS: true, 18 | FAILED: (errorMessage: string) => ({errorMessage}), 19 | }); 20 | 21 | const APP_STATE_CHANGE = createAction('APP_STATE_CHANGE', { 22 | STATE: (appState: AppStateType) => ({appState}), 23 | }); 24 | 25 | const INTERVAL_UPDATE = createAction('INTERVAL_UPDATE', { 26 | STATE: true, 27 | }); 28 | 29 | const UPDATE_SPLASH_VISIBLE_STATE = createAction( 30 | 'UPDATE_SPLASH_VISIBLE_STATE', 31 | { 32 | HIDE: true, 33 | }, 34 | ); 35 | 36 | export const AppCommonActions = Object.freeze({ 37 | APP_LOADED, 38 | APP_INITIALIZED, 39 | APP_STATE_CHANGE, 40 | INTERVAL_UPDATE, 41 | UPDATE_SPLASH_VISIBLE_STATE, 42 | }); 43 | -------------------------------------------------------------------------------- /src/store/modules/AppCommon/constants.ts: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | /** 4 | * Actions that should be fulfilled before we show the main app UI 5 | */ 6 | export const INITIALIZE_ACTIONS = []; 7 | -------------------------------------------------------------------------------- /src/store/modules/AppCommon/hooks/useAppStateListener.ts: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {AppCommonActions} from '@store/modules/AppCommon/actions'; 4 | import {useEffect} from 'react'; 5 | import {AppState} from 'react-native'; 6 | import {useDispatch} from 'react-redux'; 7 | 8 | export const useAppStateListener = () => { 9 | const dispatch = useDispatch(); 10 | useEffect(() => { 11 | const subscription = AppState.addEventListener('change', nextAppState => { 12 | dispatch(AppCommonActions.APP_STATE_CHANGE.STATE.create(nextAppState)); 13 | }); 14 | 15 | return () => { 16 | subscription.remove(); 17 | }; 18 | }, [dispatch]); 19 | }; 20 | -------------------------------------------------------------------------------- /src/store/modules/AppCommon/reducer/index.ts: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {AppCommonActions, AppStateType} from '@store/modules/AppCommon/actions'; 4 | import produce from 'immer'; 5 | export interface State { 6 | isAppLoaded: boolean; 7 | appInitState: 'loading' | 'success' | 'error'; 8 | appState: AppStateType | null; 9 | isSplashHidden: boolean; 10 | } 11 | 12 | type Actions = ReturnType< 13 | | typeof AppCommonActions.APP_LOADED.STATE.create 14 | | typeof AppCommonActions.APP_INITIALIZED.SUCCESS.create 15 | | typeof AppCommonActions.APP_INITIALIZED.FAILED.create 16 | | typeof AppCommonActions.APP_STATE_CHANGE.STATE.create 17 | | typeof AppCommonActions.UPDATE_SPLASH_VISIBLE_STATE.HIDE.create 18 | >; 19 | 20 | const INITIAL_STATE: State = { 21 | isAppLoaded: false, 22 | appInitState: 'loading', 23 | appState: 'active', 24 | isSplashHidden: false, 25 | }; 26 | 27 | export function appCommonReducer( 28 | state = INITIAL_STATE, 29 | action: Actions, 30 | ): State { 31 | return produce(state, draft => { 32 | switch (action.type) { 33 | case AppCommonActions.APP_LOADED.STATE.type: 34 | draft.isAppLoaded = true; 35 | break; 36 | case AppCommonActions.APP_INITIALIZED.SUCCESS.type: 37 | draft.appInitState = 'success'; 38 | break; 39 | case AppCommonActions.APP_INITIALIZED.FAILED.type: 40 | draft.appInitState = 'error'; 41 | break; 42 | case AppCommonActions.APP_STATE_CHANGE.STATE.type: 43 | draft.appState = action.payload.appState; 44 | break; 45 | case AppCommonActions.UPDATE_SPLASH_VISIBLE_STATE.HIDE.type: 46 | draft.isSplashHidden = true; 47 | break; 48 | } 49 | }); 50 | } 51 | -------------------------------------------------------------------------------- /src/store/modules/AppCommon/sagas/appInitializedHandler.ts: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {AppCommonActions} from '@store/modules/AppCommon/actions'; 4 | import { 5 | failedInitializeActionSelector, 6 | initSuccessSelector, 7 | isAppInitializedSelector, 8 | } from '@store/modules/AppCommon/selectors'; 9 | import {failedReasonSelector} from '@store/modules/UtilityProcessStatuses/selectors'; 10 | import {t} from '@translations/i18n'; 11 | import {call, put, SagaReturnType, select, take} from 'redux-saga/effects'; 12 | 13 | function* isModulesInitComplete() { 14 | const isAppInitialized: ReturnType = 15 | yield select(isAppInitializedSelector); 16 | 17 | /** 18 | * In case of Logout we don't need to init modules again 19 | */ 20 | if (isAppInitialized) { 21 | return true; 22 | } 23 | 24 | const initSuccess: ReturnType = yield select( 25 | initSuccessSelector, 26 | ); 27 | 28 | if (initSuccess) { 29 | yield put(AppCommonActions.APP_INITIALIZED.SUCCESS.create()); 30 | 31 | return true; 32 | } 33 | 34 | const failedInitializeAction: ReturnType< 35 | typeof failedInitializeActionSelector 36 | > = yield select(failedInitializeActionSelector); 37 | 38 | if (failedInitializeAction) { 39 | const errorMessage: ReturnType = yield select( 40 | failedReasonSelector.bind(null, failedInitializeAction), 41 | ); 42 | yield put( 43 | AppCommonActions.APP_INITIALIZED.FAILED.create( 44 | errorMessage ?? t('errors.general_error_message'), 45 | ), 46 | ); 47 | return true; 48 | } 49 | 50 | return false; 51 | } 52 | 53 | export function* appInitializedHandlerSaga() { 54 | while ( 55 | !((yield call(isModulesInitComplete)) as SagaReturnType< 56 | typeof isModulesInitComplete 57 | >) 58 | ) { 59 | yield take('*'); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/store/modules/AppCommon/sagas/appLoadedHandlerSaga.ts: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {AppCommonActions} from '@store/modules/AppCommon/actions'; 4 | import {isAppLoadedSelector} from '@store/modules/AppCommon/selectors'; 5 | import {put, SagaReturnType, select} from 'redux-saga/effects'; 6 | 7 | export function* appLoadedHandlerSaga() { 8 | const isAppLoaded: SagaReturnType = yield select( 9 | isAppLoadedSelector, 10 | ); 11 | if (!isAppLoaded) { 12 | yield put(AppCommonActions.APP_LOADED.STATE.create()); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/store/modules/AppCommon/sagas/index.ts: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {appInitializedHandlerSaga} from '@store/modules/AppCommon/sagas/appInitializedHandler'; 4 | import {appLoadedHandlerSaga} from '@store/modules/AppCommon/sagas/appLoadedHandlerSaga'; 5 | import {fork} from 'redux-saga/effects'; 6 | 7 | export const appCommonWatchers = [ 8 | fork(appInitializedHandlerSaga), 9 | fork(appLoadedHandlerSaga), 10 | ]; 11 | -------------------------------------------------------------------------------- /src/store/modules/AppCommon/selectors/index.ts: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {INITIALIZE_ACTIONS} from '@store/modules/AppCommon/constants'; 4 | import { 5 | isFailedSelector, 6 | isSuccessSelector, 7 | } from '@store/modules/UtilityProcessStatuses/selectors'; 8 | import {RootState} from '@store/rootReducer'; 9 | 10 | export const isAppLoadedSelector = (state: RootState) => 11 | state.appCommon.isAppLoaded; 12 | 13 | export const appInitStateSelector = (state: RootState) => 14 | state.appCommon.appInitState; 15 | 16 | export const isAppInitializedSelector = (state: RootState) => 17 | state.appCommon.appInitState === 'success'; 18 | 19 | export const isAppActiveSelector = (state: RootState) => 20 | state.appCommon.appState === 'active'; 21 | 22 | export const failedInitializeActionSelector = (state: RootState) => { 23 | return INITIALIZE_ACTIONS.find(action => isFailedSelector(action, state)); 24 | }; 25 | 26 | export const initSuccessSelector = (state: RootState) => { 27 | return !INITIALIZE_ACTIONS.find(action => !isSuccessSelector(action, state)); 28 | }; 29 | 30 | export const isSplashHiddenSelector = (state: RootState) => 31 | state.appCommon.isSplashHidden; 32 | -------------------------------------------------------------------------------- /src/store/modules/Linking/actions/index.ts: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {createAction} from '@store/utils/actions/createAction'; 4 | 5 | const HANDLE_URL = createAction('HANDLE_URL', { 6 | // In case of handling urls from linking (caught by the app) 7 | // we don't use Linking.openUrl to prevent recursivly 8 | // fired and caught deeplinks, e.g. from firebase: com.googleusercontent.apps... 9 | STATE: (url: string, handledInApp?: boolean) => ({url, handledInApp}), 10 | }); 11 | 12 | export const LinkingActions = Object.freeze({ 13 | HANDLE_URL, 14 | }); 15 | -------------------------------------------------------------------------------- /src/store/modules/Linking/hooks/useOpenUrlListener.ts: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {LinkingActions} from '@store/modules/Linking/actions'; 4 | import {useEffect} from 'react'; 5 | import {Linking} from 'react-native'; 6 | import {useDispatch} from 'react-redux'; 7 | 8 | export const useOpenUrlListener = () => { 9 | const dispatch = useDispatch(); 10 | useEffect(() => { 11 | Linking.getInitialURL().then(url => { 12 | if (url) { 13 | dispatch(LinkingActions.HANDLE_URL.STATE.create(url, true)); 14 | } 15 | }); 16 | const subscription = Linking.addEventListener('url', ({url}) => { 17 | dispatch(LinkingActions.HANDLE_URL.STATE.create(url, true)); 18 | }); 19 | return () => subscription.remove(); 20 | }, [dispatch]); 21 | }; 22 | -------------------------------------------------------------------------------- /src/store/modules/Linking/reducer/index.ts: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {LinkingActions} from '@store/modules/Linking/actions'; 4 | import produce from 'immer'; 5 | 6 | export interface State { 7 | handledUrl: string; 8 | } 9 | 10 | type Actions = ReturnType; 11 | 12 | const INITIAL_STATE: State = { 13 | handledUrl: '', 14 | }; 15 | 16 | export function linkingReducer(state = INITIAL_STATE, action: Actions): State { 17 | return produce(state, draft => { 18 | switch (action.type) { 19 | case LinkingActions.HANDLE_URL.STATE.type: 20 | draft.handledUrl = action.payload.url; 21 | break; 22 | } 23 | }); 24 | } 25 | -------------------------------------------------------------------------------- /src/store/modules/Linking/sagas/handleUrlSaga.ts: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {ENV} from '@constants/env'; 4 | import {logError} from '@services/logging'; 5 | import { 6 | isAppActiveSelector, 7 | isSplashHiddenSelector, 8 | } from '@store/modules/AppCommon/selectors'; 9 | import {LinkingActions} from '@store/modules/Linking/actions'; 10 | import {waitForSelector} from '@store/utils/sagas/effects'; 11 | import {openLink, openLinkWithInAppBrowser} from '@utils/device'; 12 | import {call} from 'redux-saga/effects'; 13 | 14 | const actionCreator = LinkingActions.HANDLE_URL.STATE.create; 15 | 16 | export function* handleUrlSaga(action: ReturnType) { 17 | const {url, handledInApp} = action.payload; 18 | 19 | const {path, parsedUrl, isDeeplink, isUniversalLink} = parseUrl(url); 20 | const searchParams = parsedUrl.searchParams; 21 | 22 | yield call(waitForSelector, isSplashHiddenSelector); 23 | yield call(waitForSelector, isAppActiveSelector); 24 | 25 | switch (path.toLowerCase()) { 26 | case 'browser': { 27 | const paramsUrl = searchParams.get('url'); 28 | if (paramsUrl) { 29 | const browserUrl = decodeURIComponent(paramsUrl); 30 | if (!new URL(browserUrl).protocol) { 31 | throw new Error(`Invalid url ${browserUrl}`); 32 | } 33 | yield call(openLinkWithInAppBrowser, {url: browserUrl}); 34 | } 35 | break; 36 | } 37 | default: 38 | if (!handledInApp) { 39 | if (!isDeeplink && !isUniversalLink) { 40 | openLink(url); 41 | } else { 42 | logError(`Unable to handle deeplink: ${url}`); 43 | } 44 | } 45 | } 46 | } 47 | 48 | export const parseUrl = (url: string) => { 49 | const parsedUrl = new URL(url); 50 | const isDeeplink = parsedUrl.protocol.includes(ENV.DEEPLINK_SCHEME ?? ''); 51 | const isUniversalLink = parsedUrl.host === ENV.DEEPLINK_DOMAIN; 52 | const path = isDeeplink 53 | ? parsedUrl.host 54 | : parsedUrl.pathname.replace('/', ''); 55 | return {isDeeplink, isUniversalLink, path, parsedUrl}; 56 | }; 57 | -------------------------------------------------------------------------------- /src/store/modules/Linking/sagas/index.ts: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {LinkingActions} from '@store/modules/Linking/actions'; 4 | import {takeLatest} from 'redux-saga/effects'; 5 | 6 | import {handleUrlSaga} from './handleUrlSaga'; 7 | 8 | export const linkingWatchers = [ 9 | takeLatest(LinkingActions.HANDLE_URL.STATE.type, handleUrlSaga), 10 | ]; 11 | -------------------------------------------------------------------------------- /src/store/modules/Linking/selectors/index.ts: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {RootState} from '@store/rootReducer'; 4 | 5 | export const handledUrlSelector = (state: RootState) => 6 | state.linking.handledUrl; 7 | -------------------------------------------------------------------------------- /src/store/modules/Permissions/actions/index.ts: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {PermissionType} from '@store/modules/Permissions/reducer'; 4 | import {createAction} from '@store/utils/actions/createAction'; 5 | import {PermissionStatus} from 'react-native-permissions'; 6 | 7 | export type PermissionsType = { 8 | contacts: PermissionStatus; 9 | pushNotifications: PermissionStatus; 10 | camera: PermissionStatus; 11 | }; 12 | 13 | const GET_PERMISSIONS = createAction('GET_PERMISSIONS', { 14 | START: (type: PermissionType) => ({type}), 15 | SUCCESS: (type: PermissionType, status: PermissionStatus) => ({type, status}), 16 | FAILED: (errorMessage: string) => ({ 17 | errorMessage, 18 | }), 19 | }); 20 | 21 | const CHECK_ALL_PERMISSIONS = createAction('CHECK_ALL_PERMISSIONS', { 22 | START: true, 23 | SUCCESS: (permissions: PermissionsType) => ({permissions}), 24 | FAILED: (errorMessage: string) => ({ 25 | errorMessage, 26 | }), 27 | }); 28 | 29 | export const PermissionsActions = Object.freeze({ 30 | GET_PERMISSIONS, 31 | CHECK_ALL_PERMISSIONS, 32 | }); 33 | -------------------------------------------------------------------------------- /src/store/modules/Permissions/reducer/index.ts: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {PermissionsActions} from '@store/modules/Permissions/actions'; 4 | import produce from 'immer'; 5 | import {PermissionStatus} from 'react-native-permissions'; 6 | 7 | export interface State { 8 | contacts: PermissionStatus | null; 9 | pushNotifications: PermissionStatus | null; 10 | camera: PermissionStatus | null; 11 | } 12 | 13 | export type PermissionType = keyof State; 14 | 15 | type Actions = ReturnType< 16 | | typeof PermissionsActions.GET_PERMISSIONS.SUCCESS.create 17 | | typeof PermissionsActions.CHECK_ALL_PERMISSIONS.SUCCESS.create 18 | >; 19 | 20 | const INITIAL_STATE: State = { 21 | contacts: null, 22 | pushNotifications: null, 23 | camera: null, 24 | }; 25 | 26 | export function permissionsReducer( 27 | state = INITIAL_STATE, 28 | action: Actions, 29 | ): State { 30 | return produce(state, draft => { 31 | switch (action.type) { 32 | case PermissionsActions.GET_PERMISSIONS.SUCCESS.type: 33 | draft[action.payload.type] = action.payload.status; 34 | break; 35 | case PermissionsActions.CHECK_ALL_PERMISSIONS.SUCCESS.type: 36 | return action.payload.permissions; 37 | } 38 | }); 39 | } 40 | -------------------------------------------------------------------------------- /src/store/modules/Permissions/sagas/checkAllPermissionsSaga.ts: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {AppCommonActions} from '@store/modules/AppCommon/actions'; 4 | import {isAppActiveSelector} from '@store/modules/AppCommon/selectors'; 5 | import {PermissionsActions} from '@store/modules/Permissions/actions'; 6 | import {PERMISSIONS_LIST} from '@store/modules/Permissions/sagas/getPermissionsSaga'; 7 | import {getErrorMessage} from '@utils/errors'; 8 | import Permissions, {checkNotifications} from 'react-native-permissions'; 9 | import {put, SagaReturnType, select} from 'redux-saga/effects'; 10 | 11 | export function* checkAllPermissionsSaga( 12 | action: ReturnType< 13 | | typeof AppCommonActions.APP_STATE_CHANGE.STATE.create 14 | | typeof AppCommonActions.APP_LOADED.STATE.create 15 | >, 16 | ) { 17 | try { 18 | const isAppActive: boolean = yield select(isAppActiveSelector); 19 | 20 | /** 21 | * The purpose of the check is to skip useless permission fetches when the app goes 22 | * from foreground to background (or an intermediate state). 23 | * But the App may be launched in the headless mode 24 | * and we still need to fetch the permissions in this case. 25 | */ 26 | if ( 27 | !isAppActive && 28 | action.type === AppCommonActions.APP_STATE_CHANGE.STATE.type 29 | ) { 30 | return; 31 | } 32 | 33 | const contacts: SagaReturnType = 34 | yield Permissions.check(PERMISSIONS_LIST.contacts); 35 | 36 | const camera: SagaReturnType = 37 | yield Permissions.check(PERMISSIONS_LIST.camera); 38 | 39 | const pushNotificationsResponse: SagaReturnType = 40 | yield checkNotifications(); 41 | 42 | const permissions = { 43 | contacts, 44 | camera, 45 | pushNotifications: pushNotificationsResponse.status, 46 | }; 47 | yield put( 48 | PermissionsActions.CHECK_ALL_PERMISSIONS.SUCCESS.create(permissions), 49 | ); 50 | } catch (error) { 51 | yield put( 52 | PermissionsActions.CHECK_ALL_PERMISSIONS.FAILED.create( 53 | getErrorMessage(error), 54 | ), 55 | ); 56 | throw error; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/store/modules/Permissions/sagas/index.ts: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {AppCommonActions} from '@store/modules/AppCommon/actions'; 4 | import {PermissionsActions} from '@store/modules/Permissions/actions'; 5 | import {takeLatest} from 'redux-saga/effects'; 6 | 7 | import {checkAllPermissionsSaga} from './checkAllPermissionsSaga'; 8 | import {getPermissionsSaga} from './getPermissionsSaga'; 9 | 10 | export const permissionsWatchers = [ 11 | takeLatest(PermissionsActions.GET_PERMISSIONS.START.type, getPermissionsSaga), 12 | takeLatest( 13 | [ 14 | AppCommonActions.APP_STATE_CHANGE.STATE.type, 15 | AppCommonActions.APP_LOADED.STATE.type, 16 | ], 17 | checkAllPermissionsSaga, 18 | ), 19 | ]; 20 | -------------------------------------------------------------------------------- /src/store/modules/Permissions/selectors/index.ts: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {PermissionType} from '@store/modules/Permissions/reducer'; 4 | import {RootState} from '@store/rootReducer'; 5 | import {RESULTS} from 'react-native-permissions'; 6 | 7 | export const isPermissionGrantedSelector = 8 | (type: PermissionType) => (state: RootState) => { 9 | return state.permissions[type] === RESULTS.GRANTED; 10 | }; 11 | 12 | export const isPermissionFetchedSelector = 13 | (type: PermissionType) => (state: RootState) => { 14 | return state.permissions[type] !== null; 15 | }; 16 | 17 | export const canAskPermissionSelector = 18 | (type: PermissionType) => (state: RootState) => { 19 | return state.permissions[type] === RESULTS.DENIED; 20 | }; 21 | -------------------------------------------------------------------------------- /src/store/modules/PushNotifications/actions/index.ts: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {FirebaseMessagingTypes} from '@react-native-firebase/messaging'; 4 | import {createAction} from '@store/utils/actions/createAction'; 5 | 6 | const NOTIFICATION_PRESS = createAction('NOTIFICATION_PRESS', { 7 | STATE: (payload: {data?: {deeplink?: string}}) => payload, 8 | }); 9 | 10 | const NOTIFICATION_ARRIVE = createAction('NOTIFICATION_ARRIVE', { 11 | STATE: (payload: {message?: FirebaseMessagingTypes.RemoteMessage}) => payload, 12 | }); 13 | 14 | const DATA_MESSAGE_ARRIVE = createAction('DATA_MESSAGE_ARRIVE', { 15 | STATE: (payload: { 16 | message: FirebaseMessagingTypes.RemoteMessage; 17 | finishTask?: () => void; 18 | }) => payload, 19 | }); 20 | 21 | export const PushNotificationsActions = Object.freeze({ 22 | NOTIFICATION_PRESS, 23 | NOTIFICATION_ARRIVE, 24 | DATA_MESSAGE_ARRIVE, 25 | }); 26 | -------------------------------------------------------------------------------- /src/store/modules/PushNotifications/constants.ts: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | export const CHANNEL_ID = 'ice'; 4 | -------------------------------------------------------------------------------- /src/store/modules/PushNotifications/sagas/handleNotificationArrive.ts: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {COLORS} from '@constants/colors'; 4 | import notifee, {AndroidImportance, AndroidStyle} from '@notifee/react-native'; 5 | import {isSplashHiddenSelector} from '@store/modules/AppCommon/selectors'; 6 | import {LinkingActions} from '@store/modules/Linking/actions'; 7 | import {PushNotificationsActions} from '@store/modules/PushNotifications/actions'; 8 | import {CHANNEL_ID} from '@store/modules/PushNotifications/constants'; 9 | import {waitForSelector} from '@store/utils/sagas/effects'; 10 | import {call, put} from 'redux-saga/effects'; 11 | import {isAndroid} from 'rn-units'; 12 | 13 | const actionCreator = PushNotificationsActions.NOTIFICATION_ARRIVE.STATE.create; 14 | 15 | export function* handleNotificationArriveSaga( 16 | action: ReturnType, 17 | ) { 18 | const {message} = action.payload; 19 | 20 | yield call(waitForSelector, isSplashHiddenSelector); 21 | 22 | if (isAndroid) { 23 | if (message?.notification) { 24 | yield call(notifee.displayNotification, { 25 | title: message.notification.title, 26 | body: message.notification.body, 27 | data: message?.data, 28 | android: { 29 | channelId: CHANNEL_ID, 30 | smallIcon: 'ic_stat_notification', 31 | sound: 'default', 32 | color: COLORS.primaryLight, 33 | ...(message.notification.android?.imageUrl 34 | ? { 35 | largeIcon: message.notification.android?.imageUrl, 36 | style: { 37 | type: AndroidStyle.BIGPICTURE, 38 | picture: message.notification.android?.imageUrl, 39 | }, 40 | } 41 | : {}), 42 | importance: AndroidImportance.HIGH, 43 | pressAction: { 44 | id: 'default', 45 | }, 46 | }, 47 | }); 48 | } 49 | } else { 50 | if (typeof message?.data?.deeplink === 'string') { 51 | yield put( 52 | LinkingActions.HANDLE_URL.STATE.create(message.data.deeplink, true), 53 | ); 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/store/modules/PushNotifications/sagas/handleNotificationPress.ts: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {isSplashHiddenSelector} from '@store/modules/AppCommon/selectors'; 4 | import {LinkingActions} from '@store/modules/Linking/actions'; 5 | import {PushNotificationsActions} from '@store/modules/PushNotifications/actions'; 6 | import {waitForSelector} from '@store/utils/sagas/effects'; 7 | import {call, put} from 'redux-saga/effects'; 8 | 9 | const actionCreator = PushNotificationsActions.NOTIFICATION_PRESS.STATE.create; 10 | 11 | export function* handleNotificationPressSaga( 12 | action: ReturnType, 13 | ) { 14 | const {data} = action.payload; 15 | 16 | yield call(waitForSelector, isSplashHiddenSelector); 17 | 18 | if (data?.deeplink) { 19 | yield put(LinkingActions.HANDLE_URL.STATE.create(data?.deeplink, true)); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/store/modules/PushNotifications/sagas/index.ts: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {PushNotificationsActions} from '@store/modules/PushNotifications/actions'; 4 | import {handleNotificationArriveSaga} from '@store/modules/PushNotifications/sagas/handleNotificationArrive'; 5 | import {handleNotificationPressSaga} from '@store/modules/PushNotifications/sagas/handleNotificationPress'; 6 | import {takeEvery, takeLatest} from 'redux-saga/effects'; 7 | 8 | export const pushNotificationsWatchers = [ 9 | takeLatest( 10 | PushNotificationsActions.NOTIFICATION_PRESS.STATE.type, 11 | handleNotificationPressSaga, 12 | ), 13 | takeEvery( 14 | PushNotificationsActions.NOTIFICATION_ARRIVE.STATE.type, 15 | handleNotificationArriveSaga, 16 | ), 17 | ]; 18 | -------------------------------------------------------------------------------- /src/store/modules/PushNotifications/types.ts: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | export type DataMessageType = 'delayed'; 4 | 5 | export type DelayedDataMessageData = { 6 | title: string; 7 | body: string; 8 | minDelaySec?: string; 9 | maxDelaySec?: string; 10 | imageUrl?: string; 11 | }; 12 | -------------------------------------------------------------------------------- /src/store/modules/Stats/actions/index.ts: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {IceCoinStats} from '@store/modules/Stats/types'; 4 | import {createAction} from '@store/utils/actions/createAction'; 5 | 6 | const GET_ICE_COIN_STATS = createAction('GET_ICE_COIN_STATS', { 7 | START: true, 8 | SUCCESS: (payload: {stats: IceCoinStats}) => payload, 9 | FAILED: (errorMessage: string) => ({errorMessage}), 10 | }); 11 | 12 | export const StatsActions = Object.freeze({ 13 | GET_ICE_COIN_STATS, 14 | }); 15 | -------------------------------------------------------------------------------- /src/store/modules/Stats/constants.ts: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | export const STATS_PERIODS = [3, 7, 14, 30, 90] as const; 4 | -------------------------------------------------------------------------------- /src/store/modules/Stats/reducer/index.ts: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {StatsActions} from '@store/modules/Stats/actions'; 4 | import {IceCoinStats} from '@store/modules/Stats/types'; 5 | import produce from 'immer'; 6 | 7 | export interface StatsState { 8 | iceCoin: IceCoinStats | null; 9 | } 10 | 11 | type Actions = ReturnType< 12 | typeof StatsActions.GET_ICE_COIN_STATS.SUCCESS.create 13 | >; 14 | 15 | const INITIAL_STATE: StatsState = { 16 | iceCoin: null, 17 | }; 18 | 19 | function reducer(state = INITIAL_STATE, action: Actions): StatsState { 20 | return produce(state, draft => { 21 | switch (action.type) { 22 | case StatsActions.GET_ICE_COIN_STATS.SUCCESS.type: 23 | draft.iceCoin = action.payload.stats; 24 | break; 25 | } 26 | }); 27 | } 28 | 29 | export const statsReducer = reducer; 30 | -------------------------------------------------------------------------------- /src/store/modules/Stats/sagas/getIceCoinStats.ts: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {LINKS} from '@constants/links'; 4 | import {isAppActiveSelector} from '@store/modules/AppCommon/selectors'; 5 | import {StatsActions} from '@store/modules/Stats/actions'; 6 | import {IceCoinStats} from '@store/modules/Stats/types'; 7 | import {getErrorMessage} from '@utils/errors'; 8 | import axios, {AxiosResponse} from 'axios'; 9 | import {call, put, select} from 'redux-saga/effects'; 10 | 11 | export function* getIceCoinStatsSaga() { 12 | try { 13 | const isAppActive: ReturnType = yield select( 14 | isAppActiveSelector, 15 | ); 16 | 17 | if (!isAppActive) { 18 | return; 19 | } 20 | 21 | const {data}: AxiosResponse = yield call( 22 | axios, 23 | LINKS.ICE_COIN_STATS, 24 | ); 25 | 26 | yield put(StatsActions.GET_ICE_COIN_STATS.SUCCESS.create({stats: data})); 27 | } catch (error) { 28 | yield put( 29 | StatsActions.GET_ICE_COIN_STATS.FAILED.create(getErrorMessage(error)), 30 | ); 31 | throw error; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/store/modules/Stats/sagas/index.ts: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {AppCommonActions} from '@store/modules/AppCommon/actions'; 4 | import {StatsActions} from '@store/modules/Stats/actions'; 5 | import {getIceCoinStatsSaga} from '@store/modules/Stats/sagas/getIceCoinStats'; 6 | import {takeLatest} from 'redux-saga/effects'; 7 | 8 | export const statsWatchers = [ 9 | takeLatest( 10 | [ 11 | StatsActions.GET_ICE_COIN_STATS.START.type, 12 | AppCommonActions.APP_LOADED.STATE.type, 13 | AppCommonActions.APP_STATE_CHANGE.STATE.type, 14 | ], 15 | getIceCoinStatsSaga, 16 | ), 17 | ]; 18 | -------------------------------------------------------------------------------- /src/store/modules/Stats/selectors/index.ts: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {RootState} from '@store/rootReducer'; 4 | 5 | export const iceCoinStatsSelector = (state: RootState) => state.stats.iceCoin; 6 | -------------------------------------------------------------------------------- /src/store/modules/Stats/types.ts: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | export type IceCoinStats = { 4 | circulatingSupply?: number; 5 | totalSupply?: number; 6 | price?: number; 7 | marketCap?: number; 8 | '24hTradingVolume'?: number; 9 | fullyDilutedMarketCap?: number; 10 | }; 11 | -------------------------------------------------------------------------------- /src/store/modules/UtilityProcessStatuses/reducer/index.ts: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {ACTION_DIVIDER} from '@store/utils/actions/createAction'; 4 | import lodashHead from 'lodash/head'; 5 | import lodashLast from 'lodash/last'; 6 | 7 | interface Action { 8 | type: string; 9 | id?: string | number; 10 | payload?: unknown; 11 | } 12 | 13 | export interface ActionData { 14 | status: string; 15 | payload?: unknown; 16 | timestamp: number; 17 | } 18 | 19 | interface ProcessStatusesState { 20 | [majorType: string]: 21 | | ActionData 22 | | { 23 | [id: string]: ActionData; 24 | }; 25 | } 26 | 27 | const INITIAL_STATE: ProcessStatusesState = {}; 28 | 29 | function reduceAction( 30 | partOfState: undefined | object, 31 | id: string | number | undefined, 32 | status: string, 33 | payload?: unknown, 34 | ) { 35 | const actionData: ActionData = { 36 | status, 37 | payload, 38 | timestamp: new Date().getTime(), 39 | }; 40 | 41 | return id === undefined 42 | ? actionData 43 | : { 44 | ...partOfState, 45 | [id]: actionData, 46 | }; 47 | } 48 | 49 | export function processStatusesReducer(state = INITIAL_STATE, action: Action) { 50 | const actionParts = action.type.split(ACTION_DIVIDER); 51 | 52 | if (actionParts.length < 1) { 53 | return state; 54 | } 55 | 56 | const status: string = lodashLast(actionParts) || ''; 57 | const majorType: string = lodashHead(actionParts) || ''; 58 | 59 | return { 60 | ...state, 61 | [majorType]: reduceAction( 62 | state[majorType], 63 | action.id, 64 | status, 65 | action.payload, 66 | ), 67 | }; 68 | } 69 | -------------------------------------------------------------------------------- /src/store/modules/UtilityProcessStatuses/selectors/index.ts: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {ActionData} from '@store/modules/UtilityProcessStatuses/reducer'; 4 | import {RootState} from '@store/rootReducer'; 5 | import lodashGet from 'lodash/get'; 6 | 7 | interface Action { 8 | id?: string | number; 9 | majorType: string; 10 | } 11 | 12 | const processStatusesRootSelector = (state: RootState) => 13 | state.utilityProcessStatuses; 14 | 15 | export const processStatusForActionSelector = ( 16 | state: RootState, 17 | action: Action, 18 | ): ActionData | undefined => 19 | lodashGet( 20 | processStatusesRootSelector(state), 21 | action.id ? `${action.majorType}.${action.id}` : `${action.majorType}`, 22 | ) as ActionData; 23 | 24 | export const isLoadingSelector = (action: Action, state: RootState) => { 25 | const requestData = processStatusForActionSelector(state, action); 26 | 27 | return !!(requestData && requestData.status === 'START'); 28 | }; 29 | 30 | export const isSuccessSelector = (action: Action, state: RootState) => { 31 | const requestData = processStatusForActionSelector(state, action); 32 | 33 | return !!(requestData && requestData.status === 'SUCCESS'); 34 | }; 35 | 36 | export const isFailedSelector = (action: Action, state: RootState) => { 37 | const requestData = processStatusForActionSelector(state, action); 38 | 39 | return !!(requestData && requestData.status === 'FAILED'); 40 | }; 41 | 42 | export const isFinishedSelector = (action: Action, state: RootState) => { 43 | const requestData = processStatusForActionSelector(state, action); 44 | 45 | return !!( 46 | requestData && 47 | (requestData.status === 'SUCCESS' || requestData.status === 'FAILED') 48 | ); 49 | }; 50 | 51 | export const actionPayloadSelector = (action: Action, state: RootState) => { 52 | const requestData = processStatusForActionSelector(state, action); 53 | 54 | return lodashGet(requestData, 'payload'); 55 | }; 56 | 57 | export const actionTimestampSelector = ( 58 | action: Action, 59 | state: RootState, 60 | ): number => { 61 | const requestData = processStatusForActionSelector(state, action); 62 | 63 | return lodashGet(requestData, 'timestamp', 0); 64 | }; 65 | 66 | export const successActionTimestampSelector = ( 67 | action: Action, 68 | state: RootState, 69 | ): number => { 70 | const isSuccess = isSuccessSelector(action, state); 71 | 72 | if (isSuccess) { 73 | return actionTimestampSelector(action, state); 74 | } 75 | 76 | return 0; 77 | }; 78 | 79 | export const failedReasonSelector = ( 80 | action: Action, 81 | state: RootState, 82 | ): string | undefined => { 83 | const isFailed = isFailedSelector(action, state); 84 | 85 | if (isFailed) { 86 | const requestData = processStatusForActionSelector(state, action); 87 | 88 | return lodashGet(requestData?.payload, 'errorMessage'); 89 | } 90 | 91 | return undefined; 92 | }; 93 | -------------------------------------------------------------------------------- /src/store/rootReducer.ts: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {appCommonReducer} from '@store/modules/AppCommon/reducer'; 4 | import {linkingReducer} from '@store/modules/Linking/reducer'; 5 | import {permissionsReducer} from '@store/modules/Permissions/reducer'; 6 | import {statsReducer} from '@store/modules/Stats/reducer'; 7 | import {processStatusesReducer} from '@store/modules/UtilityProcessStatuses/reducer'; 8 | import {combineReducers} from 'redux'; 9 | 10 | export const rootReducer = combineReducers({ 11 | appCommon: appCommonReducer, 12 | permissions: permissionsReducer, 13 | stats: statsReducer, 14 | utilityProcessStatuses: processStatusesReducer, 15 | linking: linkingReducer, 16 | }); 17 | 18 | export type RootState = ReturnType; 19 | -------------------------------------------------------------------------------- /src/store/rootSaga.ts: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {appCommonWatchers} from '@store/modules/AppCommon/sagas'; 4 | import {linkingWatchers} from '@store/modules/Linking/sagas'; 5 | import {permissionsWatchers} from '@store/modules/Permissions/sagas'; 6 | import {pushNotificationsWatchers} from '@store/modules/PushNotifications/sagas'; 7 | import {statsWatchers} from '@store/modules/Stats/sagas'; 8 | import {SagaIterator} from 'redux-saga'; 9 | import {all, call, spawn} from 'redux-saga/effects'; 10 | 11 | const watchers = [ 12 | ...appCommonWatchers, 13 | ...linkingWatchers, 14 | ...pushNotificationsWatchers, 15 | ...statsWatchers, 16 | ...permissionsWatchers, 17 | ]; 18 | 19 | export function* rootSaga(): SagaIterator { 20 | yield all([ 21 | ...watchers.map(watcher => { 22 | return spawn(function* () { 23 | while (true) { 24 | try { 25 | yield call(function* () { 26 | yield watcher; 27 | }); 28 | break; 29 | } catch {} 30 | } 31 | }); 32 | }), 33 | ]); 34 | } 35 | -------------------------------------------------------------------------------- /src/store/utils/sagas/effects.ts: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | /* eslint-disable @typescript-eslint/no-explicit-any */ 3 | 4 | import {RootState} from '@store/rootReducer'; 5 | import {Action} from 'redux'; 6 | import { 7 | ActionPattern, 8 | call, 9 | cancel, 10 | fork, 11 | HelperWorkerParameters, 12 | SagaReturnType, 13 | select, 14 | take, 15 | } from 'redux-saga/effects'; 16 | 17 | export function takeLatestEveryUnique< 18 | A extends Action, 19 | Fn extends (...args: any[]) => any, 20 | >( 21 | patternOrChannel: ActionPattern, 22 | worker: Fn, 23 | ...params: HelperWorkerParameters 24 | ) { 25 | return fork(function* () { 26 | const tasksSet = new Map(); 27 | 28 | while (true) { 29 | const action: any = yield take(patternOrChannel); 30 | const {id} = action; 31 | 32 | if (tasksSet.has(id)) { 33 | yield cancel(tasksSet.get(id)); // cancel is no-op if the task has already terminated 34 | tasksSet.delete(id); 35 | } 36 | 37 | const task = yield fork( 38 | worker, 39 | ...(params.concat(action) as Parameters), 40 | ); 41 | 42 | tasksSet.set(id, task); 43 | } 44 | }); 45 | } 46 | 47 | export function takeLeadingEveryUnique< 48 | A extends Action, 49 | Fn extends (...args: any[]) => any, 50 | >( 51 | patternOrChannel: ActionPattern, 52 | worker: Fn, 53 | ...params: HelperWorkerParameters 54 | ) { 55 | return fork(function* () { 56 | const tasksSet = new Map(); 57 | 58 | while (true) { 59 | const action: any = yield take(patternOrChannel); 60 | const {id} = action; 61 | if (!tasksSet.has(id)) { 62 | yield fork(function* () { 63 | tasksSet.set(id, true); 64 | yield call(worker, ...(params.concat(action) as Parameters)); 65 | tasksSet.delete(id); 66 | }); 67 | } 68 | } 69 | }); 70 | } 71 | 72 | export function* waitForSelector( 73 | selector: (state: RootState) => boolean, 74 | options: {takePattern?: ActionPattern} = {}, 75 | ) { 76 | const {takePattern = '*'} = options; 77 | while ( 78 | ((yield select(selector)) as SagaReturnType) === false 79 | ) { 80 | yield take(takePattern); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/translations/getPluralizer.ts: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {SupportedLocale} from '@translations/localeConfig'; 4 | import {MakePlural} from 'i18n-js/typings'; 5 | import * as plural from 'make-plural'; 6 | 7 | export const getPluralizer = (locale: SupportedLocale): MakePlural => { 8 | if (locale === 'zh-hant') { 9 | return plural.zh; 10 | } 11 | return plural[locale]; 12 | }; 13 | -------------------------------------------------------------------------------- /src/translations/i18n.ts: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {setDayjsLocale} from '@services/dayjs'; 4 | import {getPluralizer} from '@translations/getPluralizer'; 5 | import {localeConfig, SupportedLocale} from '@translations/localeConfig'; 6 | import {Translations} from '@translations/locales/en.json'; 7 | // eslint-disable-next-line no-restricted-imports 8 | import {I18n, TranslateOptions, useMakePlural} from 'i18n-js'; 9 | import {I18nManager} from 'react-native'; 10 | import RNLocalize from 'react-native-localize'; 11 | // eslint-disable-next-line no-restricted-imports 12 | import reactStringReplace from 'react-string-replace'; 13 | 14 | const DEFAULT_LOCALE: SupportedLocale = 'en'; 15 | 16 | const i18n = new I18n(); 17 | 18 | export const setLocale = (newLocale: SupportedLocale) => { 19 | i18n.store({[newLocale]: localeConfig[newLocale].translations}); 20 | i18n.locale = newLocale; 21 | i18n.pluralization.register( 22 | newLocale, 23 | // eslint-disable-next-line react-hooks/rules-of-hooks 24 | useMakePlural({pluralizer: getPluralizer(newLocale), includeZero: false}), 25 | ); 26 | setDayjsLocale(newLocale); 27 | }; 28 | 29 | export const getLocale = (): SupportedLocale => { 30 | return i18n.locale as SupportedLocale; 31 | }; 32 | 33 | export const availableLocales = Object.keys( 34 | localeConfig, 35 | ) as Array; 36 | 37 | export const locale = RNLocalize.findBestAvailableLanguage(availableLocales); 38 | 39 | i18n.defaultLocale = DEFAULT_LOCALE; 40 | 41 | const initialLocale = 42 | locale?.languageTag || 43 | (i18n.defaultLocale as Extract); 44 | 45 | i18n.enableFallback = true; 46 | i18n.translations = { 47 | // always load en for fallbacks 48 | en: localeConfig.en.translations, 49 | }; 50 | 51 | setLocale(initialLocale); 52 | 53 | export default i18n; 54 | 55 | export function t( 56 | ...args: O extends null 57 | ? Parameters<(key: K, options?: TranslateOptions) => string> 58 | : Parameters<(key: K, options: O & TranslateOptions) => string> 59 | ) { 60 | return i18n.t(args[0], args[1]); 61 | } 62 | 63 | export const tagRegex = (tag: string, isSingular = true) => { 64 | if (isSingular) { 65 | return `[[:${tag}]]`; 66 | } else { 67 | return new RegExp( 68 | `\\[\\[:${tag}\\]\\]([\\s\\S]+?)\\[\\[\\/:${tag}\\]\\](?!.*\\[\\[\\[:${tag}\\])`, 69 | ); 70 | } 71 | }; 72 | 73 | export const replaceString = reactStringReplace; 74 | 75 | export const isRTL: boolean = I18nManager.isRTL; 76 | -------------------------------------------------------------------------------- /src/utils/array.ts: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | export function shallowCompare(a1: T[], a2: T[]) { 4 | if (a1.length !== a2.length) { 5 | return false; 6 | } 7 | return a1.every((value, index) => value === a2[index]); 8 | } 9 | 10 | export function shallowCompareUnsorted( 11 | a1: T[] | Readonly, 12 | a2: T[] | Readonly, 13 | ) { 14 | if (a1.length !== a2.length) { 15 | return false; 16 | } 17 | 18 | const diff = new Set(a1); 19 | 20 | for (let value of a2) { 21 | if (diff.size === 0) { 22 | return false; 23 | } 24 | if (!diff.delete(value)) { 25 | return false; 26 | } 27 | } 28 | 29 | return true; 30 | } 31 | 32 | /** 33 | * Algorithm Fisher-Yates (aka Knuth) Shuffle 34 | * https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle 35 | */ 36 | export const shuffle = (array: T[]) => { 37 | let currentIndex = array.length; 38 | let randomIndex; 39 | 40 | // While there remain elements to shuffle. 41 | while (currentIndex > 0) { 42 | // Pick a remaining element. 43 | randomIndex = Math.floor(Math.random() * currentIndex); 44 | currentIndex--; 45 | 46 | // And swap it with the current element. 47 | [array[currentIndex], array[randomIndex]] = [ 48 | array[randomIndex], 49 | array[currentIndex], 50 | ]; 51 | } 52 | 53 | return array; 54 | }; 55 | -------------------------------------------------------------------------------- /src/utils/contacts.ts: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | /** 4 | * Returns display name for a Contact 5 | * on Android versions below 8 the entire display name is passed in the givenName field, middleName and familyName will be "" 6 | */ 7 | export const getContactName = ({ 8 | givenName = '', 9 | familyName = '', 10 | }: { 11 | givenName: string; 12 | familyName: string; 13 | }) => { 14 | return familyName ? `${givenName} ${familyName}` : givenName; 15 | }; 16 | 17 | /** 18 | * Returns Acronym for a Contact 19 | * e.g. John Smith -> JS 20 | */ 21 | export const getContactAcronym = ({ 22 | givenName = '', 23 | familyName = '', 24 | }: { 25 | givenName: string; 26 | familyName: string; 27 | }) => { 28 | if (familyName) { 29 | return ( 30 | String.fromCodePoint(givenName.codePointAt(0) ?? 0) + 31 | String.fromCodePoint(familyName.codePointAt(0) ?? 0) 32 | ); 33 | } else { 34 | return givenName 35 | .split(' ') 36 | .reduce( 37 | (acr, word) => 38 | acr.length < 2 39 | ? acr + String.fromCodePoint(word.codePointAt(0) ?? 0) 40 | : acr, 41 | '', 42 | ); 43 | } 44 | }; 45 | -------------------------------------------------------------------------------- /src/utils/date.ts: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {dayjs} from '@services/dayjs'; 4 | import {t} from '@translations/i18n'; 5 | import {Duration} from 'dayjs/plugin/duration'; 6 | 7 | export const getDurationString = (duration: Duration, numberOfUnits = 2) => { 8 | const parts = [ 9 | {value: Math.floor(duration.asDays()), unit: t('general.days_short')}, 10 | {value: duration.hours(), unit: t('general.hours_short')}, 11 | {value: duration.minutes(), unit: t('general.minutes_short')}, 12 | {value: duration.seconds(), unit: t('general.seconds_short')}, 13 | ]; 14 | 15 | const firstNotZeroIndex = parts.findIndex(({value}) => value > 0); 16 | 17 | return parts 18 | .slice(firstNotZeroIndex, firstNotZeroIndex + numberOfUnits) 19 | .filter(({value}) => value > 0) 20 | .map(({value, unit}) => value + unit) 21 | .join(' '); 22 | }; 23 | 24 | export const daysFromNow = (date: string | number | Date) => { 25 | return Math.ceil(dayjs(date).diff(dayjs(), 'd', true)); 26 | }; 27 | -------------------------------------------------------------------------------- /src/utils/email.ts: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | /** 4 | * Source: https://stackoverflow.com/a/46181/3967846 5 | */ 6 | export const validateEmail = (email: string) => { 7 | return email 8 | .toLowerCase() 9 | .match( 10 | /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/, 11 | ); 12 | }; 13 | -------------------------------------------------------------------------------- /src/utils/errors.tsx: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {t} from '@translations/i18n'; 4 | import {checkProp} from '@utils/guards'; 5 | 6 | export const getErrorMessage = (error: unknown): string => { 7 | if (checkProp(error, 'message')) { 8 | return String(error.message); 9 | } else if (typeof error === 'string') { 10 | return error; 11 | } 12 | return t('errors.unknown_error'); 13 | }; 14 | 15 | export const showError = (error: unknown) => { 16 | console.log(error); 17 | }; 18 | -------------------------------------------------------------------------------- /src/utils/file.ts: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {PixelRatio, Platform} from 'react-native'; 4 | 5 | export const getFilenameFromPath = (path: string) => { 6 | // eslint-disable-next-line no-useless-escape 7 | return path.replace(/^.*[\\\/]/, ''); 8 | }; 9 | 10 | export const getFilenameFromPathWithoutExtension = (path: string) => { 11 | return getFilenameFromPath(path).split('.')[0]; 12 | }; 13 | 14 | export function normalizePictureUri(pictureUri: string) { 15 | return Platform.OS === 'android' 16 | ? pictureUri 17 | : pictureUri.replace('file://', ''); 18 | } 19 | 20 | export const getImageUriForSize = ( 21 | uri: string, 22 | {width, height}: {width?: number; height?: number}, 23 | ) => { 24 | const queryWidth = width 25 | ? `&width=${PixelRatio.getPixelSizeForLayoutSize(width)}` 26 | : ''; 27 | const queryHeight = height 28 | ? `&height=${PixelRatio.getPixelSizeForLayoutSize(height)}` 29 | : ''; 30 | return `${uri}?quality=100${queryHeight}${queryWidth}`; 31 | }; 32 | 33 | export const isRemoteImage = (uri: string) => { 34 | return uri.startsWith('https://'); 35 | }; 36 | -------------------------------------------------------------------------------- /src/utils/guards.ts: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | export const checkProp = ( 4 | input: unknown, 5 | prop: T, 6 | ): input is {[t in T]: unknown} => { 7 | return typeof input === 'object' && input !== null && prop in input; 8 | }; 9 | -------------------------------------------------------------------------------- /src/utils/network.ts: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import NetInfo from '@react-native-community/netinfo'; 4 | 5 | export async function checkNetwork() { 6 | const networkStatus = await NetInfo.fetch(); 7 | return networkStatus.isConnected; 8 | } 9 | -------------------------------------------------------------------------------- /src/utils/promise.ts: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {AppState} from 'react-native'; 4 | 5 | export const getChunks = (input: Array, chunkSize: number) => { 6 | let results = []; 7 | const inputCopy = [...input]; 8 | while (inputCopy.length) { 9 | results.push(inputCopy.splice(0, chunkSize)); 10 | } 11 | return results; 12 | }; 13 | 14 | export const runInChunks = async ( 15 | input: Array, 16 | processor: (value: T, index: number, array: T[]) => unknown, 17 | chunkSize: number, 18 | ) => { 19 | const chunks = getChunks(input, chunkSize); 20 | 21 | const result = []; 22 | for (let i = 0; i < chunks.length; i++) { 23 | if (AppState.currentState === 'active') { 24 | await new Promise(requestAnimationFrame); 25 | } 26 | const chunkResult = await Promise.all(chunks[i].map(processor)); 27 | result.push(...chunkResult); 28 | } 29 | 30 | return result; 31 | }; 32 | -------------------------------------------------------------------------------- /src/utils/string.ts: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | /* eslint-disable no-bitwise */ 3 | 4 | export const capitalizeFirstLetter = (input: string) => { 5 | return input.charAt(0).toUpperCase() + input.slice(1); 6 | }; 7 | 8 | export const stringToColor = (input: string = '') => { 9 | let hash = 0; 10 | for (let i = 0; i < input.length; i++) { 11 | hash = input.charCodeAt(i) + ((hash << 5) - hash); 12 | } 13 | let colour = '#'; 14 | for (let i = 0; i < 3; i++) { 15 | const value = (hash >> (i * 8)) & 0xff; 16 | colour += ('00' + value.toString(16)).slice(-2); 17 | } 18 | return colour; 19 | }; 20 | -------------------------------------------------------------------------------- /src/utils/styles.ts: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {COLORS} from '@constants/colors'; 4 | // eslint-disable-next-line no-restricted-imports 5 | import {FontFamily, FONTS, FontWight} from '@constants/fonts'; 6 | import {isRTL} from '@translations/i18n'; 7 | import {TextStyle} from 'react-native'; 8 | import {isAndroid, rem} from 'rn-units'; 9 | 10 | export const font = ( 11 | fontSize: number, 12 | lineHeight?: number | null, 13 | fontWeight: FontWight = 'regular', 14 | color: keyof typeof COLORS = 'white', 15 | textAlign: TextStyle['textAlign'] = 'left', 16 | fontFamily: FontFamily = 'primary', 17 | ) => { 18 | return { 19 | fontSize: rem(fontSize), 20 | lineHeight: lineHeight != null ? rem(lineHeight) : undefined, 21 | fontFamily: FONTS[fontFamily][fontWeight], 22 | color: COLORS[color], 23 | textAlign, 24 | }; 25 | }; 26 | 27 | export function paddingLeftRtl(padding: number) { 28 | return { 29 | paddingLeft: isRTL && isAndroid ? null : padding, 30 | paddingRight: isRTL && isAndroid ? padding : null, 31 | }; 32 | } 33 | 34 | export const mirrorTransform = (isReverted?: boolean) => { 35 | if (isReverted) { 36 | return { 37 | transform: [{scaleX: isRTL ? 1 : -1}], 38 | }; 39 | } 40 | return { 41 | transform: [{scaleX: isRTL ? -1 : 1}], 42 | }; 43 | }; 44 | -------------------------------------------------------------------------------- /src/utils/ui.ts: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {Text, TextInput} from 'react-native'; 4 | 5 | export function disableFontScaling() { 6 | // @ts-ignore 7 | Text.defaultProps = Text.defaultProps ?? {}; 8 | // @ts-ignore 9 | Text.defaultProps.allowFontScaling = false; 10 | // @ts-ignore 11 | TextInput.defaultProps = TextInput.defaultProps ?? {}; 12 | // @ts-ignore 13 | TextInput.defaultProps.allowFontScaling = false; 14 | } 15 | -------------------------------------------------------------------------------- /src/utils/units.ts: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | export const radiansToDegrees = (radians: number) => { 4 | return (radians * 180) / Math.PI; 5 | }; 6 | 7 | export const degreesToRadians = (degrees: number) => { 8 | return (degrees * Math.PI) / 180; 9 | }; 10 | -------------------------------------------------------------------------------- /src/utils/uri.ts: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | export function isValidURI(uri: string | undefined) { 4 | if (!uri) { 5 | return false; 6 | } 7 | try { 8 | // eslint-disable-next-line no-new 9 | new URL(uri); 10 | return true; 11 | } catch (error) { 12 | return false; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/utils/username.ts: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {LINKS} from '@constants/links'; 4 | import {isRTL} from '@translations/i18n'; 5 | 6 | export const removeInvalidUsernameCharacters = (username: string) => { 7 | return username.replace(/[^a-zA-Z0-9.]/g, ''); 8 | }; 9 | 10 | export const validateUsername = (username: string) => { 11 | const validationRegex: RegExp = /^[a-zA-Z0-9.]+$/; 12 | return validationRegex.test(username); 13 | }; 14 | 15 | export const buildUsernameLink = (username = '') => `${LINKS.MAIN}@${username}`; 16 | 17 | export const getUsernameFromUsernameLink = (usernameLink: string) => 18 | usernameLink.match(`${LINKS.MAIN}@(.+)`)?.[1]; 19 | 20 | export const buildUsernameWithPrefix = (username = '') => { 21 | if (username.length) { 22 | return isRTL ? `${username}@` : `@${username}`; 23 | } else { 24 | return username; 25 | } 26 | }; 27 | -------------------------------------------------------------------------------- /test/e2e/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "maxWorkers": 1, 3 | "testEnvironment": "./environment", 4 | "testRunner": "jest-circus/runner", 5 | "testTimeout": 120000, 6 | "testRegex": "\\.e2e\\.js$", 7 | "reporters": ["detox/runners/jest/streamlineReporter"], 8 | "verbose": true 9 | } 10 | -------------------------------------------------------------------------------- /test/e2e/environment.js: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | const { 4 | DetoxCircusEnvironment, 5 | SpecReporter, 6 | WorkerAssignReporter, 7 | } = require('detox/runners/jest-circus'); 8 | 9 | class CustomDetoxEnvironment extends DetoxCircusEnvironment { 10 | constructor(config, context) { 11 | super(config, context); 12 | 13 | // Can be safely removed, if you are content with the default value (=300000ms) 14 | this.initTimeout = 300000; 15 | 16 | // This takes care of generating status logs on a per-spec basis. By default, Jest only reports at file-level. 17 | // This is strictly optional. 18 | this.registerListeners({ 19 | SpecReporter, 20 | WorkerAssignReporter, 21 | }); 22 | } 23 | } 24 | 25 | module.exports = CustomDetoxEnvironment; 26 | -------------------------------------------------------------------------------- /test/e2e/firstTest.e2e.js: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | const {seconds, wait} = require('test/e2e/tools'); 4 | 5 | describe('Sign in', () => { 6 | beforeEach(async () => { 7 | await device.launchApp(); 8 | }); 9 | 10 | it('should have "Sign in" title', async () => { 11 | await wait(seconds(15)); 12 | await waitFor(element(by.id('welcome_title'))).toBeVisible(); 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /test/e2e/tools.js: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | 3 | import {by, element, expect} from 'detox'; 4 | 5 | export const wait = (timeout = 0) => 6 | new Promise(resolve => setTimeout(resolve, timeout)); 7 | 8 | export const seconds = (amount = 0) => amount * 1000; 9 | 10 | export const minutes = (amount = 0) => amount * seconds(60); 11 | 12 | export const has = async testID => expect(element(by.id(testID))).toBeVisible(); 13 | 14 | export const tap = async testID => { 15 | const button = await element(by.id(testID)); 16 | return button.tap(); 17 | }; 18 | 19 | export const find = async testID => element(by.id(testID)); 20 | 21 | export const fill = async (testID, text) => { 22 | const input = await find(testID); 23 | await input.replaceText(''); 24 | return input.typeText(text + '\n'); 25 | }; 26 | 27 | export const scrollToBottom = async testID => { 28 | return element(by.id(testID)).swipe('up', 'fast', 0.75); 29 | }; 30 | 31 | export const scrollToTop = async testID => { 32 | return element(by.id(testID)).swipe('down', 'fast', 0.75); 33 | }; 34 | 35 | export const getRandomPhoneNumber = ( 36 | countryCode = 380, 37 | operatorCode = 57, 38 | size = 12, 39 | ) => { 40 | const usedLength = `${countryCode}${operatorCode}`.length; 41 | const randomDigits = `${(Date.now() / 100).toFixed(0)}`; 42 | const number = randomDigits.slice(usedLength - size); 43 | return `${countryCode}${operatorCode}${number}`; 44 | }; 45 | 46 | export const iOS = device.getPlatform() === 'ios'; 47 | 48 | export const android = device.getPlatform() === 'android'; 49 | 50 | export const waitForElement = async (testID, timeout = seconds(5)) => 51 | waitFor(element(by.id(testID))) 52 | .toBeVisible() 53 | .withTimeout(timeout); 54 | -------------------------------------------------------------------------------- /test/mock-data.js: -------------------------------------------------------------------------------- 1 | // SPDX-License-Identifier: ice License 1.0 2 | const testUser = { 3 | user: { 4 | name: 'Test User', 5 | email: 'testemail@gmail.com', 6 | }, 7 | token: 'testtoken', 8 | }; 9 | 10 | export {testUser}; 11 | -------------------------------------------------------------------------------- /types/cldr-compact-number.d.ts: -------------------------------------------------------------------------------- 1 | // source: https://github.com/WoodyWoodsta/react-native-slider/blob/master/index.d.ts 2 | 3 | declare module 'cldr-compact-number' { 4 | type CompactFormat = ( 5 | input: number, 6 | lang: 'en', 7 | localeData: null, 8 | options: { 9 | significantDigits?: number; 10 | minimumFractionDigits?: number; 11 | maximumFractionDigits?: number; 12 | financialFormat?: boolean; 13 | threshold?: number; 14 | long?: boolean; 15 | }, 16 | ) => string; 17 | 18 | const compactFormat: CompactFormat; 19 | 20 | export default compactFormat; 21 | } 22 | -------------------------------------------------------------------------------- /types/images.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.png'; 2 | declare module '*.jpg'; 3 | declare module '*.jpeg'; 4 | -------------------------------------------------------------------------------- /types/react-native.d.ts: -------------------------------------------------------------------------------- 1 | declare module '@ice/react-native' { 2 | export interface ViewMeasurementsResult { 3 | pageX: number; 4 | pageY: number; 5 | width: number; 6 | height: number; 7 | x: number; 8 | y: number; 9 | } 10 | } 11 | --------------------------------------------------------------------------------