├── .github └── workflows │ └── firebase-testing.yml ├── .gitignore ├── AppIcon ├── CHANGELOG.md ├── HeaderImage ├── NotificationIcon ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-project.txt └── src │ ├── androidTest │ ├── AndroidManifest.xml │ ├── assets │ │ ├── HelperClass.java │ │ └── appConfig.json │ └── java │ │ └── com │ │ └── gonative │ │ └── testFiles │ │ ├── FirstTestClass.java │ │ └── TestMethods.java │ ├── main │ ├── AndroidManifest.xml │ ├── assets │ │ ├── BlobDownloader.js │ │ ├── GoNativeJSBridgeLibrary.js │ │ ├── appConfig.json │ │ ├── custom-icons.json │ │ ├── fonts │ │ │ └── custom-icons.ttf │ │ └── offline.html │ ├── java │ │ └── io │ │ │ └── gonative │ │ │ └── android │ │ │ ├── ActionManager.java │ │ │ ├── AppLinksActivity.java │ │ │ ├── AudioUtils.java │ │ │ ├── ConfigPreferences.java │ │ │ ├── ConfigUpdater.java │ │ │ ├── CustomHeaders.java │ │ │ ├── DownloadService.java │ │ │ ├── FileDownloader.java │ │ │ ├── FileUploadIntentsCreator.kt │ │ │ ├── FileWriterSharer.java │ │ │ ├── GoNativeApplication.java │ │ │ ├── GoNativeWindowManager.java │ │ │ ├── HtmlIntercept.java │ │ │ ├── IOUtils.java │ │ │ ├── Installation.java │ │ │ ├── JsCustomCodeExecutor.java │ │ │ ├── JsResultBridge.java │ │ │ ├── JsonMenuAdapter.java │ │ │ ├── KeyboardManager.kt │ │ │ ├── LoginManager.java │ │ │ ├── MainActivity.java │ │ │ ├── MySwipeRefreshLayout.java │ │ │ ├── ProfilePicker.java │ │ │ ├── RegistrationManager.java │ │ │ ├── SegmentedController.java │ │ │ ├── ShakeDialogFragment.java │ │ │ ├── SplashActivity.java │ │ │ ├── TabManager.java │ │ │ ├── UrlInspector.java │ │ │ ├── UrlNavigation.java │ │ │ ├── WebViewPool.java │ │ │ ├── WebViewPoolDisownPolicy.java │ │ │ ├── files │ │ │ └── CapturedImageSaver.kt │ │ │ └── widget │ │ │ ├── CircleImageView.java │ │ │ ├── GoNativeDrawerLayout.java │ │ │ ├── GoNativeSwipeRefreshLayout.java │ │ │ ├── HandleView.kt │ │ │ ├── SwipeHistoryNavigationLayout.kt │ │ │ └── WebViewContainerView.java │ └── res │ │ ├── anim │ │ └── fast_fade_out.xml │ │ ├── drawable-hdpi │ │ ├── drawer_shadow.9.png │ │ ├── ic_actionbar.png │ │ ├── ic_chevron_down_light.png │ │ ├── ic_chevron_up_light.png │ │ ├── ic_notification.png │ │ ├── ic_refresh_black_24dp.png │ │ ├── ic_refresh_white_24dp.png │ │ ├── ic_search_black_24dp.png │ │ ├── ic_search_white_24dp.png │ │ ├── ic_share_black_24dp.png │ │ ├── ic_share_white_24dp.png │ │ └── splash.9.png │ │ ├── drawable-mdpi │ │ ├── drawer_shadow.9.png │ │ ├── ic_actionbar.png │ │ ├── ic_chevron_down_light.png │ │ ├── ic_chevron_up_light.png │ │ ├── ic_notification.png │ │ ├── ic_refresh_black_24dp.png │ │ ├── ic_refresh_white_24dp.png │ │ ├── ic_search_black_24dp.png │ │ ├── ic_search_white_24dp.png │ │ ├── ic_share_black_24dp.png │ │ ├── ic_share_white_24dp.png │ │ └── splash.9.png │ │ ├── drawable-night-hdpi │ │ ├── ic_actionbar.png │ │ └── splash.9.png │ │ ├── drawable-night-mdpi │ │ ├── ic_actionbar.png │ │ └── splash.9.png │ │ ├── drawable-night-xhdpi │ │ ├── ic_actionbar.png │ │ └── splash.9.png │ │ ├── drawable-night-xxhdpi │ │ ├── ic_actionbar.png │ │ └── splash.9.png │ │ ├── drawable-night-xxxhdpi │ │ ├── ic_actionbar.png │ │ └── splash.9.png │ │ ├── drawable-xhdpi │ │ ├── drawer_shadow.9.png │ │ ├── ic_actionbar.png │ │ ├── ic_chevron_down_light.png │ │ ├── ic_chevron_up_light.png │ │ ├── ic_notification.png │ │ ├── ic_refresh_black_24dp.png │ │ ├── ic_refresh_white_24dp.png │ │ ├── ic_search_black_24dp.png │ │ ├── ic_search_white_24dp.png │ │ ├── ic_share_black_24dp.png │ │ ├── ic_share_white_24dp.png │ │ └── splash.9.png │ │ ├── drawable-xxhdpi │ │ ├── drawer_shadow.9.png │ │ ├── ic_actionbar.png │ │ ├── ic_chevron_down_light.png │ │ ├── ic_chevron_up_light.png │ │ ├── ic_notification.png │ │ ├── ic_refresh_black_24dp.png │ │ ├── ic_refresh_white_24dp.png │ │ ├── ic_search_black_24dp.png │ │ ├── ic_search_white_24dp.png │ │ ├── ic_share_black_24dp.png │ │ ├── ic_share_white_24dp.png │ │ └── splash.9.png │ │ ├── drawable-xxxhdpi │ │ ├── ic_actionbar.png │ │ ├── ic_notification.png │ │ ├── ic_refresh_black_24dp.png │ │ ├── ic_refresh_white_24dp.png │ │ ├── ic_search_black_24dp.png │ │ ├── ic_search_white_24dp.png │ │ ├── ic_share_black_24dp.png │ │ ├── ic_share_white_24dp.png │ │ └── splash.9.png │ │ ├── drawable │ │ ├── bg_nav_icon.xml │ │ ├── ic_baseline_arrow_back_24.xml │ │ ├── ic_baseline_arrow_forward_24.xml │ │ ├── ic_go_back.xml │ │ ├── ic_go_forward.xml │ │ ├── ic_stat_onesignal_default.xml │ │ └── shape_rounded.xml │ │ ├── layout │ │ ├── actionbar_title.xml │ │ ├── activity_gonative.xml │ │ ├── activity_subscriptions.xml │ │ ├── button_menu.xml │ │ ├── empty.xml │ │ ├── menu_child_icon.xml │ │ ├── menu_child_noicon.xml │ │ ├── menu_group_icon.xml │ │ ├── menu_group_noicon.xml │ │ ├── profile_picker_dropdown.xml │ │ ├── splash_screen.xml │ │ ├── tab.xml │ │ └── view_handle.xml │ │ ├── menu │ │ └── topmenu.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_sidebar_logo.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_sidebar_logo.png │ │ ├── mipmap-night-hdpi │ │ └── ic_sidebar_logo.png │ │ ├── mipmap-night-mdpi │ │ └── ic_sidebar_logo.png │ │ ├── mipmap-night-xhdpi │ │ └── ic_sidebar_logo.png │ │ ├── mipmap-night-xxhdpi │ │ └── ic_sidebar_logo.png │ │ ├── mipmap-night-xxxhdpi │ │ └── ic_sidebar_logo.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_sidebar_logo.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_sidebar_logo.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ ├── ic_launcher_foreground.png │ │ └── ic_sidebar_logo.png │ │ ├── values-ko │ │ └── strings.xml │ │ ├── values-large │ │ └── styles.xml │ │ ├── values-night-v29 │ │ └── styles.xml │ │ ├── values-night │ │ ├── colors.xml │ │ └── styles.xml │ │ ├── values-sw600dp │ │ ├── attr.xml │ │ └── dimens.xml │ │ ├── values-sw720dp-land │ │ └── dimens.xml │ │ ├── values-v21 │ │ └── styles.xml │ │ ├── values-v29 │ │ └── styles.xml │ │ ├── values │ │ ├── attrs.xml │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── ic_launcher_background.xml │ │ ├── integers.xml │ │ ├── strings.xml │ │ └── styles.xml │ │ └── xml │ │ ├── filepaths.xml │ │ └── network_security_config.xml │ └── normal │ └── java │ └── io │ └── gonative │ └── android │ ├── GoNativeWebChromeClient.java │ ├── GoNativeWebviewClient.java │ ├── LeanWebView.java │ ├── PoolWebViewClient.java │ ├── WebViewSetup.java │ └── WebkitCookieManagerProxy.java ├── build.gradle ├── generate-app-icons.sh ├── generate-header-images.sh ├── generate-plugin-icons.sh ├── generate-theme.js ├── generate-tinted-icons.sh ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── plugins.gradle ├── plugins └── .keep └── settings.gradle /.github/workflows/firebase-testing.yml: -------------------------------------------------------------------------------- 1 | name: firebase-testing 2 | on: [push, workflow_dispatch] 3 | jobs: 4 | test-app: 5 | runs-on: ubuntu-latest 6 | steps: 7 | - uses: actions/checkout@v2 8 | - name: Set up gcloud Cloud SDK environment 9 | # You may pin to the exact commit or the version. 10 | # uses: google-github-actions/setup-gcloud@94337306dda8180d967a56932ceb4ddcf01edae7 11 | uses: google-github-actions/setup-gcloud@v0.2.0 12 | with: 13 | # Version of the gcloud SDK to install. If unspecified or set to "latest", the latest available gcloud SDK version for the target platform will be installed. Example: "290.0.1". 14 | version: latest 15 | # Service account email address to use for authentication. This is required for legacy .p12 keys but can be omitted for .json keys. This is usually of the format @.iam.gserviceaccount.com. 16 | 17 | # Service account key to use for authentication. This should be the JSON formatted private key which can be exported from the Cloud Console. The value can be raw or base64-encoded. 18 | service_account_key: ${{ secrets.GCLOUD_KEY }} 19 | # ID of the Google Cloud project. If provided, this will configure gcloud to use this project ID by default for commands. Individual commands can still override the project using the --project flag which takes precedence. 20 | project_id: gn-test-firebase-test-lab 21 | # Export the provided credentials as Google Default Application Credentials. This will make the credentials available to later steps via the GOOGLE_APPLICATION_CREDENTIALS environment variable. Future steps that consume Default Application Credentials will automatically detect and use these credentials. 22 | export_default_credentials: true 23 | 24 | - name: Make gradlew executable 25 | run: chmod +x ./gradlew 26 | 27 | - name: Copying the appConfig file from androidTest to main 28 | run: cp -f ./app/src/androidTest/assets/appConfig.json ./app/src/main/assets/appConfig.json 29 | 30 | - name: Add dependencies in app/build.gradle 31 | run: sed -i "s/dependencies *\n*{/dependencies {\nandroidTestImplementation 'androidx.test.ext:junit:1.1.2'\nandroidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'\nandroidTestImplementation 'androidx.test.espresso:espresso-contrib:3.3.0'\nandroidTestImplementation 'androidx.test.espresso:espresso-web:3.3.0'\nandroidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0'\n/" ./app/build.gradle 32 | 33 | - name: Add test runner in defaultConfig in app/build.gradle 34 | run: sed -i "s/defaultConfig *\n*{/defaultConfig {\ntestInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'/" ./app/build.gradle 35 | 36 | - name: Adding Helper method in GoNativeWebviewClient.java 37 | run: sed -i "s/super.onPageFinished *( *view *, *url *) *;/super.onPageFinished(view, url);\nHelperClass.newLoad++;/" ./app/src/normal/java/io/gonative/android/GoNativeWebviewClient.java 38 | 39 | - name: Copying HelperClass.java from androidTest/assets to normal/java/io/gonative/android/ 40 | run: cp -f ./app/src/androidTest/assets/HelperClass.java ./app/src/normal/java/io/gonative/android/HelperClass.java 41 | 42 | - name: Build the App 43 | run: ./gradlew assembleDebug assembleAndroidTest 44 | 45 | - name: Testing the App 46 | run: gcloud firebase test android run --type instrumentation --app ./app/build/outputs/apk/normal/debug/app-normal-debug.apk --test ./app/build/outputs/apk/androidTest/normal/debug/app-normal-debug-androidTest.apk --device model=flo,version=21,locale=en,orientation=portrait --device model=hammerhead,version=23,locale=en,orientation=portrait --device model=griffin,version=24,locale=en,orientation=portrait --device model=G8142,version=25,locale=en,orientation=portrait --device model=star2qlteue,version=26,locale=en,orientation=portrait --device model=walleye,version=27,locale=en,orientation=portrait --device model=OnePlus5T,version=28,locale=en,orientation=portrait --device model=x1q,version=29,locale=en,orientation=portrait --device model=flame,version=30,locale=en,orientation=portrait --results-bucket cloud-test-gn-test-firebase-test-lab --timeout 300s 47 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | .idea/ 3 | .gradle/ 4 | *.iml 5 | local.properties 6 | app/build/ 7 | build/ 8 | captures/ 9 | .DS_Store 10 | plugins/ 11 | -------------------------------------------------------------------------------- /AppIcon: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/250d90c9f09660f8a9920751e8220a9918fdb14c/AppIcon -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ## 2014-01-04 4 | 5 | - Fix a crash on reload with no page loaded. 6 | 7 | ## 2015-01-02 8 | 9 | - Update to latest gradle and build tools versions, making the project compatible with Android Studio 1.0. 10 | - Fix bugs related to syncing of tabs with sidebar menu. 11 | 12 | ## 2014-12-23 13 | 14 | - Allow setting of viewport while preserving ability to zoom. 15 | - Allow dynamic config of navigation title image URLs. 16 | - Various bug fixes involving javascript after page load, and tab coloring, tab animations, and a crash on application resume. 17 | 18 | ## 2014-12-22 19 | 20 | - Fix various threading bugs where UI methods were called from non-UI threads. 21 | 22 | ## 2014-12-05 23 | 24 | - Support showing the navigation title image on specific URLs. 25 | 26 | ## 2014-12-03 27 | 28 | - Support customizing user agent per URL. 29 | - Add color styling options for tabs. 30 | 31 | ## 2014-11-30 32 | 33 | - New tabs with better material design and animations. 34 | - Fix some automatic icon generation scripts. 35 | 36 | ## 2014-11-26 37 | 38 | - Fix a crash involving webview pools. 39 | 40 | ## 2014-11-25 41 | 42 | - Add support for custom actions in action bar. 43 | -------------------------------------------------------------------------------- /HeaderImage: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/250d90c9f09660f8a9920751e8220a9918fdb14c/HeaderImage -------------------------------------------------------------------------------- /NotificationIcon: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/250d90c9f09660f8a9920751e8220a9918fdb14c/NotificationIcon -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Archive Notice 2 | 3 | This repository is archived and is no longer being maintained. You can now build an app using our online App Studio at https://median.co/app and download custom iOS source code for free. 4 | 5 | 6 | gonative-android 7 | ================ 8 | 9 | This is the native Android code previously used by [GoNative](https://median.co). 10 | 11 | It allows the creation of apps from existing mobile-optimized websites. 12 | 13 | How to use 14 | ------------ 15 | Import into Android Studio. Edit appConfig.json as appropriate. 16 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | -------------------------------------------------------------------------------- /app/proguard-project.txt: -------------------------------------------------------------------------------- 1 | # To enable ProGuard in your project, edit project.properties 2 | # to define the proguard.config property as described in that file. 3 | # 4 | # Add project specific ProGuard rules here. 5 | # By default, the flags in this file are appended to flags specified 6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt 7 | # You can edit the include path and order by changing the ProGuard 8 | # include property in project.properties. 9 | # 10 | # For more details, see 11 | # http://developer.android.com/guide/developing/tools/proguard.html 12 | 13 | # Add any project specific keep options here: 14 | 15 | # If your project uses WebView with JS, uncomment the following 16 | # and specify the fully qualified class name to the JavaScript interface 17 | # class: 18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 19 | # public *; 20 | #} 21 | 22 | # webview interfaces 23 | 24 | -keepclassmembers class io.gonative.android.ProfilePicker$ProfileJsBridge { 25 | ; 26 | } 27 | -keepclassmembers class io.gonative.android.MainActivity$StatusCheckerBridge { 28 | ; 29 | } 30 | -keepattributes JavascriptInterface 31 | 32 | # stuff for google play services 33 | -keep class * extends java.util.ListResourceBundle { 34 | protected Object[][] getContents(); 35 | } 36 | 37 | -keep public class com.google.android.gms.common.internal.safeparcel.SafeParcelable { 38 | public static final *** NULL; 39 | } 40 | 41 | -keepnames @com.google.android.gms.common.annotation.KeepName class * 42 | -keepclassmembernames class * { 43 | @com.google.android.gms.common.annotation.KeepName *; 44 | } 45 | 46 | -keepnames class * implements android.os.Parcelable { 47 | public static final ** CREATOR; 48 | } 49 | 50 | # Google Cloud Messaging 51 | -keep class com.google.android.gms.** { *; } 52 | -keep interface com.google.android.gms.** { *; } 53 | 54 | # appcompat library 55 | -dontwarn android.support.v4.** 56 | -keep class android.support.v4.** { *; } 57 | -keep interface android.support.v4.** { *; } 58 | -dontwarn android.support.v7.** 59 | -keep class android.support.v7.** { *; } 60 | -keep interface android.support.v7.** { *; } 61 | -------------------------------------------------------------------------------- /app/src/androidTest/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/src/androidTest/assets/HelperClass.java: -------------------------------------------------------------------------------- 1 | package io.gonative.android; 2 | 3 | public class HelperClass { 4 | public volatile static int newLoad = 0; 5 | } 6 | -------------------------------------------------------------------------------- /app/src/androidTest/assets/appConfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "general": { 3 | "userAgentAdd": "gonative", 4 | "initialUrl": "https://gonative.io", 5 | "appName": "GoNative.io" 6 | }, 7 | "navigation": { 8 | "androidPullToRefresh": true, 9 | "sidebarNavigation": { 10 | "sidebarEnabledRegex": null, 11 | "menus": [{ 12 | "name": "default", 13 | "items": [{ 14 | "url": "https://gonative.io", 15 | "label": "Home", 16 | "subLinks": [] 17 | }, { 18 | "url": "https://gonative.io/about", 19 | "label": "About", 20 | "subLinks": [] 21 | }, { 22 | "url": "https://gonative.io/examples", 23 | "label": "Examples", 24 | "subLinks": [] 25 | }], 26 | "active": true 27 | }] 28 | }, 29 | "tabNavigation": { 30 | "tabSelectionConfig": [{ 31 | "id": "1", 32 | "regex": ".*about.*" 33 | }], 34 | "tabMenus": [{ 35 | "id": "1", 36 | "items": [{ 37 | "icon": "fa-cloud", 38 | "label": "Tab 1", 39 | "url": "https://www.gonative.io/pricing" 40 | }, { 41 | "icon": "fa-globe", 42 | "label": "Tab 2", 43 | "url": "https://www.gonative.io/examples" 44 | }, { 45 | "icon": "fa-users", 46 | "label": "Tab 3", 47 | "url": "javascript:alert('You selected tab 3. These tabs are only shown on the about page')" 48 | }] 49 | }], 50 | "active": true 51 | }, 52 | "actionConfig": { 53 | "active": true, 54 | "actions": [{ 55 | "id": "exampleActions", 56 | "items": [{ 57 | "label": "Globe", 58 | "icon": "fa-globe", 59 | "url": "javascript:alert('You tapped the globe! It only appears on the Examples page')" 60 | }] 61 | }], 62 | "actionSelection": [{ 63 | "regex": ".*/examples.*", 64 | "id": "exampleActions" 65 | }] 66 | }, 67 | "regexInternalExternal": { 68 | "rules": [{ 69 | "regex": "https?://([-\\w]+\\.)*facebook\\.com/login.php.*", 70 | "internal": true 71 | }, { 72 | "regex": "https?://([-\\w]+\\.)*facebook\\.com/pages/.*", 73 | "internal": false 74 | }, { 75 | "regex": "https?://([-\\w]+\\.)*facebook\\.com/sharer\\.php.*", 76 | "internal": false 77 | }, { 78 | "regex": "https?://([-\\w]+\\.)*plus\\.google\\.com/share.*", 79 | "internal": false 80 | }, { 81 | "regex": "https?://([-\\w]+\\.)*twitter\\.com/intent/.*", 82 | "internal": false 83 | }, { 84 | "regex": "https?://([-\\w]+\\.)*gonative\\.io/?.*", 85 | "internal": true 86 | }, { 87 | "regex": "https?://([-\\w]+\\.)*google\\.com/?.*", 88 | "internal": true 89 | }, { 90 | "regex": "https://gonative-test-web.web.app/.*", 91 | "internal": true 92 | }, 93 | { 94 | "regex": "https://us-central1-gn-test-firebase-test-lab.cloudfunctions.net/.*", 95 | "internal": true 96 | }], 97 | "active": true 98 | }, 99 | "redirects": [{ 100 | "from": "https://example.com/from/", 101 | "to": "https://example.com/to/" 102 | }] 103 | }, 104 | "forms": { 105 | "search": { 106 | "active": true, 107 | "searchTemplateURL": "https://us-central1-gn-test-firebase-test-lab.cloudfunctions.net/gnTestSearch?q=" 108 | } 109 | }, 110 | "styling": { 111 | "showActionBar": true, 112 | "showNavigationBar": true, 113 | "iosTitleColor": "#333333", 114 | "iosTintColor": "#0091fe", 115 | "androidTheme": "Light.DarkActionBar", 116 | "androidSidebarBackgroundColor": "#111111", 117 | "androidSidebarForegroundColor": "#d0d0d0", 118 | "androidHideTitleInActionBar": false, 119 | "androidPullToRefreshColor": "#333333", 120 | "androidTabBarBackgroundColor": "#fefefe", 121 | "androidTabBarTextColor": "#747474", 122 | "androidTabBarIndicatorColorx": "#2f79fe", 123 | "androidShowSplash": true, 124 | "androidShowSplashMaxTime": null, 125 | "androidShowSplashForceTime": null, 126 | "disableAnimations": false, 127 | "menuAnimationDuration": 0.15, 128 | "transitionInteractiveDelayMax": 0.2 129 | }, 130 | "permissions": { 131 | "usesGeolocation": false, 132 | "androidDownloadToPublicStorage": false 133 | }, 134 | "services": { 135 | "oneSignal": { 136 | "active": false, 137 | "applicationId": "" 138 | }, 139 | "facebook": { 140 | "active": false, 141 | "appId": "", 142 | "displayName": "" 143 | }, 144 | "registration": { 145 | "active": false, 146 | "endpoints": [{ 147 | "url": "https://gonative.io/example_push_endpoint", 148 | "dataType": "onesignal", 149 | "urlRegex": ".*/loginfinished" 150 | }] 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/gonative/testFiles/FirstTestClass.java: -------------------------------------------------------------------------------- 1 | package com.gonative.testFiles; 2 | 3 | import android.webkit.WebView; 4 | import androidx.test.ext.junit.rules.ActivityScenarioRule; 5 | import androidx.test.ext.junit.runners.AndroidJUnit4; 6 | import androidx.test.filters.LargeTest; 7 | import androidx.test.filters.SdkSuppress; 8 | import androidx.test.uiautomator.UiDevice; 9 | import org.json.JSONException; 10 | import org.junit.Before; 11 | import org.junit.Rule; 12 | import org.junit.Test; 13 | import org.junit.runner.RunWith; 14 | import io.gonative.android.MainActivity; 15 | import io.gonative.android.R; 16 | import io.gonative.gonative_core.AppConfig; 17 | import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation; 18 | 19 | @RunWith(AndroidJUnit4.class) 20 | @SdkSuppress(minSdkVersion = 18) 21 | @LargeTest 22 | public class FirstTestClass{ 23 | TestMethods testMethods; 24 | AppConfig appConfig; 25 | WebView webView; 26 | private UiDevice uiDevice; 27 | 28 | @Rule 29 | public ActivityScenarioRule activityScenarioRule = new ActivityScenarioRule<>(MainActivity.class); 30 | 31 | @Before 32 | public void initMethod() throws InterruptedException { 33 | for(int i = 0; i < 10; i++){ 34 | try{ 35 | uiDevice = UiDevice.getInstance(getInstrumentation()); 36 | }catch (RuntimeException runtimeException){ 37 | Thread.sleep(2000); 38 | continue; 39 | } 40 | Thread.sleep(1000); 41 | break; 42 | } 43 | activityScenarioRule.getScenario().onActivity(activity -> { 44 | appConfig = AppConfig.getInstance(activity); 45 | webView = activity.findViewById(R.id.webview); 46 | testMethods = new TestMethods(activity, webView); 47 | }); 48 | } 49 | 50 | //Sidebar Navigation Test 51 | @Test 52 | public void testSidebarNavigation() throws InterruptedException, JSONException { 53 | if(appConfig.showNavigationMenu && (appConfig.menus.get("default") != null)){ 54 | if(appConfig.menus.get("default") == null) throw new RuntimeException("Navigation drawer list not found."); 55 | else { 56 | testMethods.waitForPageLoaded(); 57 | testMethods.testNavigation(appConfig.menus.get("default")); 58 | } 59 | } 60 | } 61 | 62 | //Tab Menu Navigation Test 63 | @Test 64 | public void testTabMenuNavigation() throws JSONException, InterruptedException { 65 | if(appConfig.tabMenuRegexes.size() == 0) throw new RuntimeException("No Tab Menus found."); 66 | else{ 67 | testMethods.waitForPageLoaded(); 68 | testMethods.m_testTabNavigation(appConfig.tabMenus, appConfig.tabMenuRegexes); 69 | } 70 | } 71 | 72 | //Internal vs External Links Test 73 | @Test 74 | public void testIvE() throws InterruptedException { 75 | testMethods.waitForPageLoaded(); 76 | testMethods.testInternalvExternalLinks(uiDevice); 77 | } 78 | 79 | //Pull to Refresh Test 80 | @Test 81 | public void pullToRefresh() throws InterruptedException { 82 | if(appConfig.pullToRefresh){ 83 | testMethods.waitForPageLoaded(); 84 | testMethods.testPullToRefresh(); 85 | } 86 | } 87 | 88 | //Search Button Test 89 | @Test 90 | public void testSearch() throws InterruptedException { 91 | if(appConfig.searchTemplateUrl != null && !appConfig.searchTemplateUrl.isEmpty()){ 92 | testMethods.waitForPageLoaded(); 93 | testMethods.testSearchButton(); 94 | } 95 | } 96 | 97 | //Refresh Button Test 98 | @Test 99 | public void testRefreshButton() throws InterruptedException { 100 | if(appConfig.showRefreshButton){ 101 | testMethods.waitForPageLoaded(); 102 | testMethods.testRefreshButton(); 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 75 | 76 | 83 | 84 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 108 | 111 | 112 | 113 | 114 | 117 | 120 | 121 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | -------------------------------------------------------------------------------- /app/src/main/assets/BlobDownloader.js: -------------------------------------------------------------------------------- 1 | // This is used because download from native side won't have session changes. 2 | 3 | function gonativeDownloadBlobUrl(url) { 4 | var req = new XMLHttpRequest(); 5 | req.open('GET', url, true); 6 | req.responseType = 'blob'; 7 | 8 | req.onload = function(event) { 9 | var blob = req.response; 10 | saveBlob(blob); 11 | }; 12 | req.send(); 13 | 14 | function sendMessage(message) { 15 | if (window.webkit && window.webkit.messageHandlers && 16 | window.webkit.messageHandlers.fileWriterSharer) { 17 | window.webkit.messageHandlers.fileWriterSharer.postMessage(message); 18 | } 19 | if (window.gonative_file_writer_sharer && window.gonative_file_writer_sharer.postMessage) { 20 | window.gonative_file_writer_sharer.postMessage(JSON.stringify(message)); 21 | } 22 | } 23 | 24 | function saveBlob(blob, filename) { 25 | var chunkSize = 1024 * 1024; // 1mb 26 | var index = 0; 27 | // random string to identify this file transfer 28 | var id = Math.random().toString(36).substring(2, 15) + Math.random().toString(36).substring(2, 15); 29 | 30 | function sendHeader() { 31 | sendMessage({ 32 | event: 'fileStart', 33 | id: id, 34 | size: blob.size, 35 | type: blob.type, 36 | name: filename 37 | }); 38 | } 39 | 40 | function sendChunk() { 41 | if (index >= blob.size) { 42 | return sendEnd(); 43 | } 44 | 45 | var chunkToSend = blob.slice(index, index + chunkSize); 46 | var reader = new FileReader(); 47 | reader.readAsDataURL(chunkToSend); 48 | reader.onloadend = function() { 49 | sendMessage({ 50 | event: 'fileChunk', 51 | id: id, 52 | data: reader.result 53 | }); 54 | index += chunkSize; 55 | setTimeout(sendChunk); 56 | }; 57 | } 58 | 59 | function sendEnd() { 60 | sendMessage({ 61 | event: 'fileEnd', 62 | id:id 63 | }); 64 | } 65 | 66 | sendHeader(); 67 | gonative_run_after_storage_permissions.push(sendChunk); 68 | } 69 | } 70 | 71 | gonative_run_after_storage_permissions = []; 72 | function gonativeGotStoragePermissions() { 73 | while (gonative_run_after_storage_permissions.length > 0) { 74 | var run = gonative_run_after_storage_permissions.shift(); 75 | run(); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /app/src/main/assets/custom-icons.json: -------------------------------------------------------------------------------- 1 | { 2 | "gonative-icon": 59392 3 | } 4 | -------------------------------------------------------------------------------- /app/src/main/assets/fonts/custom-icons.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/250d90c9f09660f8a9920751e8220a9918fdb14c/app/src/main/assets/fonts/custom-icons.ttf -------------------------------------------------------------------------------- /app/src/main/assets/offline.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Device Offline 5 | 15 | 16 | 17 |
18 | 21 | 22 |

No internet connection
Check your connection and try again

23 |
24 | 25 |
26 | 48 | 49 | -------------------------------------------------------------------------------- /app/src/main/java/io/gonative/android/AppLinksActivity.java: -------------------------------------------------------------------------------- 1 | package io.gonative.android; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | 6 | import androidx.annotation.Nullable; 7 | import androidx.appcompat.app.AppCompatActivity; 8 | 9 | public class AppLinksActivity extends AppCompatActivity { 10 | 11 | @Override 12 | protected void onCreate(@Nullable Bundle savedInstanceState) { 13 | super.onCreate(savedInstanceState); 14 | launchApp(); 15 | } 16 | 17 | private void launchApp() { 18 | Intent intent = new Intent(this, MainActivity.class); 19 | if (getIntent().getData() != null) { 20 | intent.setData(getIntent().getData()); 21 | intent.setAction(Intent.ACTION_VIEW); 22 | } 23 | startActivity(intent); 24 | finish(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/io/gonative/android/ConfigPreferences.java: -------------------------------------------------------------------------------- 1 | package io.gonative.android; 2 | 3 | import android.content.Context; 4 | import android.content.SharedPreferences; 5 | import android.preference.PreferenceManager; 6 | import android.text.TextUtils; 7 | 8 | public class ConfigPreferences { 9 | private static final String APP_THEME_KEY = "io.gonative.android.appTheme"; 10 | 11 | private Context context; 12 | private SharedPreferences sharedPreferences; 13 | 14 | public ConfigPreferences(Context context) { 15 | this.context = context; 16 | } 17 | 18 | private SharedPreferences getSharedPreferences() { 19 | if (this.sharedPreferences == null) { 20 | this.sharedPreferences = PreferenceManager.getDefaultSharedPreferences(this.context); 21 | } 22 | return this.sharedPreferences; 23 | } 24 | 25 | public String getAppTheme() { 26 | SharedPreferences preferences = getSharedPreferences(); 27 | return preferences.getString(APP_THEME_KEY, null); 28 | } 29 | 30 | public void setAppTheme(String appTheme) { 31 | SharedPreferences preferences = getSharedPreferences(); 32 | if (TextUtils.isEmpty(appTheme)) { 33 | preferences.edit().remove(APP_THEME_KEY).commit(); 34 | } else { 35 | preferences.edit().putString(APP_THEME_KEY, appTheme).commit(); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/io/gonative/android/ConfigUpdater.java: -------------------------------------------------------------------------------- 1 | package io.gonative.android; 2 | 3 | import android.content.Context; 4 | import android.os.AsyncTask; 5 | 6 | import org.json.JSONException; 7 | import org.json.JSONObject; 8 | 9 | import java.io.OutputStreamWriter; 10 | import java.lang.ref.WeakReference; 11 | import java.net.HttpURLConnection; 12 | import java.net.URL; 13 | 14 | import io.gonative.gonative_core.AppConfig; 15 | import io.gonative.gonative_core.GNLog; 16 | 17 | /** 18 | * Created by weiyin on 8/8/14. 19 | */ 20 | public class ConfigUpdater { 21 | private static final String TAG = ConfigUpdater.class.getName(); 22 | 23 | private Context context; 24 | 25 | ConfigUpdater(Context context) { 26 | this.context = context; 27 | } 28 | 29 | public void registerEvent() { 30 | if (AppConfig.getInstance(context).disableEventRecorder) return; 31 | 32 | new EventTask(context).execute(); 33 | } 34 | 35 | private static class EventTask extends AsyncTask { 36 | WeakReference contextReference; 37 | 38 | EventTask(Context context) { 39 | this.contextReference = new WeakReference<>(context); 40 | } 41 | 42 | @Override 43 | protected Void doInBackground(Void... params) { 44 | Context context = contextReference.get(); 45 | if (context == null) return null; 46 | 47 | JSONObject json = new JSONObject(Installation.getInfo(context)); 48 | 49 | try { 50 | json.put("event", "launch"); 51 | } catch (JSONException e) { 52 | GNLog.getInstance().logError(TAG, e.getMessage(), e); 53 | return null; 54 | } 55 | 56 | try { 57 | URL url = new URL("https://events.gonative.io/api/events/new"); 58 | HttpURLConnection connection = (HttpURLConnection) url.openConnection(); 59 | connection.setRequestMethod("POST"); 60 | connection.setRequestProperty("Content-Type", "application/json"); 61 | connection.setDoOutput(true); 62 | connection.setDoInput(false); // we do not care about response 63 | OutputStreamWriter writer = new OutputStreamWriter(connection.getOutputStream(), "UTF-8"); 64 | writer.write(json.toString()); 65 | writer.close(); 66 | connection.connect(); 67 | connection.getResponseCode(); 68 | connection.disconnect(); 69 | } catch (Exception e) { 70 | GNLog.getInstance().logError(TAG, e.getMessage(), e); 71 | } 72 | 73 | return null; 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /app/src/main/java/io/gonative/android/CustomHeaders.java: -------------------------------------------------------------------------------- 1 | package io.gonative.android; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.content.Context; 5 | import android.os.Build; 6 | import android.provider.Settings; 7 | import android.util.Base64; 8 | 9 | import java.io.UnsupportedEncodingException; 10 | import java.util.HashMap; 11 | import java.util.Map; 12 | 13 | import io.gonative.gonative_core.AppConfig; 14 | 15 | /** 16 | * Created by weiyin on 5/1/17. 17 | */ 18 | 19 | public class CustomHeaders { 20 | public static Map getCustomHeaders(Context context) { 21 | AppConfig appConfig = AppConfig.getInstance(context); 22 | if (appConfig.customHeaders == null) return null; 23 | 24 | HashMap result = new HashMap<>(); 25 | for (Map.Entry entry : appConfig.customHeaders.entrySet()) { 26 | String key = entry.getKey(); 27 | String val; 28 | try { 29 | val = interpolateValues(context, entry.getValue()); 30 | } catch (UnsupportedEncodingException e) { 31 | val = null; 32 | } 33 | 34 | if (key != null & val != null) { 35 | result.put(key, val); 36 | } 37 | } 38 | 39 | return result; 40 | } 41 | 42 | private static String interpolateValues(Context context, String value) throws UnsupportedEncodingException { 43 | if (value == null) return null; 44 | 45 | if (value.contains("%DEVICEID%")) { 46 | @SuppressLint("HardwareIds") 47 | String androidId = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID); 48 | if (androidId == null) androidId = ""; 49 | value = value.replace("%DEVICEID%", androidId); 50 | } 51 | 52 | if (value.contains("%DEVICENAME64%")) { 53 | // base 64 encoded name 54 | String manufacturer = Build.MANUFACTURER; 55 | String model = Build.MODEL; 56 | String name; 57 | if (model.startsWith(manufacturer)) { 58 | name = model; 59 | } else { 60 | name = manufacturer + " " + model; 61 | } 62 | 63 | String name64 = Base64.encodeToString(name.getBytes("UTF-8"), Base64.NO_WRAP); 64 | value = value.replace("%DEVICENAME64%", name64); 65 | } 66 | 67 | return value; 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /app/src/main/java/io/gonative/android/GoNativeApplication.java: -------------------------------------------------------------------------------- 1 | package io.gonative.android; 2 | 3 | import android.os.Message; 4 | import android.webkit.ValueCallback; 5 | import android.widget.Toast; 6 | 7 | import androidx.appcompat.app.AppCompatDelegate; 8 | import androidx.multidex.MultiDexApplication; 9 | 10 | import java.util.List; 11 | import java.util.Map; 12 | 13 | import io.gonative.gonative_core.AppConfig; 14 | import io.gonative.gonative_core.Bridge; 15 | import io.gonative.gonative_core.BridgeModule; 16 | import io.gonative.gonative_core.GNLog; 17 | 18 | /** 19 | * Created by weiyin on 9/2/15. 20 | * Copyright 2014 GoNative.io LLC 21 | */ 22 | public class GoNativeApplication extends MultiDexApplication { 23 | 24 | private LoginManager loginManager; 25 | private RegistrationManager registrationManager; 26 | private WebViewPool webViewPool; 27 | private Message webviewMessage; 28 | private ValueCallback webviewValueCallback; 29 | private GoNativeWindowManager goNativeWindowManager; 30 | private List plugins; 31 | private final static String TAG = GoNativeApplication.class.getSimpleName(); 32 | public final Bridge mBridge = new Bridge(this) { 33 | @Override 34 | protected List getPlugins() { 35 | if (GoNativeApplication.this.plugins == null) { 36 | GoNativeApplication.this.plugins = new PackageList(GoNativeApplication.this).getPackages(); 37 | } 38 | 39 | return GoNativeApplication.this.plugins; 40 | } 41 | }; 42 | 43 | @Override 44 | public void onCreate() { 45 | super.onCreate(); 46 | AppCompatDelegate.setCompatVectorFromResourcesEnabled(true); 47 | 48 | mBridge.onApplicationCreate(this); 49 | 50 | AppConfig appConfig = AppConfig.getInstance(this); 51 | if (appConfig.configError != null) { 52 | Toast.makeText(this, "Invalid appConfig json", Toast.LENGTH_LONG).show(); 53 | GNLog.getInstance().logError(TAG, "AppConfig error", appConfig.configError); 54 | } 55 | 56 | this.loginManager = new LoginManager(this); 57 | 58 | if (appConfig.registrationEndpoints != null) { 59 | this.registrationManager = new RegistrationManager(this); 60 | registrationManager.processConfig(appConfig.registrationEndpoints); 61 | } 62 | 63 | // some global webview setup 64 | WebViewSetup.setupWebviewGlobals(this); 65 | 66 | webViewPool = new WebViewPool(); 67 | 68 | goNativeWindowManager = new GoNativeWindowManager(); 69 | } 70 | 71 | public LoginManager getLoginManager() { 72 | return loginManager; 73 | } 74 | 75 | public RegistrationManager getRegistrationManager() { 76 | return registrationManager; 77 | } 78 | 79 | public WebViewPool getWebViewPool() { 80 | return webViewPool; 81 | } 82 | 83 | public Message getWebviewMessage() { 84 | return webviewMessage; 85 | } 86 | 87 | public void setWebviewMessage(Message webviewMessage) { 88 | this.webviewMessage = webviewMessage; 89 | } 90 | 91 | public Map getAnalyticsProviderInfo() { 92 | return mBridge.getAnalyticsProviderInfo(); 93 | } 94 | 95 | // Needed for Crosswalk 96 | @SuppressWarnings("unused") 97 | public ValueCallback getWebviewValueCallback() { 98 | return webviewValueCallback; 99 | } 100 | 101 | public void setWebviewValueCallback(ValueCallback webviewValueCallback) { 102 | this.webviewValueCallback = webviewValueCallback; 103 | } 104 | 105 | public GoNativeWindowManager getWindowManager() { 106 | return goNativeWindowManager; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /app/src/main/java/io/gonative/android/GoNativeWindowManager.java: -------------------------------------------------------------------------------- 1 | package io.gonative.android; 2 | 3 | import android.text.TextUtils; 4 | 5 | import java.util.LinkedHashMap; 6 | import java.util.Map; 7 | 8 | public class GoNativeWindowManager { 9 | private final LinkedHashMap windows; 10 | private ExcessWindowsClosedListener excessWindowsClosedListener; 11 | 12 | public GoNativeWindowManager() { 13 | windows = new LinkedHashMap<>(); 14 | } 15 | 16 | public void addNewWindow(String activityId, boolean isRoot) { 17 | this.windows.put(activityId, new ActivityWindow(activityId, isRoot)); 18 | } 19 | 20 | public void removeWindow(String activityId) { 21 | this.windows.remove(activityId); 22 | 23 | if (excessWindowsClosedListener != null && windows.size() <= 1) { 24 | excessWindowsClosedListener.onAllExcessWindowClosed(); 25 | } 26 | } 27 | 28 | public void setOnExcessWindowClosedListener(ExcessWindowsClosedListener listener) { 29 | this.excessWindowsClosedListener = listener; 30 | } 31 | 32 | public ActivityWindow getActivityWindowInfo(String activityId) { 33 | return windows.get(activityId); 34 | } 35 | 36 | public void setUrlLevel(String activityId, int urlLevel) { 37 | ActivityWindow window = windows.get(activityId); 38 | if (window != null) { 39 | window.setUrlLevels(urlLevel, window.parentUrlLevel); 40 | } 41 | } 42 | 43 | public int getUrlLevel(String activityId) { 44 | ActivityWindow window = windows.get(activityId); 45 | if (window != null) { 46 | return window.urlLevel; 47 | } 48 | return -1; 49 | } 50 | 51 | public void setParentUrlLevel(String activityId, int parentLevel) { 52 | ActivityWindow window = windows.get(activityId); 53 | if (window != null) { 54 | window.setUrlLevels(window.urlLevel, parentLevel); 55 | } 56 | } 57 | 58 | public int getParentUrlLevel(String activityId) { 59 | ActivityWindow window = windows.get(activityId); 60 | if (window != null) { 61 | return window.parentUrlLevel; 62 | } 63 | return -1; 64 | } 65 | 66 | public void setUrlLevels(String activityId, int urlLevel, int parentLevel) { 67 | ActivityWindow window = windows.get(activityId); 68 | if (window != null) { 69 | window.setUrlLevels(urlLevel, parentLevel); 70 | } 71 | } 72 | 73 | public boolean isRoot(String activityId) { 74 | ActivityWindow window = windows.get(activityId); 75 | if (window != null) { 76 | return window.isRoot; 77 | } 78 | return false; 79 | } 80 | 81 | public void setAsNewRoot(String activityId) { 82 | for (Map.Entry entry : windows.entrySet()) { 83 | ActivityWindow window = entry.getValue(); 84 | if (TextUtils.equals(activityId, entry.getKey())) { 85 | window.isRoot = true; 86 | } else { 87 | window.isRoot = false; 88 | } 89 | } 90 | } 91 | 92 | public void setIgnoreInterceptMaxWindows(String activityId, boolean ignore) { 93 | ActivityWindow window = windows.get(activityId); 94 | if (window != null) { 95 | window.ignoreInterceptMaxWindows = ignore; 96 | } 97 | } 98 | 99 | public boolean isIgnoreInterceptMaxWindows(String activityId) { 100 | ActivityWindow window = windows.get(activityId); 101 | if (window != null) { 102 | return window.ignoreInterceptMaxWindows; 103 | } 104 | return false; 105 | } 106 | 107 | public int getWindowCount() { 108 | return windows.size(); 109 | } 110 | 111 | // Returns ID of the next window after root as Excess window 112 | public String getExcessWindow() { 113 | for (Map.Entry entry : windows.entrySet()) { 114 | ActivityWindow window = entry.getValue(); 115 | if (window.isRoot) continue; 116 | return window.id; 117 | } 118 | return null; 119 | } 120 | 121 | public static class ActivityWindow { 122 | private final String id; 123 | private boolean isRoot; 124 | private int urlLevel; 125 | private int parentUrlLevel; 126 | private boolean ignoreInterceptMaxWindows; 127 | 128 | ActivityWindow(String id, boolean isRoot) { 129 | this.id = id; 130 | this.isRoot = isRoot; 131 | this.urlLevel = -1; 132 | this.parentUrlLevel = -1; 133 | } 134 | 135 | public void setUrlLevels(int urlLevel, int parentUrlLevel) { 136 | this.urlLevel = urlLevel; 137 | this.parentUrlLevel = parentUrlLevel; 138 | } 139 | 140 | @Override 141 | public String toString() { 142 | return "id=" + id + "\n" + 143 | "isRoot=" + isRoot + "\n" + 144 | "urlLevel=" + urlLevel + "\n" + 145 | "parentUrlLevel=" + parentUrlLevel; 146 | } 147 | } 148 | 149 | 150 | interface ExcessWindowsClosedListener { 151 | void onAllExcessWindowClosed(); 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /app/src/main/java/io/gonative/android/IOUtils.java: -------------------------------------------------------------------------------- 1 | package io.gonative.android; 2 | 3 | 4 | import java.io.Closeable; 5 | import java.io.IOException; 6 | import java.io.InputStream; 7 | import java.io.OutputStream; 8 | 9 | import io.gonative.gonative_core.GNLog; 10 | 11 | public class IOUtils { 12 | private static final String TAG = IOUtils.class.getName(); 13 | 14 | public static void copy(InputStream in, OutputStream out) throws IOException{ 15 | byte[] buf = new byte[1024]; 16 | int len; 17 | while ((len = in.read(buf)) > 0) { 18 | out.write(buf, 0, len); 19 | } 20 | } 21 | 22 | public static void close(Closeable c) { 23 | if (c == null) return; 24 | try { 25 | c.close(); 26 | } catch (IOException e){ 27 | GNLog.getInstance().logError(TAG, e.toString(), e); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/io/gonative/android/Installation.java: -------------------------------------------------------------------------------- 1 | package io.gonative.android; 2 | 3 | import android.content.Context; 4 | import android.content.pm.ApplicationInfo; 5 | import android.content.pm.PackageInfo; 6 | import android.content.pm.PackageManager; 7 | import android.os.Build; 8 | import android.telephony.SubscriptionInfo; 9 | import android.telephony.SubscriptionManager; 10 | import android.util.Log; 11 | 12 | import androidx.core.app.ActivityCompat; 13 | 14 | import java.io.File; 15 | import java.io.FileOutputStream; 16 | import java.io.IOException; 17 | import java.io.RandomAccessFile; 18 | import java.util.ArrayList; 19 | import java.util.HashMap; 20 | import java.util.List; 21 | import java.util.Locale; 22 | import java.util.Map; 23 | import java.util.TimeZone; 24 | import java.util.UUID; 25 | 26 | import io.gonative.gonative_core.AppConfig; 27 | import io.gonative.gonative_core.GNLog; 28 | 29 | /** 30 | * Created by weiyin on 8/8/14. 31 | */ 32 | public class Installation { 33 | private static final String TAG = Installation.class.getName(); 34 | 35 | private static String sID = null; 36 | private static final String INSTALLATION = "INSTALLATION"; 37 | 38 | public synchronized static String id(Context context) { 39 | if (sID == null) { 40 | File installation = new File(context.getFilesDir(), INSTALLATION); 41 | try { 42 | if (!installation.exists()) 43 | writeInstallationFile(installation); 44 | sID = readInstallationFile(installation); 45 | } catch (Exception e) { 46 | throw new RuntimeException(e); 47 | } 48 | } 49 | return sID; 50 | } 51 | 52 | public static Map getInfo(Context context) { 53 | HashMap info = new HashMap<>(); 54 | 55 | info.put("platform", "android"); 56 | 57 | String publicKey = AppConfig.getInstance(context).publicKey; 58 | if (publicKey == null) publicKey = ""; 59 | info.put("publicKey", publicKey); 60 | 61 | String packageName = context.getPackageName(); 62 | info.put("appId", packageName); 63 | 64 | 65 | PackageManager manager = context.getPackageManager(); 66 | try { 67 | PackageInfo packageInfo = manager.getPackageInfo(packageName, 0); 68 | info.put("appVersion", packageInfo.versionName); 69 | info.put("appVersionCode", packageInfo.versionCode); 70 | } catch (PackageManager.NameNotFoundException e) { 71 | GNLog.getInstance().logError(TAG, e.getMessage(), e); 72 | } 73 | 74 | String distribution; 75 | boolean isDebuggable = ( 0 != ( context.getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE ) ); 76 | if (isDebuggable) { 77 | distribution = "debug"; 78 | } else { 79 | String installer = manager.getInstallerPackageName(packageName); 80 | if (installer == null) { 81 | distribution = "adhoc"; 82 | } else if (installer.equals("com.android.vending") || installer.equals("com.google.market")) { 83 | distribution = "playstore"; 84 | } else if (installer.equals("com.amazon.venezia")) { 85 | distribution = "amazon"; 86 | } else { 87 | distribution = installer; 88 | } 89 | } 90 | info.put("distribution", distribution); 91 | 92 | info.put("language", Locale.getDefault().getLanguage()); 93 | info.put("os", "Android"); 94 | info.put("osVersion", Build.VERSION.RELEASE); 95 | info.put("model", Build.MANUFACTURER + " " + Build.MODEL); 96 | info.put("hardware", Build.FINGERPRINT); 97 | info.put("timeZone", TimeZone.getDefault().getID()); 98 | info.put("deviceName", getDeviceName()); 99 | 100 | if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP_MR1) { 101 | SubscriptionManager subscriptionManager = SubscriptionManager.from(context); 102 | 103 | if (ActivityCompat.checkSelfPermission(context, android.Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED) { 104 | List carriers = new ArrayList<>(); 105 | for (SubscriptionInfo subscriptionInfo : subscriptionManager.getActiveSubscriptionInfoList()) { 106 | carriers.add(subscriptionInfo.getCarrierName().toString()); 107 | } 108 | info.put("carrierNames", carriers); 109 | try { 110 | info.put("carrierName", carriers.get(0)); 111 | } catch ( IndexOutOfBoundsException e ) { 112 | Log.w(TAG, "getInfo: No carriers registered with subscription manager"); 113 | } 114 | } else { 115 | Log.w(TAG, "getInfo: Cannot get carrierNames, READ_PHONE_STATE not granted"); 116 | } 117 | } 118 | 119 | info.put("installationId", Installation.id(context)); 120 | 121 | return info; 122 | } 123 | 124 | private static String readInstallationFile(File installation) throws IOException { 125 | RandomAccessFile f = new RandomAccessFile(installation, "r"); 126 | byte[] bytes = new byte[(int) f.length()]; 127 | f.readFully(bytes); 128 | f.close(); 129 | return new String(bytes); 130 | } 131 | 132 | private static void writeInstallationFile(File installation) throws IOException { 133 | FileOutputStream out = new FileOutputStream(installation); 134 | String id = UUID.randomUUID().toString(); 135 | out.write(id.getBytes()); 136 | out.close(); 137 | } 138 | 139 | private static String getDeviceName() { 140 | String manufacturer = Build.MANUFACTURER; 141 | String model = Build.MODEL; 142 | String name; 143 | if (model.startsWith(manufacturer)) { 144 | name = model; 145 | } else { 146 | name = manufacturer + " " + model; 147 | } 148 | return name; 149 | } 150 | } -------------------------------------------------------------------------------- /app/src/main/java/io/gonative/android/JsCustomCodeExecutor.java: -------------------------------------------------------------------------------- 1 | package io.gonative.android; 2 | 3 | import org.json.JSONException; 4 | import org.json.JSONObject; 5 | 6 | import java.util.Map; 7 | 8 | import io.gonative.gonative_core.GNLog; 9 | 10 | public class JsCustomCodeExecutor { 11 | private static final String TAG = JsCustomCodeExecutor.class.getName(); 12 | 13 | public static interface CustomCodeHandler { 14 | JSONObject execute(Map params); 15 | } 16 | 17 | // The default CustomCodeHandler "Echo" 18 | // Simply maps all the key/values of the given params into a JSONObject 19 | private static CustomCodeHandler handler = new CustomCodeHandler() { 20 | @Override 21 | public JSONObject execute(Map params) { 22 | if(params != null) { 23 | JSONObject json = new JSONObject(); 24 | try { 25 | for(Map.Entry entry : params.entrySet()) { 26 | json.put(entry.getKey(), entry.getValue()); 27 | } 28 | } 29 | catch(JSONException e) { 30 | GNLog.getInstance().logError(TAG, "Error building custom Json Data", e); 31 | } 32 | return json; 33 | } 34 | return null; 35 | } 36 | }; 37 | 38 | /** 39 | * Set new CustomCodeHandler to override the default "Echo" handler 40 | * @param customHandler 41 | */ 42 | public static void setHandler(CustomCodeHandler customHandler) { 43 | if(customHandler == null) 44 | return; 45 | handler = customHandler; 46 | } 47 | 48 | /** 49 | * Code Handler gets triggered by the UrlNavigation class 50 | * 51 | * @param params A map consisting of all URI parameters and their values 52 | * @return A JSONObject as defined by the Code Handler 53 | * 54 | * @see UrlNavigation#shouldOverrideUrlLoading 55 | */ 56 | public static JSONObject execute(Map params) { 57 | try { 58 | return handler.execute(params); 59 | } catch(Exception e) { 60 | GNLog.getInstance().logError(TAG, "Error executing custom code", e); 61 | return null; 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /app/src/main/java/io/gonative/android/JsResultBridge.java: -------------------------------------------------------------------------------- 1 | package io.gonative.android; 2 | 3 | public class JsResultBridge { 4 | public static String jsResult = ""; 5 | } 6 | -------------------------------------------------------------------------------- /app/src/main/java/io/gonative/android/KeyboardManager.kt: -------------------------------------------------------------------------------- 1 | package io.gonative.android 2 | 3 | import android.graphics.Rect 4 | import android.text.TextUtils 5 | import android.view.ViewGroup 6 | import io.gonative.gonative_core.LeanUtils 7 | import org.json.JSONObject 8 | 9 | 10 | class KeyboardManager(val activity: MainActivity, private val rootLayout: ViewGroup) { 11 | 12 | var callback: String? = "" 13 | private var keyboardWidth = 0 14 | private var keyboardHeight = 0 15 | private var screenWidth = 0 16 | private var screenHeight = 0 17 | private var isKeyboardVisible = false 18 | private var screenHeightOffset = 0 19 | 20 | init { 21 | rootLayout.viewTreeObserver 22 | .addOnGlobalLayoutListener { 23 | val r = Rect() 24 | rootLayout.getWindowVisibleDisplayFrame(r) 25 | 26 | if (screenHeightOffset == 0) { 27 | screenHeightOffset = rootLayout.rootView.height - r.bottom 28 | } 29 | 30 | screenWidth = rootLayout.rootView.width 31 | screenHeight = r.bottom + screenHeightOffset 32 | 33 | keyboardHeight = rootLayout.rootView.height - screenHeight 34 | 35 | if (keyboardHeight == screenHeightOffset) { 36 | keyboardHeight = 0 37 | } 38 | 39 | val visible = keyboardHeight != 0 40 | 41 | if (visible) { 42 | keyboardWidth = screenWidth 43 | if (!isKeyboardVisible) { 44 | isKeyboardVisible = true 45 | notifyCallback(); 46 | } 47 | } else { 48 | keyboardWidth = 0 49 | if (isKeyboardVisible) { 50 | isKeyboardVisible = false 51 | notifyCallback(); 52 | } 53 | } 54 | } 55 | } 56 | 57 | private fun notifyCallback() { 58 | if (TextUtils.isEmpty(callback)) return 59 | activity.runJavascript(LeanUtils.createJsForCallback(callback, getKeyboardData())) 60 | } 61 | 62 | fun getKeyboardData() : JSONObject { 63 | val keyboardWindowSize = JSONObject() 64 | keyboardWindowSize.put("visible", isKeyboardVisible) 65 | keyboardWindowSize.put("width", keyboardWidth) 66 | keyboardWindowSize.put("height", keyboardHeight) 67 | 68 | val visibleWindowSize = JSONObject() 69 | visibleWindowSize.put("width", screenWidth) 70 | visibleWindowSize.put("height", screenHeight) 71 | 72 | val data = JSONObject() 73 | data.put("keyboardWindowSize", keyboardWindowSize) 74 | data.put("visibleWindowSize", visibleWindowSize) 75 | 76 | return data 77 | } 78 | } -------------------------------------------------------------------------------- /app/src/main/java/io/gonative/android/LoginManager.java: -------------------------------------------------------------------------------- 1 | package io.gonative.android; 2 | 3 | import android.content.Context; 4 | import android.os.AsyncTask; 5 | 6 | import org.json.JSONObject; 7 | 8 | import java.lang.ref.WeakReference; 9 | import java.net.HttpURLConnection; 10 | import java.net.URL; 11 | import java.util.List; 12 | import java.util.Observable; 13 | import java.util.regex.Pattern; 14 | 15 | import io.gonative.gonative_core.AppConfig; 16 | import io.gonative.gonative_core.GNLog; 17 | 18 | /** 19 | * Created by weiyin on 3/16/14. 20 | */ 21 | public class LoginManager extends Observable { 22 | private static final String TAG = LoginManager.class.getName(); 23 | 24 | private Context context; 25 | private CheckRedirectionTask task = null; 26 | 27 | private boolean loggedIn = false; 28 | 29 | LoginManager(Context context) { 30 | this.context = context; 31 | checkLogin(); 32 | } 33 | 34 | public void checkLogin() { 35 | if (task != null) 36 | task.cancel(true); 37 | 38 | String loginDetectionUrl = AppConfig.getInstance(context).loginDetectionUrl; 39 | if (loginDetectionUrl == null) { 40 | return; 41 | } 42 | 43 | task = new CheckRedirectionTask(this); 44 | task.execute(AppConfig.getInstance(context).loginDetectionUrl); 45 | } 46 | 47 | public boolean isLoggedIn() { 48 | return loggedIn; 49 | } 50 | 51 | 52 | private static class CheckRedirectionTask extends AsyncTask { 53 | private WeakReference loginManagerReference; 54 | 55 | public CheckRedirectionTask(LoginManager loginManager) { 56 | this.loginManagerReference = new WeakReference<>(loginManager); 57 | } 58 | 59 | @Override 60 | protected String doInBackground(String... urls){ 61 | LoginManager loginManager = loginManagerReference.get(); 62 | if (loginManager == null) return null; 63 | 64 | try { 65 | URL parsedUrl = new URL(urls[0]); 66 | HttpURLConnection connection = null; 67 | boolean wasRedirected; 68 | int numRedirects = 0; 69 | do { 70 | if (connection != null) 71 | connection.disconnect(); 72 | 73 | connection = (HttpURLConnection) parsedUrl.openConnection(); 74 | connection.setInstanceFollowRedirects(true); 75 | connection.setRequestProperty("User-Agent", AppConfig.getInstance(loginManager.context).userAgent); 76 | 77 | connection.connect(); 78 | int responseCode = connection.getResponseCode(); 79 | 80 | if (responseCode == HttpURLConnection.HTTP_MOVED_PERM || 81 | responseCode == HttpURLConnection.HTTP_MOVED_TEMP) { 82 | wasRedirected = true; 83 | parsedUrl = new URL(parsedUrl, connection.getHeaderField("Location")); 84 | numRedirects++; 85 | } else { 86 | wasRedirected = false; 87 | } 88 | } while (!isCancelled() && wasRedirected && numRedirects < 10); 89 | 90 | String finalUrl = connection.getURL().toString(); 91 | connection.disconnect(); 92 | return finalUrl; 93 | 94 | } catch (Exception e) { 95 | GNLog.getInstance().logError(TAG, e.getMessage(), e); 96 | return null; 97 | } 98 | } 99 | 100 | @Override 101 | protected void onPostExecute(String finalUrl) { 102 | LoginManager loginManager = loginManagerReference.get(); 103 | if (loginManager == null) return; 104 | 105 | UrlInspector.getInstance().inspectUrl(finalUrl); 106 | String loginStatus; 107 | 108 | if (finalUrl == null) { 109 | loginManager.loggedIn = false; 110 | loginStatus = "default"; 111 | loginManager.setChanged(); 112 | loginManager.notifyObservers(); 113 | return; 114 | } 115 | 116 | // iterate through loginDetectionRegexes 117 | AppConfig appConfig = AppConfig.getInstance(loginManager.context); 118 | 119 | List regexes = appConfig.loginDetectRegexes; 120 | for (int i = 0; i < regexes.size(); i++) { 121 | Pattern regex = regexes.get(i); 122 | if (regex.matcher(finalUrl).matches()) { 123 | JSONObject entry = appConfig.loginDetectLocations.get(i); 124 | loginManager.loggedIn = entry.optBoolean("loggedIn", false); 125 | 126 | loginStatus = AppConfig.optString(entry, "menuName"); 127 | if (loginStatus == null) loginStatus = loginManager.loggedIn ? "loggedIn" : "default"; 128 | 129 | loginManager.setChanged(); 130 | loginManager.notifyObservers(); 131 | break; 132 | } 133 | } 134 | } 135 | 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /app/src/main/java/io/gonative/android/MySwipeRefreshLayout.java: -------------------------------------------------------------------------------- 1 | package io.gonative.android; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | 6 | import io.gonative.android.widget.GoNativeSwipeRefreshLayout; 7 | 8 | /** 9 | * Created by weiyin on 9/13/15. 10 | * Copyright 2014 GoNative.io LLC 11 | */ 12 | public class MySwipeRefreshLayout extends GoNativeSwipeRefreshLayout { 13 | private CanChildScrollUpCallback canChildScrollUpCallback; 14 | 15 | public interface CanChildScrollUpCallback { 16 | boolean canSwipeRefreshChildScrollUp(); 17 | } 18 | 19 | public MySwipeRefreshLayout(Context context) { 20 | super(context); 21 | } 22 | 23 | public MySwipeRefreshLayout(Context context, AttributeSet attrs) { 24 | super(context, attrs); 25 | } 26 | 27 | public void setCanChildScrollUpCallback(CanChildScrollUpCallback canChildScrollUpCallback) { 28 | this.canChildScrollUpCallback = canChildScrollUpCallback; 29 | } 30 | 31 | @Override 32 | public boolean canChildScrollUp() { 33 | if (canChildScrollUpCallback != null) { 34 | return canChildScrollUpCallback.canSwipeRefreshChildScrollUp(); 35 | } else { 36 | return super.canChildScrollUp(); 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/java/io/gonative/android/ProfilePicker.java: -------------------------------------------------------------------------------- 1 | package io.gonative.android; 2 | 3 | import androidx.annotation.NonNull; 4 | import android.view.View; 5 | import android.view.ViewGroup; 6 | import android.webkit.JavascriptInterface; 7 | import android.widget.AdapterView; 8 | import android.widget.ArrayAdapter; 9 | import android.widget.Spinner; 10 | import android.widget.TextView; 11 | 12 | import org.json.JSONArray; 13 | import org.json.JSONException; 14 | import org.json.JSONObject; 15 | 16 | import java.util.ArrayList; 17 | 18 | import io.gonative.gonative_core.GNLog; 19 | 20 | /** 21 | * Created by weiyin on 5/9/14. 22 | */ 23 | public class ProfilePicker implements AdapterView.OnItemSelectedListener { 24 | private static final String TAG = ProfilePicker.class.getName(); 25 | 26 | private MainActivity mainActivity; 27 | private JSONArray json; 28 | private ArrayList names; 29 | private ArrayList links; 30 | private int selectedIndex; 31 | 32 | private ArrayAdapter adapter; 33 | private Spinner spinner; 34 | private ProfileJsBridge profileJsBridge; 35 | 36 | public ProfilePicker(MainActivity mainActivity, Spinner spinner) { 37 | this.mainActivity = mainActivity; 38 | this.spinner = spinner; 39 | this.names = new ArrayList<>(); 40 | this.links = new ArrayList<>(); 41 | this.spinner.setAdapter(getAdapter()); 42 | this.spinner.setOnItemSelectedListener(this); 43 | this.profileJsBridge = new ProfileJsBridge(); 44 | } 45 | 46 | private void parseJson(String s){ 47 | try { 48 | json = new JSONArray(s); 49 | this.names.clear(); 50 | this.links.clear(); 51 | 52 | for (int i = 0; i < json.length(); i++) { 53 | JSONObject item = json.getJSONObject(i); 54 | 55 | this.names.add(item.optString("name", "")); 56 | this.links.add(item.optString("link", "")); 57 | 58 | if (item.optBoolean("selected", false)){ 59 | selectedIndex = i; 60 | } 61 | } 62 | 63 | mainActivity.runOnUiThread(new Runnable() { 64 | public void run() { 65 | if (selectedIndex < ProfilePicker.this.names.size()) { 66 | ProfilePicker.this.spinner.setSelection(selectedIndex); 67 | } 68 | if (ProfilePicker.this.json != null && 69 | ProfilePicker.this.json.length() > 0) 70 | ProfilePicker.this.spinner.setVisibility(View.VISIBLE); 71 | else 72 | ProfilePicker.this.spinner.setVisibility(View.GONE); 73 | getAdapter().notifyDataSetChanged(); 74 | } 75 | }); 76 | 77 | } catch (JSONException e) { 78 | GNLog.getInstance().logError(TAG, e.getMessage(), e); 79 | } 80 | } 81 | 82 | private ArrayAdapter getAdapter(){ 83 | if (adapter == null) { 84 | 85 | adapter = new ArrayAdapter(mainActivity, R.layout.profile_picker_dropdown, names) { 86 | @NonNull 87 | @Override 88 | public View getView(int position, View convertView, @NonNull ViewGroup parent) { 89 | TextView view = (TextView) super.getView(position, convertView, parent); 90 | view.setTextColor(mainActivity.getResources().getColor(R.color.sidebarForeground)); 91 | return view; 92 | } 93 | 94 | @Override 95 | public View getDropDownView(int position, View convertView, @NonNull ViewGroup parent) { 96 | TextView view = (TextView) super.getDropDownView(position, convertView, parent); 97 | view.setTextColor(mainActivity.getResources().getColor(R.color.sidebarForeground)); 98 | return view; 99 | } 100 | }; 101 | } 102 | 103 | return adapter; 104 | } 105 | 106 | public void onItemSelected(AdapterView parent, View view, int position, long id) { 107 | // only load if selection has changed 108 | if (position != selectedIndex) { 109 | mainActivity.loadUrl(links.get(position)); 110 | mainActivity.closeDrawers(); 111 | selectedIndex = position; 112 | } 113 | } 114 | 115 | public void onNothingSelected(AdapterView parent) { 116 | // do nothing 117 | } 118 | 119 | public ProfileJsBridge getProfileJsBridge() { 120 | return profileJsBridge; 121 | } 122 | 123 | public class ProfileJsBridge { 124 | @JavascriptInterface 125 | public void parseJson(String s) { 126 | ProfilePicker.this.parseJson(s); 127 | } 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /app/src/main/java/io/gonative/android/RegistrationManager.java: -------------------------------------------------------------------------------- 1 | package io.gonative.android; 2 | 3 | import android.content.Context; 4 | import android.os.AsyncTask; 5 | import android.util.Log; 6 | 7 | import org.json.JSONArray; 8 | import org.json.JSONObject; 9 | 10 | import java.io.OutputStreamWriter; 11 | import java.net.HttpURLConnection; 12 | import java.net.URL; 13 | import java.util.HashMap; 14 | import java.util.Iterator; 15 | import java.util.LinkedList; 16 | import java.util.List; 17 | import java.util.Map; 18 | import java.util.regex.Pattern; 19 | 20 | import io.gonative.gonative_core.GNLog; 21 | import io.gonative.gonative_core.LeanUtils; 22 | 23 | /** 24 | * Created by weiyin on 10/4/15. 25 | */ 26 | public class RegistrationManager { 27 | private final static String TAG = RegistrationManager.class.getName(); 28 | 29 | private Context context; 30 | private JSONObject customData; 31 | private String lastUrl; 32 | 33 | private List registrationEndpoints; 34 | 35 | RegistrationManager(Context context) { 36 | this.context = context; 37 | this.registrationEndpoints = new LinkedList<>(); 38 | } 39 | 40 | public void processConfig(JSONArray endpoints) { 41 | registrationEndpoints.clear(); 42 | 43 | if (endpoints == null) return; 44 | 45 | for (int i = 0; i < endpoints.length(); i++) { 46 | JSONObject endpoint = endpoints.optJSONObject(i); 47 | if (endpoint == null) continue; 48 | 49 | String url = LeanUtils.optString(endpoint, "url"); 50 | if (url == null) { 51 | Log.w(TAG, "Invalid registration: endpoint url is null"); 52 | continue; 53 | } 54 | 55 | List urlRegexes = LeanUtils.createRegexArrayFromStrings(endpoint.opt("urlRegex")); 56 | 57 | RegistrationEndpoint registrationEndpoint = new RegistrationEndpoint(url, urlRegexes); 58 | registrationEndpoints.add(registrationEndpoint); 59 | } 60 | } 61 | 62 | public void checkUrl(String url) { 63 | this.lastUrl = url; 64 | for (RegistrationEndpoint endpoint : registrationEndpoints) { 65 | if (LeanUtils.stringMatchesAnyRegex(url, endpoint.urlRegexes)) { 66 | endpoint.sendRegistrationInfo(); 67 | } 68 | } 69 | } 70 | 71 | public void setCustomData(JSONObject customData) { 72 | this.customData = customData; 73 | registrationDataChanged(); 74 | } 75 | 76 | public void sendToAllEndpoints() { 77 | for (RegistrationEndpoint endpoint : registrationEndpoints) { 78 | endpoint.sendRegistrationInfo(); 79 | } 80 | } 81 | 82 | private void registrationDataChanged() { 83 | for (RegistrationEndpoint endpoint : registrationEndpoints) { 84 | if (this.lastUrl != null && 85 | LeanUtils.stringMatchesAnyRegex(this.lastUrl, endpoint.urlRegexes)) { 86 | endpoint.sendRegistrationInfo(); 87 | } 88 | } 89 | } 90 | 91 | public void subscriptionInfoChanged(){ 92 | registrationDataChanged(); 93 | } 94 | 95 | private class RegistrationEndpoint { 96 | private String postUrl; 97 | private List urlRegexes; 98 | 99 | RegistrationEndpoint(String postUrl, List urlRegexes) { 100 | this.postUrl = postUrl; 101 | this.urlRegexes = urlRegexes; 102 | } 103 | 104 | void sendRegistrationInfo() { 105 | new SendRegistrationTask(context, this, RegistrationManager.this).execute(); 106 | } 107 | } 108 | 109 | private static class SendRegistrationTask extends AsyncTask { 110 | private RegistrationEndpoint registrationEndpoint; 111 | private RegistrationManager registrationManager; 112 | private Context context; 113 | 114 | SendRegistrationTask(Context context, RegistrationEndpoint registrationEndpoint, RegistrationManager registrationManager) { 115 | this.registrationEndpoint = registrationEndpoint; 116 | this.registrationManager = registrationManager; 117 | this.context = context; 118 | } 119 | 120 | @Override 121 | protected Void doInBackground(Void... voids) { 122 | Map toSend = new HashMap<>(); 123 | 124 | toSend.putAll(Installation.getInfo(registrationManager.context)); 125 | 126 | // Append provider info to Map toSend 127 | if (((GoNativeApplication) context).getAnalyticsProviderInfo() != null) { 128 | toSend.putAll(((GoNativeApplication) context).getAnalyticsProviderInfo()); 129 | } 130 | 131 | if (registrationManager.customData != null) { 132 | Iterator keys = registrationManager.customData.keys(); 133 | while(keys.hasNext()) { 134 | String key = keys.next(); 135 | toSend.put("customData_" + key, registrationManager.customData.opt(key)); 136 | } 137 | } 138 | 139 | try { 140 | JSONObject json = new JSONObject(toSend); 141 | 142 | URL url = new URL(registrationEndpoint.postUrl); 143 | HttpURLConnection connection = (HttpURLConnection) url.openConnection(); 144 | connection.setRequestMethod("POST"); 145 | connection.setRequestProperty("Content-Type", "application/json"); 146 | connection.setDoOutput(true); 147 | OutputStreamWriter writer = new OutputStreamWriter(connection.getOutputStream(), "UTF-8"); 148 | writer.write(json.toString()); 149 | writer.close(); 150 | connection.connect(); 151 | int result = connection.getResponseCode(); 152 | 153 | if (result < 200 || result > 299) { 154 | Log.w(TAG, "Recevied status code " + result + " when posting to " + registrationEndpoint.postUrl); 155 | } 156 | } catch (Exception e) { 157 | GNLog.getInstance().logError(TAG, "Error posting to " + registrationEndpoint.postUrl, e); 158 | } 159 | 160 | return null; 161 | } 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /app/src/main/java/io/gonative/android/SegmentedController.java: -------------------------------------------------------------------------------- 1 | package io.gonative.android; 2 | 3 | import android.content.BroadcastReceiver; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.content.IntentFilter; 7 | import androidx.localbroadcastmanager.content.LocalBroadcastManager; 8 | import android.view.View; 9 | import android.widget.AdapterView; 10 | import android.widget.ArrayAdapter; 11 | import android.widget.Spinner; 12 | 13 | import org.json.JSONObject; 14 | 15 | import java.util.ArrayList; 16 | 17 | import io.gonative.gonative_core.AppConfig; 18 | 19 | /** 20 | * Created by weiyin on 12/20/15. 21 | * Copyright 2014 GoNative.io LLC 22 | */ 23 | public class SegmentedController implements AdapterView.OnItemSelectedListener { 24 | private MainActivity mainActivity; 25 | private ArrayList labels; 26 | private ArrayList urls; 27 | private int selectedIndex; 28 | 29 | private ArrayAdapter adapter; 30 | private Spinner spinner; 31 | 32 | SegmentedController(MainActivity mainActivity, Spinner spinner) { 33 | this.mainActivity = mainActivity; 34 | this.spinner = spinner; 35 | 36 | this.labels = new ArrayList<>(); 37 | this.urls = new ArrayList<>(); 38 | 39 | this.spinner.setAdapter(getAdapter()); 40 | this.spinner.setOnItemSelectedListener(this); 41 | 42 | BroadcastReceiver messageReceiver = new BroadcastReceiver() { 43 | @Override 44 | public void onReceive(Context context, Intent intent) { 45 | if (intent == null || intent.getAction() == null) return; 46 | 47 | if (intent.getAction().equals(AppConfig.PROCESSED_SEGMENTED_CONTROL)) { 48 | updateSegmentedControl(); 49 | } 50 | } 51 | }; 52 | LocalBroadcastManager.getInstance(this.mainActivity).registerReceiver( 53 | messageReceiver, new IntentFilter(AppConfig.PROCESSED_SEGMENTED_CONTROL)); 54 | 55 | updateSegmentedControl(); 56 | } 57 | 58 | private void updateSegmentedControl() { 59 | this.labels.clear(); 60 | this.urls.clear(); 61 | this.selectedIndex = -1; 62 | 63 | AppConfig appConfig = AppConfig.getInstance(mainActivity); 64 | if (appConfig.segmentedControl == null) return; 65 | 66 | for (int i = 0; i < appConfig.segmentedControl.size(); i++) { 67 | JSONObject item = appConfig.segmentedControl.get(i); 68 | 69 | String label = item.optString("label", "Invalid"); 70 | String url = item.optString("url", ""); 71 | Boolean selected = item.optBoolean("selected"); 72 | 73 | this.labels.add(i, label); 74 | this.urls.add(i, url); 75 | if (selected) this.selectedIndex = i; 76 | } 77 | 78 | mainActivity.runOnUiThread(new Runnable() { 79 | @Override 80 | public void run() { 81 | if (selectedIndex > -1) { 82 | spinner.setSelection(selectedIndex); 83 | } 84 | 85 | if (labels.size() > 0) { 86 | spinner.setVisibility(View.VISIBLE); 87 | } else { 88 | spinner.setVisibility(View.GONE); 89 | } 90 | 91 | adapter.notifyDataSetChanged(); 92 | } 93 | }); 94 | 95 | } 96 | 97 | private ArrayAdapter getAdapter() { 98 | if (this.adapter != null) { 99 | return this.adapter; 100 | } 101 | 102 | ArrayAdapter adapter = new ArrayAdapter<>(mainActivity, 103 | android.R.layout.simple_spinner_item, labels); 104 | adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); 105 | this.adapter = adapter; 106 | return adapter; 107 | } 108 | 109 | @Override 110 | public void onItemSelected(AdapterView parent, View view, int position, long id) { 111 | // only load if selection has changed 112 | if (position != selectedIndex) { 113 | String url = urls.get(position); 114 | 115 | if (url != null && url.length() > 0) { 116 | mainActivity.loadUrl(url); 117 | } 118 | 119 | mainActivity.closeDrawers(); 120 | selectedIndex = position; 121 | } 122 | } 123 | 124 | @Override 125 | public void onNothingSelected(AdapterView parent) { 126 | // do nothing 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /app/src/main/java/io/gonative/android/ShakeDialogFragment.java: -------------------------------------------------------------------------------- 1 | package io.gonative.android; 2 | 3 | import android.app.AlertDialog; 4 | import android.app.Dialog; 5 | import android.content.Context; 6 | import android.content.DialogInterface; 7 | import android.os.Bundle; 8 | import androidx.annotation.NonNull; 9 | import androidx.annotation.Nullable; 10 | import androidx.fragment.app.DialogFragment; 11 | 12 | public class ShakeDialogFragment extends DialogFragment { 13 | public interface ShakeDialogListener { 14 | public void onClearCache(DialogFragment dialog); 15 | } 16 | 17 | ShakeDialogListener listener; 18 | 19 | @NonNull 20 | @Override 21 | public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { 22 | AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); 23 | builder.setTitle(R.string.shake_to_clear_cache) 24 | .setItems(R.array.device_shaken_options, new DialogInterface.OnClickListener() { 25 | @Override 26 | public void onClick(DialogInterface dialogInterface, int i) { 27 | if (i == 0) { 28 | listener.onClearCache(ShakeDialogFragment.this); 29 | } 30 | } 31 | }); 32 | return builder.create(); 33 | } 34 | 35 | @Override 36 | public void onAttach(Context context) { 37 | super.onAttach(context); 38 | try { 39 | listener = (ShakeDialogListener) context; 40 | } catch (ClassCastException e) { 41 | throw new ClassCastException(context.toString() + " must implement ShakeDialogListener"); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/src/main/java/io/gonative/android/SplashActivity.java: -------------------------------------------------------------------------------- 1 | package io.gonative.android; 2 | 3 | import android.Manifest; 4 | import android.content.Intent; 5 | import android.content.pm.PackageManager; 6 | import android.graphics.Color; 7 | import android.os.Bundle; 8 | 9 | import android.os.Handler; 10 | import android.os.Looper; 11 | import android.view.View; 12 | 13 | import androidx.annotation.NonNull; 14 | import androidx.core.app.ActivityCompat; 15 | import androidx.appcompat.app.AppCompatActivity; 16 | 17 | import java.util.HashSet; 18 | 19 | import androidx.core.content.ContextCompat; 20 | import androidx.core.splashscreen.SplashScreen; 21 | 22 | import io.gonative.gonative_core.AppConfig; 23 | 24 | public class SplashActivity extends AppCompatActivity { 25 | private static final int REQUEST_STARTUP_PERMISSIONS = 100; 26 | 27 | @Override 28 | protected void onCreate(Bundle savedInstanceState) { 29 | SplashScreen.installSplashScreen(this); 30 | super.onCreate(savedInstanceState); 31 | AppConfig config = AppConfig.getInstance(this); 32 | 33 | getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION); 34 | getWindow().setStatusBarColor(Color.TRANSPARENT); 35 | getWindow().setNavigationBarColor(Color.TRANSPARENT); 36 | setContentView(R.layout.splash_screen); 37 | 38 | HashSet permissionsToRequest = new HashSet<>(); 39 | 40 | if (LeanWebView.isCrosswalk()) { 41 | if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED){ 42 | permissionsToRequest.add(Manifest.permission.READ_CONTACTS); 43 | } 44 | if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_CONTACTS) != PackageManager.PERMISSION_GRANTED){ 45 | permissionsToRequest.add(Manifest.permission.WRITE_CONTACTS); 46 | } 47 | } 48 | 49 | if (permissionsToRequest.isEmpty()) { 50 | handleSplash(config); 51 | } else { 52 | ActivityCompat.requestPermissions(this, 53 | permissionsToRequest.toArray(new String[]{}), 54 | REQUEST_STARTUP_PERMISSIONS); 55 | } 56 | } 57 | 58 | @Override 59 | public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { 60 | super.onRequestPermissionsResult(requestCode, permissions, grantResults); 61 | startMainActivity(); 62 | } 63 | 64 | private void handleSplash(AppConfig config){ 65 | // Check app if unlicensed and show banner 66 | if (!config.isLicensed()) { 67 | findViewById(R.id.banner_text).setVisibility(View.VISIBLE); 68 | } 69 | 70 | Handler handler = new Handler(Looper.getMainLooper()); 71 | 72 | // handle splash 73 | double delay = 1.5; // in seconds 74 | double forceTime = config.showSplashForceTime; 75 | double maxTime = config.showSplashMaxTime; 76 | if (forceTime > 0) { 77 | delay = forceTime; 78 | } else if (maxTime > 0) { 79 | delay = maxTime; 80 | } 81 | handler.postDelayed(this::startMainActivity, (long)delay * 1000); 82 | } 83 | 84 | private void startMainActivity() { 85 | Intent intent = new Intent(this, MainActivity.class); 86 | // Make MainActivity think it was started from launcher 87 | intent.addCategory(Intent.CATEGORY_LAUNCHER); 88 | intent.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); 89 | startActivity(intent); 90 | finish(); 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /app/src/main/java/io/gonative/android/UrlInspector.java: -------------------------------------------------------------------------------- 1 | package io.gonative.android; 2 | 3 | import android.content.Context; 4 | 5 | import java.util.regex.Matcher; 6 | import java.util.regex.Pattern; 7 | import java.util.regex.PatternSyntaxException; 8 | 9 | import io.gonative.gonative_core.AppConfig; 10 | import io.gonative.gonative_core.GNLog; 11 | 12 | /** 13 | * Created by weiyin on 4/28/14. 14 | */ 15 | public class UrlInspector { 16 | private static final String TAG = UrlInspector.class.getName(); 17 | // singleton 18 | private static UrlInspector instance = null; 19 | 20 | private Pattern userIdRegex = null; 21 | 22 | private String userId = null; 23 | 24 | 25 | public static UrlInspector getInstance(){ 26 | if (instance == null) { 27 | instance = new UrlInspector(); 28 | } 29 | return instance; 30 | } 31 | 32 | public void init(Context context) { 33 | String regexString = AppConfig.getInstance(context).userIdRegex; 34 | if (regexString != null && !regexString.isEmpty()) { 35 | try { 36 | userIdRegex = Pattern.compile(regexString); 37 | } catch (PatternSyntaxException e) { 38 | GNLog.getInstance().logError(TAG, e.getMessage(), e); 39 | } 40 | } 41 | } 42 | 43 | private UrlInspector() { 44 | // prevent direct instantiation 45 | } 46 | 47 | public void inspectUrl(String url) { 48 | if (userIdRegex != null) { 49 | Matcher matcher = userIdRegex.matcher(url); 50 | if (matcher.groupCount() > 0 && matcher.find()) { 51 | setUserId(matcher.group(1)); 52 | } 53 | } 54 | } 55 | 56 | public String getUserId() { 57 | return userId; 58 | } 59 | 60 | private void setUserId(String userId) { 61 | this.userId = userId; 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /app/src/main/java/io/gonative/android/WebViewPoolDisownPolicy.java: -------------------------------------------------------------------------------- 1 | package io.gonative.android; 2 | 3 | /** 4 | * Created by weiyin on 9/3/14. 5 | */ 6 | public enum WebViewPoolDisownPolicy { 7 | Always, Reload, Never; 8 | 9 | public static WebViewPoolDisownPolicy defaultPolicy = WebViewPoolDisownPolicy.Reload; 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/java/io/gonative/android/files/CapturedImageSaver.kt: -------------------------------------------------------------------------------- 1 | package io.gonative.android.files 2 | 3 | import android.content.ContentResolver 4 | import android.content.ContentValues 5 | import android.content.Context 6 | import android.net.Uri 7 | import android.os.Build 8 | import android.os.Environment 9 | import android.provider.MediaStore 10 | import androidx.core.content.FileProvider 11 | import java.io.File 12 | import java.text.SimpleDateFormat 13 | import java.util.Date 14 | import java.util.Locale 15 | 16 | 17 | class CapturedImageSaver { 18 | fun saveCapturedBitmap(context: Context, bitmapUri: Uri): Uri? { 19 | val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(Date()) 20 | val imageFileName = "IMG_$timeStamp.jpg" 21 | 22 | val resolver: ContentResolver = context.contentResolver 23 | val currentUri = if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) { 24 | val contentValues = ContentValues() 25 | contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, imageFileName) 26 | contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "image/*") 27 | contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES) 28 | resolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues) 29 | } else { 30 | val storageDir = Environment.getExternalStoragePublicDirectory( 31 | Environment.DIRECTORY_PICTURES) 32 | val captureFile = File(storageDir, imageFileName) 33 | FileProvider.getUriForFile(context, context.applicationContext.packageName + ".fileprovider", captureFile); 34 | } 35 | 36 | currentUri?.let { 37 | context.contentResolver.openOutputStream(it).use { output -> 38 | context.contentResolver.openInputStream(bitmapUri).use { input -> 39 | output?.write(input?.readBytes()) 40 | } 41 | } 42 | } 43 | 44 | return currentUri 45 | } 46 | } -------------------------------------------------------------------------------- /app/src/main/java/io/gonative/android/widget/CircleImageView.java: -------------------------------------------------------------------------------- 1 | package io.gonative.android.widget; 2 | 3 | import android.content.Context; 4 | import android.graphics.Canvas; 5 | import android.graphics.Color; 6 | import android.graphics.Paint; 7 | import android.graphics.RadialGradient; 8 | import android.graphics.Shader; 9 | import android.graphics.drawable.ShapeDrawable; 10 | import android.graphics.drawable.shapes.OvalShape; 11 | import android.view.View; 12 | import android.view.animation.Animation; 13 | 14 | import androidx.core.content.ContextCompat; 15 | import androidx.core.view.ViewCompat; 16 | 17 | /** 18 | * Private class created to work around issues with AnimationListeners being 19 | * called before the animation is actually complete and support shadows on older 20 | * platforms. 21 | */ 22 | class CircleImageView extends androidx.appcompat.widget.AppCompatImageView { 23 | 24 | private static final int KEY_SHADOW_COLOR = 0x1E000000; 25 | private static final int FILL_SHADOW_COLOR = 0x3D000000; 26 | // PX 27 | private static final float X_OFFSET = 0f; 28 | private static final float Y_OFFSET = 1.75f; 29 | private static final float SHADOW_RADIUS = 3.5f; 30 | private static final int SHADOW_ELEVATION = 4; 31 | 32 | private Animation.AnimationListener mListener; 33 | int mShadowRadius; 34 | 35 | CircleImageView(Context context, int color) { 36 | super(context); 37 | final float density = getContext().getResources().getDisplayMetrics().density; 38 | final int shadowYOffset = (int) (density * Y_OFFSET); 39 | final int shadowXOffset = (int) (density * X_OFFSET); 40 | 41 | mShadowRadius = (int) (density * SHADOW_RADIUS); 42 | 43 | ShapeDrawable circle; 44 | if (elevationSupported()) { 45 | circle = new ShapeDrawable(new OvalShape()); 46 | ViewCompat.setElevation(this, SHADOW_ELEVATION * density); 47 | } else { 48 | OvalShape oval = new CircleImageView.OvalShadow(mShadowRadius); 49 | circle = new ShapeDrawable(oval); 50 | setLayerType(View.LAYER_TYPE_SOFTWARE, circle.getPaint()); 51 | circle.getPaint().setShadowLayer(mShadowRadius, shadowXOffset, shadowYOffset, 52 | KEY_SHADOW_COLOR); 53 | final int padding = mShadowRadius; 54 | // set padding so the inner image sits correctly within the shadow. 55 | setPadding(padding, padding, padding, padding); 56 | } 57 | circle.getPaint().setColor(color); 58 | ViewCompat.setBackground(this, circle); 59 | } 60 | 61 | private boolean elevationSupported() { 62 | return android.os.Build.VERSION.SDK_INT >= 21; 63 | } 64 | 65 | @Override 66 | protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 67 | super.onMeasure(widthMeasureSpec, heightMeasureSpec); 68 | if (!elevationSupported()) { 69 | setMeasuredDimension(getMeasuredWidth() + mShadowRadius * 2, getMeasuredHeight() 70 | + mShadowRadius * 2); 71 | } 72 | } 73 | 74 | public void setAnimationListener(Animation.AnimationListener listener) { 75 | mListener = listener; 76 | } 77 | 78 | @Override 79 | public void onAnimationStart() { 80 | super.onAnimationStart(); 81 | if (mListener != null) { 82 | mListener.onAnimationStart(getAnimation()); 83 | } 84 | } 85 | 86 | @Override 87 | public void onAnimationEnd() { 88 | super.onAnimationEnd(); 89 | if (mListener != null) { 90 | mListener.onAnimationEnd(getAnimation()); 91 | } 92 | } 93 | 94 | /** 95 | * Update the background color of the circle image view. 96 | * 97 | * @param colorRes Id of a color resource. 98 | */ 99 | public void setBackgroundColorRes(int colorRes) { 100 | setBackgroundColor(ContextCompat.getColor(getContext(), colorRes)); 101 | } 102 | 103 | @Override 104 | public void setBackgroundColor(int color) { 105 | if (getBackground() instanceof ShapeDrawable) { 106 | ((ShapeDrawable) getBackground()).getPaint().setColor(color); 107 | } 108 | } 109 | 110 | private class OvalShadow extends OvalShape { 111 | private RadialGradient mRadialGradient; 112 | private Paint mShadowPaint; 113 | 114 | OvalShadow(int shadowRadius) { 115 | super(); 116 | mShadowPaint = new Paint(); 117 | mShadowRadius = shadowRadius; 118 | updateRadialGradient((int) rect().width()); 119 | } 120 | 121 | @Override 122 | protected void onResize(float width, float height) { 123 | super.onResize(width, height); 124 | updateRadialGradient((int) width); 125 | } 126 | 127 | @Override 128 | public void draw(Canvas canvas, Paint paint) { 129 | final int viewWidth = CircleImageView.this.getWidth(); 130 | final int viewHeight = CircleImageView.this.getHeight(); 131 | canvas.drawCircle(viewWidth / 2, viewHeight / 2, viewWidth / 2, mShadowPaint); 132 | canvas.drawCircle(viewWidth / 2, viewHeight / 2, viewWidth / 2 - mShadowRadius, paint); 133 | } 134 | 135 | private void updateRadialGradient(int diameter) { 136 | mRadialGradient = new RadialGradient(diameter / 2, diameter / 2, 137 | mShadowRadius, new int[] { FILL_SHADOW_COLOR, Color.TRANSPARENT }, 138 | null, Shader.TileMode.CLAMP); 139 | mShadowPaint.setShader(mRadialGradient); 140 | } 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /app/src/main/java/io/gonative/android/widget/GoNativeDrawerLayout.java: -------------------------------------------------------------------------------- 1 | package io.gonative.android.widget; 2 | 3 | import android.content.Context; 4 | import android.util.AttributeSet; 5 | import android.util.Log; 6 | import android.view.MotionEvent; 7 | 8 | import androidx.annotation.NonNull; 9 | import androidx.annotation.Nullable; 10 | import androidx.drawerlayout.widget.DrawerLayout; 11 | 12 | public class GoNativeDrawerLayout extends DrawerLayout { 13 | 14 | private boolean disableTouch = false; 15 | 16 | public GoNativeDrawerLayout(@NonNull Context context) { 17 | this(context, null); 18 | } 19 | 20 | public GoNativeDrawerLayout(@NonNull Context context, @Nullable AttributeSet attrs) { 21 | this(context, attrs, 0); 22 | } 23 | 24 | public GoNativeDrawerLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyle) { 25 | super(context, attrs, defStyle); 26 | } 27 | 28 | @Override 29 | public boolean onInterceptTouchEvent(MotionEvent ev) { 30 | if (disableTouch) { 31 | Log.d("SWIPE", "GNDrawerLayout disabled touch"); 32 | return false; 33 | } 34 | return super.onInterceptTouchEvent(ev); 35 | } 36 | 37 | 38 | @Override 39 | public boolean onTouchEvent(MotionEvent ev) { 40 | if (disableTouch) { 41 | Log.d("SWIPE", "GNDrawerLayout disabled touch"); 42 | return false; 43 | } 44 | return super.onTouchEvent(ev); 45 | } 46 | 47 | public void setDisableTouch(boolean disableTouch){ 48 | this.disableTouch = disableTouch; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/src/main/java/io/gonative/android/widget/HandleView.kt: -------------------------------------------------------------------------------- 1 | package io.gonative.android.widget 2 | 3 | import android.animation.ArgbEvaluator 4 | import android.animation.ValueAnimator 5 | import android.content.Context 6 | import android.graphics.Color 7 | import android.graphics.PorterDuff 8 | import android.graphics.drawable.Drawable 9 | import android.util.AttributeSet 10 | import android.widget.ImageView 11 | import android.widget.RelativeLayout 12 | import android.widget.TextView 13 | import androidx.annotation.ColorInt 14 | import androidx.core.content.res.ResourcesCompat 15 | import io.gonative.android.R 16 | 17 | class HandleView : RelativeLayout { 18 | private val iconView: ImageView 19 | private val textView: TextView 20 | 21 | init { 22 | inflate(context, R.layout.view_handle, this) 23 | iconView = findViewById(R.id.icon) 24 | textView = findViewById(R.id.text) 25 | } 26 | 27 | @JvmOverloads 28 | constructor( 29 | context: Context, 30 | attrs: AttributeSet? = null, 31 | defStyle: Int = 0 32 | ) : super(context, attrs, defStyle) { 33 | context.theme.obtainStyledAttributes(attrs, R.styleable.HandleView, 0, 0).apply { 34 | val backgroundDrawable = getDrawable(R.styleable.HandleView_handleBackground) 35 | ?: ResourcesCompat.getDrawable( 36 | resources, 37 | R.drawable.shape_rounded, 38 | context.theme 39 | ) 40 | val iconDrawable = getDrawable(R.styleable.HandleView_iconDrawable) 41 | val text = getString(R.styleable.HandleView_text) 42 | val inactiveColor = getColor(R.styleable.HandleView_inactiveColor, inactiveColor) 43 | val activeColor = getColor(R.styleable.HandleView_activeColor, activeColor) 44 | initView(backgroundDrawable, iconDrawable, text, inactiveColor, activeColor) 45 | } 46 | } 47 | 48 | constructor( 49 | context: Context, 50 | backgroundDrawable: Drawable?, 51 | iconDrawable: Drawable?, 52 | text: String?, 53 | @ColorInt inactiveColor: Int, 54 | @ColorInt activeColor: Int, 55 | ) : super(context, null, 0) { 56 | initView(backgroundDrawable, iconDrawable, text, inactiveColor, activeColor) 57 | } 58 | 59 | var maxTextWidth: Int = Int.MIN_VALUE 60 | var inactiveColor: Int = Color.WHITE 61 | var activeColor: Int = Color.WHITE 62 | 63 | fun initView( 64 | backgroundDrawable: Drawable?, 65 | iconDrawable: Drawable?, 66 | text: String?, 67 | @ColorInt inactiveColor: Int, 68 | @ColorInt activeColor: Int 69 | ) { 70 | background = backgroundDrawable 71 | iconView.setImageDrawable(iconDrawable) 72 | setText(text) 73 | textView.layoutParams.let { 74 | it.width = 0 75 | textView.layoutParams = it 76 | } 77 | 78 | this.inactiveColor = inactiveColor 79 | this.activeColor = activeColor 80 | iconView.setColorFilter(inactiveColor) 81 | } 82 | 83 | fun setText(text: String?) { 84 | textView.layoutParams.width = LayoutParams.WRAP_CONTENT 85 | textView.text = text 86 | textView.measure( 87 | MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), 88 | MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED) 89 | ) 90 | maxTextWidth = textView.measuredWidth 91 | textView.layoutParams.let { 92 | it.width = 0 93 | textView.layoutParams = it 94 | } 95 | } 96 | 97 | fun animateShowText() { 98 | if (textView.text.isEmpty()) { 99 | return 100 | } 101 | if (textView.layoutParams.width != 0) { 102 | textView.layoutParams.let { 103 | it.width = 0 104 | textView.layoutParams = it 105 | } 106 | } 107 | val animator = ValueAnimator.ofInt(0, maxTextWidth) 108 | animator.addUpdateListener { anim -> 109 | val value = anim.animatedValue as Int 110 | textView.layoutParams.let { 111 | it.width = value 112 | textView.layoutParams = it 113 | } 114 | } 115 | animator.duration = 300 116 | animator.start() 117 | } 118 | 119 | fun animateHideText() { 120 | if (textView.text.isEmpty()) { 121 | return 122 | } 123 | val animator = ValueAnimator.ofInt(maxTextWidth, 0) 124 | animator.duration = 300 125 | animator.addUpdateListener { anim -> 126 | val value = anim.animatedValue as Int 127 | val params = textView.layoutParams 128 | params.width = value 129 | textView.layoutParams = params 130 | } 131 | animator.start() 132 | } 133 | 134 | fun animateActive() { 135 | val animator = ValueAnimator.ofObject(ArgbEvaluator(), inactiveColor, activeColor) 136 | animator.addUpdateListener { anim -> 137 | val color = anim.animatedValue as Int 138 | textView.setTextColor(color) 139 | iconView.setColorFilter(color, PorterDuff.Mode.SRC_IN) 140 | } 141 | animator.duration = 100 142 | animator.start() 143 | } 144 | 145 | fun animateInactive() { 146 | val animator = ValueAnimator.ofObject(ArgbEvaluator(), activeColor, inactiveColor) 147 | animator.addUpdateListener { anim -> 148 | val color = anim.animatedValue as Int 149 | textView.setTextColor(color) 150 | iconView.setColorFilter(color, PorterDuff.Mode.SRC_IN) 151 | } 152 | animator.duration = 200 153 | animator.start() 154 | } 155 | } -------------------------------------------------------------------------------- /app/src/main/java/io/gonative/android/widget/WebViewContainerView.java: -------------------------------------------------------------------------------- 1 | package io.gonative.android.widget; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.util.AttributeSet; 6 | import android.view.ViewGroup; 7 | import android.widget.FrameLayout; 8 | 9 | import androidx.annotation.NonNull; 10 | import androidx.annotation.Nullable; 11 | 12 | import java.lang.reflect.Constructor; 13 | import java.lang.reflect.InvocationTargetException; 14 | import java.lang.reflect.Method; 15 | 16 | import io.gonative.android.GoNativeApplication; 17 | import io.gonative.android.LeanWebView; 18 | import io.gonative.android.MainActivity; 19 | import io.gonative.android.WebViewSetup; 20 | import io.gonative.gonative_core.AppConfig; 21 | import io.gonative.gonative_core.Bridge; 22 | import io.gonative.gonative_core.GoNativeWebviewInterface; 23 | 24 | public class WebViewContainerView extends FrameLayout { 25 | 26 | private ViewGroup webview; 27 | private boolean isGecko = false; 28 | 29 | public WebViewContainerView(@NonNull Context context) { 30 | super(context); 31 | } 32 | 33 | public WebViewContainerView(@NonNull Context context, @Nullable AttributeSet attrs) { 34 | super(context, attrs); 35 | initializeWebview(context); 36 | } 37 | 38 | public WebViewContainerView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) { 39 | super(context, attrs, defStyleAttr); 40 | initializeWebview(context); 41 | } 42 | 43 | private void initializeWebview(Context context) { 44 | AppConfig appConfig = AppConfig.getInstance(context); 45 | 46 | if (appConfig.geckoViewEnabled) { 47 | try { 48 | Class classGecko = Class.forName("io.gonative.plugins.android.gecko.GNGeckoView"); 49 | Constructor consGecko = classGecko.getConstructor(Context.class); 50 | webview = (ViewGroup) consGecko.newInstance(context); 51 | this.isGecko = true; 52 | } catch (Exception e) { 53 | e.printStackTrace(); 54 | } 55 | } else { 56 | webview = new LeanWebView(context); 57 | } 58 | ViewGroup.LayoutParams layoutParams = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT); 59 | webview.setLayoutParams(layoutParams); 60 | this.addView(webview); 61 | } 62 | 63 | public void setupWebview(MainActivity activity, boolean isRoot) { 64 | if (isGecko) { 65 | try { 66 | Class geckoSetupClass = Class.forName("io.gonative.plugins.android.gecko.WebViewSetup"); 67 | Method setupWebview = geckoSetupClass.getMethod("setupWebviewForActivity", Activity.class, GoNativeWebviewInterface.class, Bridge.class, boolean.class); 68 | setupWebview.invoke(geckoSetupClass, activity, (GoNativeWebviewInterface) webview, ((GoNativeApplication) activity.getApplication()).mBridge, isRoot); 69 | } catch (ClassNotFoundException | NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { 70 | e.printStackTrace(); 71 | } 72 | } else { 73 | WebViewSetup.setupWebviewForActivity(getWebview(), activity); 74 | } 75 | } 76 | 77 | public GoNativeWebviewInterface getWebview() { 78 | return (GoNativeWebviewInterface) webview; 79 | } 80 | 81 | } 82 | -------------------------------------------------------------------------------- /app/src/main/res/anim/fast_fade_out.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/drawer_shadow.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/250d90c9f09660f8a9920751e8220a9918fdb14c/app/src/main/res/drawable-hdpi/drawer_shadow.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_actionbar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/250d90c9f09660f8a9920751e8220a9918fdb14c/app/src/main/res/drawable-hdpi/ic_actionbar.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_chevron_down_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/250d90c9f09660f8a9920751e8220a9918fdb14c/app/src/main/res/drawable-hdpi/ic_chevron_down_light.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_chevron_up_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/250d90c9f09660f8a9920751e8220a9918fdb14c/app/src/main/res/drawable-hdpi/ic_chevron_up_light.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/250d90c9f09660f8a9920751e8220a9918fdb14c/app/src/main/res/drawable-hdpi/ic_notification.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_refresh_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/250d90c9f09660f8a9920751e8220a9918fdb14c/app/src/main/res/drawable-hdpi/ic_refresh_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_refresh_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/250d90c9f09660f8a9920751e8220a9918fdb14c/app/src/main/res/drawable-hdpi/ic_refresh_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_search_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/250d90c9f09660f8a9920751e8220a9918fdb14c/app/src/main/res/drawable-hdpi/ic_search_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_search_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/250d90c9f09660f8a9920751e8220a9918fdb14c/app/src/main/res/drawable-hdpi/ic_search_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_share_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/250d90c9f09660f8a9920751e8220a9918fdb14c/app/src/main/res/drawable-hdpi/ic_share_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_share_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/250d90c9f09660f8a9920751e8220a9918fdb14c/app/src/main/res/drawable-hdpi/ic_share_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/splash.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/250d90c9f09660f8a9920751e8220a9918fdb14c/app/src/main/res/drawable-hdpi/splash.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/drawer_shadow.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/250d90c9f09660f8a9920751e8220a9918fdb14c/app/src/main/res/drawable-mdpi/drawer_shadow.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_actionbar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/250d90c9f09660f8a9920751e8220a9918fdb14c/app/src/main/res/drawable-mdpi/ic_actionbar.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_chevron_down_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/250d90c9f09660f8a9920751e8220a9918fdb14c/app/src/main/res/drawable-mdpi/ic_chevron_down_light.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_chevron_up_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/250d90c9f09660f8a9920751e8220a9918fdb14c/app/src/main/res/drawable-mdpi/ic_chevron_up_light.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/250d90c9f09660f8a9920751e8220a9918fdb14c/app/src/main/res/drawable-mdpi/ic_notification.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_refresh_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/250d90c9f09660f8a9920751e8220a9918fdb14c/app/src/main/res/drawable-mdpi/ic_refresh_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_refresh_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/250d90c9f09660f8a9920751e8220a9918fdb14c/app/src/main/res/drawable-mdpi/ic_refresh_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_search_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/250d90c9f09660f8a9920751e8220a9918fdb14c/app/src/main/res/drawable-mdpi/ic_search_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_search_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/250d90c9f09660f8a9920751e8220a9918fdb14c/app/src/main/res/drawable-mdpi/ic_search_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_share_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/250d90c9f09660f8a9920751e8220a9918fdb14c/app/src/main/res/drawable-mdpi/ic_share_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_share_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/250d90c9f09660f8a9920751e8220a9918fdb14c/app/src/main/res/drawable-mdpi/ic_share_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/splash.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/250d90c9f09660f8a9920751e8220a9918fdb14c/app/src/main/res/drawable-mdpi/splash.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-night-hdpi/ic_actionbar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/250d90c9f09660f8a9920751e8220a9918fdb14c/app/src/main/res/drawable-night-hdpi/ic_actionbar.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-night-hdpi/splash.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/250d90c9f09660f8a9920751e8220a9918fdb14c/app/src/main/res/drawable-night-hdpi/splash.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-night-mdpi/ic_actionbar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/250d90c9f09660f8a9920751e8220a9918fdb14c/app/src/main/res/drawable-night-mdpi/ic_actionbar.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-night-mdpi/splash.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/250d90c9f09660f8a9920751e8220a9918fdb14c/app/src/main/res/drawable-night-mdpi/splash.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-night-xhdpi/ic_actionbar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/250d90c9f09660f8a9920751e8220a9918fdb14c/app/src/main/res/drawable-night-xhdpi/ic_actionbar.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-night-xhdpi/splash.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/250d90c9f09660f8a9920751e8220a9918fdb14c/app/src/main/res/drawable-night-xhdpi/splash.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-night-xxhdpi/ic_actionbar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/250d90c9f09660f8a9920751e8220a9918fdb14c/app/src/main/res/drawable-night-xxhdpi/ic_actionbar.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-night-xxhdpi/splash.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/250d90c9f09660f8a9920751e8220a9918fdb14c/app/src/main/res/drawable-night-xxhdpi/splash.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-night-xxxhdpi/ic_actionbar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/250d90c9f09660f8a9920751e8220a9918fdb14c/app/src/main/res/drawable-night-xxxhdpi/ic_actionbar.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-night-xxxhdpi/splash.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/250d90c9f09660f8a9920751e8220a9918fdb14c/app/src/main/res/drawable-night-xxxhdpi/splash.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/drawer_shadow.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/250d90c9f09660f8a9920751e8220a9918fdb14c/app/src/main/res/drawable-xhdpi/drawer_shadow.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_actionbar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/250d90c9f09660f8a9920751e8220a9918fdb14c/app/src/main/res/drawable-xhdpi/ic_actionbar.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_chevron_down_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/250d90c9f09660f8a9920751e8220a9918fdb14c/app/src/main/res/drawable-xhdpi/ic_chevron_down_light.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_chevron_up_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/250d90c9f09660f8a9920751e8220a9918fdb14c/app/src/main/res/drawable-xhdpi/ic_chevron_up_light.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/250d90c9f09660f8a9920751e8220a9918fdb14c/app/src/main/res/drawable-xhdpi/ic_notification.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_refresh_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/250d90c9f09660f8a9920751e8220a9918fdb14c/app/src/main/res/drawable-xhdpi/ic_refresh_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_refresh_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/250d90c9f09660f8a9920751e8220a9918fdb14c/app/src/main/res/drawable-xhdpi/ic_refresh_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_search_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/250d90c9f09660f8a9920751e8220a9918fdb14c/app/src/main/res/drawable-xhdpi/ic_search_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_search_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/250d90c9f09660f8a9920751e8220a9918fdb14c/app/src/main/res/drawable-xhdpi/ic_search_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_share_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/250d90c9f09660f8a9920751e8220a9918fdb14c/app/src/main/res/drawable-xhdpi/ic_share_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_share_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/250d90c9f09660f8a9920751e8220a9918fdb14c/app/src/main/res/drawable-xhdpi/ic_share_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/splash.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/250d90c9f09660f8a9920751e8220a9918fdb14c/app/src/main/res/drawable-xhdpi/splash.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/drawer_shadow.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/250d90c9f09660f8a9920751e8220a9918fdb14c/app/src/main/res/drawable-xxhdpi/drawer_shadow.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_actionbar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/250d90c9f09660f8a9920751e8220a9918fdb14c/app/src/main/res/drawable-xxhdpi/ic_actionbar.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_chevron_down_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/250d90c9f09660f8a9920751e8220a9918fdb14c/app/src/main/res/drawable-xxhdpi/ic_chevron_down_light.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_chevron_up_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/250d90c9f09660f8a9920751e8220a9918fdb14c/app/src/main/res/drawable-xxhdpi/ic_chevron_up_light.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/250d90c9f09660f8a9920751e8220a9918fdb14c/app/src/main/res/drawable-xxhdpi/ic_notification.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_refresh_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/250d90c9f09660f8a9920751e8220a9918fdb14c/app/src/main/res/drawable-xxhdpi/ic_refresh_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_refresh_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/250d90c9f09660f8a9920751e8220a9918fdb14c/app/src/main/res/drawable-xxhdpi/ic_refresh_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_search_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/250d90c9f09660f8a9920751e8220a9918fdb14c/app/src/main/res/drawable-xxhdpi/ic_search_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_search_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/250d90c9f09660f8a9920751e8220a9918fdb14c/app/src/main/res/drawable-xxhdpi/ic_search_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_share_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/250d90c9f09660f8a9920751e8220a9918fdb14c/app/src/main/res/drawable-xxhdpi/ic_share_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_share_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/250d90c9f09660f8a9920751e8220a9918fdb14c/app/src/main/res/drawable-xxhdpi/ic_share_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/splash.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/250d90c9f09660f8a9920751e8220a9918fdb14c/app/src/main/res/drawable-xxhdpi/splash.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_actionbar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/250d90c9f09660f8a9920751e8220a9918fdb14c/app/src/main/res/drawable-xxxhdpi/ic_actionbar.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_notification.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/250d90c9f09660f8a9920751e8220a9918fdb14c/app/src/main/res/drawable-xxxhdpi/ic_notification.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_refresh_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/250d90c9f09660f8a9920751e8220a9918fdb14c/app/src/main/res/drawable-xxxhdpi/ic_refresh_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_refresh_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/250d90c9f09660f8a9920751e8220a9918fdb14c/app/src/main/res/drawable-xxxhdpi/ic_refresh_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_search_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/250d90c9f09660f8a9920751e8220a9918fdb14c/app/src/main/res/drawable-xxxhdpi/ic_search_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_search_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/250d90c9f09660f8a9920751e8220a9918fdb14c/app/src/main/res/drawable-xxxhdpi/ic_search_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_share_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/250d90c9f09660f8a9920751e8220a9918fdb14c/app/src/main/res/drawable-xxxhdpi/ic_share_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_share_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/250d90c9f09660f8a9920751e8220a9918fdb14c/app/src/main/res/drawable-xxxhdpi/ic_share_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/splash.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gonativeio/gonative-android/250d90c9f09660f8a9920751e8220a9918fdb14c/app/src/main/res/drawable-xxxhdpi/splash.9.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_nav_icon.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_arrow_back_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_arrow_forward_24.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_go_back.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_go_forward.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_stat_onesignal_default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shape_rounded.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/layout/actionbar_title.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 14 | 15 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_subscriptions.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 16 | 17 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/layout/button_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 |