From dfc706b13ecebf2d6d76c35a00fd6ccd86aae04f Mon Sep 17 00:00:00 2001 From: Rahul Kumar Patel Date: Mon, 15 Feb 2021 04:52:56 +0530 Subject: [PATCH] Initial v4 --- .gitignore | 43 + app/.gitignore | 1 + app/build.gradle | 166 + app/proguard-rules.pro | 120 + .../aurora/store/ExampleInstrumentedTest.kt | 41 + app/src/main/AndroidManifest.xml | 128 + .../aurora/services/IPrivilegedCallback.aidl | 24 + .../aurora/services/IPrivilegedService.aidl | 46 + app/src/main/assets/accent.json | 50 + app/src/main/assets/dash.json | 37 + app/src/main/assets/exodus_trackers.json | 2724 +++++++++++++++++ app/src/main/assets/installers.json | 28 + app/src/main/assets/themes.json | 32 + app/src/main/ic_launcher-playstore.png | Bin 0 -> 19502 bytes app/src/main/java/com/aurora/Constants.kt | 51 + .../com/aurora/store/AuroraApplication.kt | 78 + .../main/java/com/aurora/store/AuroraGlide.kt | 53 + .../java/com/aurora/store/Enumerations.kt | 25 + .../java/com/aurora/store/MainActivity.kt | 243 ++ .../com/aurora/store/data/SingletonHolder.kt | 36 + .../main/java/com/aurora/store/data/State.kt | 44 + .../store/data/downloader/DownloadManager.kt | 90 + .../store/data/downloader/RequestBuilder.kt | 68 + .../com/aurora/store/data/event/BusEvent.kt | 31 + .../store/data/installer/AppInstaller.kt | 49 + .../aurora/store/data/installer/IInstaller.kt | 29 + .../store/data/installer/InstallerBase.kt | 70 + .../store/data/installer/InstallerService.kt | 76 + .../store/data/installer/NativeInstaller.kt | 66 + .../store/data/installer/RootInstaller.kt | 99 + .../store/data/installer/ServiceInstaller.kt | 106 + .../store/data/installer/SessionInstaller.kt | 119 + .../com/aurora/store/data/model/Accent.kt | 36 + .../java/com/aurora/store/data/model/Black.kt | 40 + .../java/com/aurora/store/data/model/Dash.kt | 39 + .../com/aurora/store/data/model/Download.kt | 36 + .../com/aurora/store/data/model/Exodus.kt | 72 + .../com/aurora/store/data/model/Installer.kt | 39 + .../java/com/aurora/store/data/model/Link.kt | 39 + .../java/com/aurora/store/data/model/Theme.kt | 37 + .../aurora/store/data/network/FuelClient.kt | 126 + .../aurora/store/data/network/HttpClient.kt | 34 + .../aurora/store/data/network/OkHttpClient.kt | 169 + .../store/data/providers/AccountProvider.kt | 53 + .../store/data/providers/ApkProvider.kt | 26 + .../store/data/providers/AuthProvider.kt | 53 + .../store/data/providers/BlacklistProvider.kt | 78 + .../data/providers/EglExtensionProvider.kt | 131 + .../data/providers/ExodusDataProvider.kt | 60 + .../providers/NativeDeviceInfoProvider.kt | 167 + .../providers/NativeGsfVersionProvider.kt | 90 + .../store/data/providers/NetworkProvider.kt | 101 + .../data/providers/SpoofDeviceProvider.kt | 176 ++ .../store/data/providers/SpoofProvider.kt | 87 + .../store/data/receiver/BootReceiver.kt | 27 + .../data/receiver/DownloadCancelReceiver.kt | 38 + .../data/receiver/DownloadPauseReceiver.kt | 38 + .../data/receiver/DownloadResumeReceiver.kt | 38 + .../store/data/receiver/InstallReceiver.kt | 36 + .../data/receiver/PackageManagerReceiver.kt | 82 + .../store/data/receiver/UpdatesReceiver.kt | 30 + .../store/data/service/NotificationService.kt | 378 +++ .../java/com/aurora/store/util/AC2DMTask.kt | 66 + .../java/com/aurora/store/util/ApkCopier.kt | 122 + .../java/com/aurora/store/util/CertUtil.kt | 100 + .../java/com/aurora/store/util/CommonUtil.kt | 189 ++ .../main/java/com/aurora/store/util/Log.kt | 73 + .../com/aurora/store/util/NavigationUtil.kt | 99 + .../java/com/aurora/store/util/PackageUtil.kt | 155 + .../java/com/aurora/store/util/PathUtil.kt | 78 + .../java/com/aurora/store/util/Preferences.kt | 114 + .../main/java/com/aurora/store/util/Util.java | 53 + .../java/com/aurora/store/util/ViewUtil.kt | 71 + .../store/util/extensions/Collection.kt | 30 + .../aurora/store/util/extensions/Commons.kt | 46 + .../aurora/store/util/extensions/Context.kt | 114 + .../com/aurora/store/util/extensions/Glide.kt | 165 + .../aurora/store/util/extensions/Number.kt | 28 + .../store/util/extensions/ThemeEngine.kt | 148 + .../aurora/store/util/extensions/Threading.kt | 41 + .../com/aurora/store/util/extensions/Toast.kt | 36 + .../com/aurora/store/util/extensions/View.kt | 81 + .../aurora/store/view/custom/ActionButton.kt | 86 + .../view/custom/CubicBezierInterpolator.java | 94 + .../aurora/store/view/custom/RatingView.kt | 71 + .../aurora/store/view/custom/StateButton.kt | 89 + .../view/custom/layouts/ActionHeaderLayout.kt | 69 + .../store/view/custom/layouts/ViewDevInfo.kt | 83 + .../custom/preference/AuroraListPreference.kt | 60 + .../EndlessRecyclerOnScrollListener.kt | 176 ++ .../controller/CategoryCarouselController.kt | 29 + .../EarlyAccessCarouselController.kt | 30 + .../controller/EditorChoiceController.kt | 50 + .../epoxy/controller/FlexLayoutManager.kt | 47 + .../controller/GenericCarouselController.kt | 102 + .../view/epoxy/groups/CarouselHorizontal.kt | 36 + .../view/epoxy/groups/CarouselModelGroup.kt | 102 + .../view/epoxy/groups/CarouselShimmerGroup.kt | 60 + .../epoxy/groups/EditorChoiceModelGroup.kt | 76 + .../store/view/epoxy/views/AccentView.kt | 82 + .../store/view/epoxy/views/AppProgressView.kt | 54 + .../aurora/store/view/epoxy/views/BaseView.kt | 49 + .../store/view/epoxy/views/BlackView.kt | 102 + .../store/view/epoxy/views/CategoryView.kt | 81 + .../store/view/epoxy/views/DownloadView.kt | 146 + .../store/view/epoxy/views/EditorHeadView.kt | 63 + .../store/view/epoxy/views/EditorImageView.kt | 90 + .../store/view/epoxy/views/HeaderView.kt | 84 + .../view/epoxy/views/MinimalHeaderView.kt | 76 + .../view/epoxy/views/SearchSuggestionView.kt | 98 + .../store/view/epoxy/views/ThemeView.kt | 77 + .../view/epoxy/views/UpdateHeaderView.kt | 81 + .../store/view/epoxy/views/app/AppListView.kt | 110 + .../view/epoxy/views/app/AppUpdateView.kt | 124 + .../store/view/epoxy/views/app/AppView.kt | 93 + .../view/epoxy/views/app/NoAppAltView.kt | 65 + .../store/view/epoxy/views/app/NoAppView.kt | 73 + .../epoxy/views/details/AppDependentView.kt | 89 + .../view/epoxy/views/details/BadgeView.kt | 91 + .../view/epoxy/views/details/ExodusView.kt | 78 + .../view/epoxy/views/details/FileView.kt | 66 + .../views/details/LargeScreenshotView.kt | 118 + .../view/epoxy/views/details/MoreBadgeView.kt | 99 + .../view/epoxy/views/details/ReviewView.kt | 98 + .../epoxy/views/details/ScreenshotView.kt | 126 + .../view/epoxy/views/preference/DashView.kt | 84 + .../view/epoxy/views/preference/DeviceView.kt | 84 + .../epoxy/views/preference/InstallerView.kt | 79 + .../view/epoxy/views/preference/LinkView.kt | 81 + .../view/epoxy/views/preference/LocaleView.kt | 81 + .../epoxy/views/shimmer/AppListViewShimmer.kt | 58 + .../epoxy/views/shimmer/AppViewShimmer.kt | 58 + .../epoxy/views/shimmer/HeaderViewShimmer.kt | 58 + .../store/view/ui/about/AboutActivity.kt | 128 + .../store/view/ui/account/AccountActivity.kt | 246 ++ .../store/view/ui/account/GoogleActivity.kt | 141 + .../store/view/ui/all/AppsGamesActivity.kt | 103 + .../view/ui/all/InstalledAppsFragment.kt | 130 + .../store/view/ui/all/LibraryAppsFragment.kt | 130 + .../view/ui/apps/AppsContainerFragment.kt | 106 + .../view/ui/apps/TopChartContainerFragment.kt | 104 + .../store/view/ui/apps/TopChartFragment.kt | 132 + .../store/view/ui/commons/BaseActivity.kt | 144 + .../store/view/ui/commons/BaseFragment.kt | 74 + .../view/ui/commons/BlacklistActivity.kt | 114 + .../view/ui/commons/CategoryBrowseActivity.kt | 150 + .../store/view/ui/commons/CategoryFragment.kt | 101 + .../view/ui/commons/EarlyAccessFragment.kt | 141 + .../view/ui/commons/EditorChoiceFragment.kt | 102 + .../store/view/ui/commons/ForYouFragment.kt | 150 + .../view/ui/commons/StreamBrowseActivity.kt | 130 + .../view/ui/details/AppDetailsActivity.kt | 579 ++++ .../view/ui/details/BaseDetailsActivity.kt | 329 ++ .../view/ui/details/DetailsExodusActivity.kt | 130 + .../view/ui/details/DetailsMoreActivity.kt | 178 ++ .../view/ui/details/DetailsReviewActivity.kt | 150 + .../store/view/ui/details/DevAppsActivity.kt | 127 + .../view/ui/details/ScreenshotActivity.kt | 92 + .../view/ui/downloads/DownloadActivity.kt | 243 ++ .../view/ui/games/GamesContainerFragment.kt | 109 + .../ui/games/TopChartContainerFragment.kt | 104 + .../store/view/ui/games/TopChartFragment.kt | 136 + .../view/ui/onboarding/AccentFragment.kt | 195 ++ .../view/ui/onboarding/InstallerFragment.kt | 128 + .../view/ui/onboarding/OnboardingActivity.kt | 145 + .../store/view/ui/onboarding/ThemeFragment.kt | 180 ++ .../view/ui/onboarding/WelcomeFragment.kt | 104 + .../view/ui/preferences/DownloadPreference.kt | 31 + .../view/ui/preferences/FilterPreference.kt | 30 + .../ui/preferences/InstallationPreference.kt | 54 + .../view/ui/preferences/MainPreference.kt | 31 + .../view/ui/preferences/SettingsActivity.kt | 152 + .../store/view/ui/preferences/UIPreference.kt | 76 + .../store/view/ui/sale/AppSalesActivity.kt | 121 + .../view/ui/search/SearchResultsActivity.kt | 176 ++ .../ui/search/SearchSuggestionActivity.kt | 159 + .../store/view/ui/sheets/AppMenuSheet.kt | 143 + .../view/ui/sheets/AppPeekDialogSheet.kt | 96 + .../store/view/ui/sheets/BaseBottomSheet.kt | 81 + .../store/view/ui/sheets/DownloadMenuSheet.kt | 122 + .../view/ui/sheets/NetworkDialogSheet.kt | 64 + .../store/view/ui/splash/SplashActivity.kt | 199 ++ .../view/ui/spoof/DeviceSpoofFragment.kt | 120 + .../view/ui/spoof/LocaleSpoofFragment.kt | 121 + .../store/view/ui/spoof/SpoofActivity.kt | 92 + .../store/view/ui/updates/UpdatesFragment.kt | 277 ++ .../store/viewmodel/BaseAndroidViewModel.kt | 80 + .../store/viewmodel/all/BaseAppsViewModel.kt | 93 + .../store/viewmodel/all/BlacklistViewModel.kt | 86 + .../store/viewmodel/all/InstalledViewModel.kt | 88 + .../viewmodel/all/LibraryAppsViewModel.kt | 82 + .../store/viewmodel/all/UpdatesViewModel.kt | 112 + .../store/viewmodel/auth/AuthViewModel.kt | 224 ++ .../viewmodel/browse/StreamBrowseViewModel.kt | 102 + .../category/BaseCategoryViewModel.kt | 59 + .../viewmodel/category/CategoryViewModel.kt | 42 + .../BaseEditorChoiceViewModel.kt | 63 + .../editorschoice/EditorChoiceViewModel.kt | 38 + .../homestream/BaseClusterViewModel.kt | 122 + .../homestream/EarlyAccessViewModel.kt | 45 + .../viewmodel/homestream/ForYouViewModel.kt | 44 + .../store/viewmodel/review/ReviewViewModel.kt | 75 + .../store/viewmodel/sale/AppSalesViewModel.kt | 71 + .../viewmodel/search/SearchResultViewModel.kt | 95 + .../search/SearchSuggestionViewModel.kt | 57 + .../SubCategoryClusterViewModel.kt | 126 + .../viewmodel/topchart/AppChartViewModel.kt | 60 + .../viewmodel/topchart/BaseChartViewModel.kt | 90 + .../viewmodel/topchart/GameChartViewModel.kt | 60 + app/src/main/res/anim/fade_in.xml | 24 + app/src/main/res/anim/fade_out.xml | 24 + .../main/res/color-v21/chip_txt_selector.xml | 23 + .../main/res/color/chip_stroke_selector.xml | 23 + .../main/res/color/chip_surface_selector.xml | 23 + app/src/main/res/color/chip_txt_selector.xml | 23 + .../main/res/drawable-v21/bg_bottomsheet.xml | 29 + .../res/drawable-v21/bg_outlined_padded.xml | 33 + .../main/res/drawable-v21/bg_placeholder.xml | 25 + app/src/main/res/drawable-v21/bg_rounded.xml | 25 + app/src/main/res/drawable-v21/bg_search.xml | 27 + app/src/main/res/drawable-v21/bg_sheet.xml | 27 + app/src/main/res/drawable-v21/divider.xml | 26 + app/src/main/res/drawable-v21/divider_alt.xml | 26 + .../main/res/drawable-v21/divider_line.xml | 26 + app/src/main/res/drawable-v21/ic_cancel.xml | 30 + app/src/main/res/drawable-v21/ic_shield.xml | 28 + .../drawable-v21/tab_default_onboarding.xml | 30 + .../main/res/drawable-v21/tab_indicator.xml | 30 + .../drawable-v21/tab_selected_onboarding.xml | 30 + .../drawable-v21/tab_selector_onboarding.xml | 23 + .../res/drawable-v24/bg_outlined_rounded.xml | 31 + .../main/res/drawable-v24/bg_progressbar.xml | 56 + .../main/res/drawable-v24/ic_arrow_left.xml | 28 + .../drawable-v24/ic_launcher_foreground.xml | 162 + app/src/main/res/drawable/bg_bottomsheet.xml | 29 + app/src/main/res/drawable/bg_cancel.xml | 25 + app/src/main/res/drawable/bg_changelog.xml | 30 + app/src/main/res/drawable/bg_circle.xml | 24 + .../main/res/drawable/bg_gradient_linear.xml | 30 + .../main/res/drawable/bg_outlined_padded.xml | 33 + .../main/res/drawable/bg_outlined_rounded.xml | 31 + app/src/main/res/drawable/bg_placeholder.xml | 25 + app/src/main/res/drawable/bg_progressbar.xml | 56 + app/src/main/res/drawable/bg_round_solid.xml | 24 + app/src/main/res/drawable/bg_rounded.xml | 25 + .../main/res/drawable/bg_rounded_outlined.xml | 27 + app/src/main/res/drawable/bg_search.xml | 27 + app/src/main/res/drawable/bg_sharp.xml | 24 + app/src/main/res/drawable/bg_sheet.xml | 26 + .../drawable/custom_ripple_circular_solid.xml | 31 + .../drawable/custom_ripple_rounded_solid.xml | 33 + app/src/main/res/drawable/divider.xml | 26 + app/src/main/res/drawable/divider_alt.xml | 26 + app/src/main/res/drawable/divider_line.xml | 25 + .../main/res/drawable/ic_account_manager.xml | 28 + app/src/main/res/drawable/ic_anonymous.xml | 28 + app/src/main/res/drawable/ic_apps.xml | 28 + app/src/main/res/drawable/ic_arrow_back.xml | 28 + .../main/res/drawable/ic_arrow_download.xml | 28 + app/src/main/res/drawable/ic_arrow_left.xml | 28 + app/src/main/res/drawable/ic_arrow_right.xml | 28 + app/src/main/res/drawable/ic_bhim.xml | 34 + app/src/main/res/drawable/ic_bitcoin_bch.xml | 33 + app/src/main/res/drawable/ic_bitcoin_btc.xml | 33 + app/src/main/res/drawable/ic_blacklist.xml | 28 + app/src/main/res/drawable/ic_cancel.xml | 30 + app/src/main/res/drawable/ic_check.xml | 28 + app/src/main/res/drawable/ic_child.xml | 28 + app/src/main/res/drawable/ic_code.xml | 28 + .../res/drawable/ic_dashboard_black_24dp.xml | 28 + app/src/main/res/drawable/ic_disclaimer.xml | 28 + app/src/main/res/drawable/ic_disk.xml | 28 + app/src/main/res/drawable/ic_download.xml | 28 + .../main/res/drawable/ic_download_cancel.png | Bin 0 -> 142 bytes .../main/res/drawable/ic_download_fail.png | Bin 0 -> 251 bytes .../main/res/drawable/ic_download_manager.xml | 28 + .../main/res/drawable/ic_download_pause.png | Bin 0 -> 83 bytes app/src/main/res/drawable/ic_ethereum_eth.xml | 57 + app/src/main/res/drawable/ic_expand.xml | 28 + app/src/main/res/drawable/ic_faq.xml | 28 + app/src/main/res/drawable/ic_fdroid.xml | 134 + app/src/main/res/drawable/ic_filter.xml | 28 + app/src/main/res/drawable/ic_games.xml | 28 + app/src/main/res/drawable/ic_gitlab.xml | 57 + app/src/main/res/drawable/ic_google.xml | 28 + .../main/res/drawable/ic_home_black_24dp.xml | 28 + app/src/main/res/drawable/ic_installation.xml | 28 + .../res/drawable/ic_launcher_background.xml | 188 ++ .../res/drawable/ic_launcher_foreground.xml | 49 + app/src/main/res/drawable/ic_libera_pay.xml | 34 + app/src/main/res/drawable/ic_license.xml | 28 + app/src/main/res/drawable/ic_logo.xml | 156 + app/src/main/res/drawable/ic_logout.xml | 28 + app/src/main/res/drawable/ic_mail.xml | 28 + app/src/main/res/drawable/ic_map.xml | 28 + app/src/main/res/drawable/ic_map_marker.xml | 28 + app/src/main/res/drawable/ic_menu_about.xml | 28 + .../main/res/drawable/ic_menu_settings.xml | 28 + app/src/main/res/drawable/ic_menu_unfold.xml | 28 + app/src/main/res/drawable/ic_network.xml | 28 + .../res/drawable/ic_notification_outlined.xml | 28 + .../drawable/ic_notifications_black_24dp.xml | 28 + app/src/main/res/drawable/ic_paypal.xml | 40 + app/src/main/res/drawable/ic_privacy.xml | 28 + app/src/main/res/drawable/ic_round_search.xml | 28 + app/src/main/res/drawable/ic_sale.xml | 28 + .../main/res/drawable/ic_search_append.xml | 28 + .../res/drawable/ic_search_suggestion.xml | 28 + app/src/main/res/drawable/ic_send.xml | 28 + app/src/main/res/drawable/ic_share.xml | 28 + app/src/main/res/drawable/ic_shield.xml | 28 + app/src/main/res/drawable/ic_size.xml | 28 + app/src/main/res/drawable/ic_star.xml | 28 + app/src/main/res/drawable/ic_telegram.xml | 37 + app/src/main/res/drawable/ic_ui.xml | 28 + app/src/main/res/drawable/ic_updates.xml | 28 + app/src/main/res/drawable/ic_xda.xml | 28 + app/src/main/res/drawable/messages.xml | 28 + app/src/main/res/drawable/progressbar_bg.xml | 37 + app/src/main/res/drawable/sync.xml | 28 + .../res/drawable/tab_default_onboarding.xml | 30 + app/src/main/res/drawable/tab_indicator.xml | 30 + .../res/drawable/tab_selected_onboarding.xml | 30 + .../res/drawable/tab_selector_onboarding.xml | 23 + app/src/main/res/font/gilroy_extra_bold.otf | Bin 0 -> 54776 bytes app/src/main/res/font/montserrat_regular.otf | Bin 0 -> 228620 bytes .../main/res/layout-land/activity_about.xml | 100 + .../main/res/layout-land/activity_account.xml | 201 ++ app/src/main/res/layout-v21/activity_main.xml | 84 + .../main/res/layout-v21/item_preference.xml | 74 + .../res/layout-v21/layout_details_install.xml | 160 + app/src/main/res/layout-v21/sheet_base.xml | 31 + app/src/main/res/layout-v21/view_app.xml | 58 + app/src/main/res/layout-v21/view_dash.xml | 51 + app/src/main/res/layout-v21/view_dev_info.xml | 60 + app/src/main/res/layout/activity_about.xml | 92 + app/src/main/res/layout/activity_account.xml | 194 ++ app/src/main/res/layout/activity_details.xml | 99 + .../main/res/layout/activity_details_more.xml | 97 + .../res/layout/activity_details_review.xml | 117 + app/src/main/res/layout/activity_download.xml | 48 + .../res/layout/activity_generic_pager.xml | 51 + .../res/layout/activity_generic_recycler.xml | 40 + app/src/main/res/layout/activity_google.xml | 28 + app/src/main/res/layout/activity_main.xml | 83 + .../main/res/layout/activity_onboarding.xml | 84 + .../main/res/layout/activity_screenshot.xml | 31 + .../res/layout/activity_search_result.xml | 42 + .../res/layout/activity_search_suggestion.xml | 40 + app/src/main/res/layout/activity_setting.xml | 35 + app/src/main/res/layout/activity_splash.xml | 121 + .../main/res/layout/fragment_apps_games.xml | 47 + .../layout/fragment_details_screenshots.xml | 35 + app/src/main/res/layout/fragment_for_you.xml | 34 + .../res/layout/fragment_generic_recycler.xml | 34 + .../res/layout/fragment_onboarding_accent.xml | 75 + .../layout/fragment_onboarding_installer.xml | 63 + .../res/layout/fragment_onboarding_theme.xml | 75 + .../layout/fragment_onboarding_welcome.xml | 62 + .../main/res/layout/fragment_top_chart.xml | 79 + .../res/layout/fragment_top_container.xml | 35 + app/src/main/res/layout/fragment_updates.xml | 43 + app/src/main/res/layout/item_preference.xml | 74 + .../main/res/layout/layout_details_app.xml | 75 + .../res/layout/layout_details_description.xml | 150 + .../main/res/layout/layout_details_dev.xml | 62 + .../main/res/layout/layout_details_info.xml | 158 + .../res/layout/layout_details_install.xml | 159 + .../res/layout/layout_details_privacy.xml | 65 + .../main/res/layout/layout_details_review.xml | 150 + app/src/main/res/layout/layout_nav_header.xml | 54 + .../main/res/layout/model_carousel_group.xml | 35 + .../res/layout/model_editorchoice_group.xml | 34 + app/src/main/res/layout/multi_recycler.xml | 50 + app/src/main/res/layout/settings_activity.xml | 32 + app/src/main/res/layout/sheet_app_menu.xml | 35 + app/src/main/res/layout/sheet_app_peek.xml | 123 + app/src/main/res/layout/sheet_base.xml | 31 + .../main/res/layout/sheet_download_menu.xml | 34 + app/src/main/res/layout/sheet_network.xml | 63 + app/src/main/res/layout/view_accent.xml | 40 + .../main/res/layout/view_action_button.xml | 59 + .../main/res/layout/view_action_header.xml | 65 + app/src/main/res/layout/view_app.xml | 59 + .../main/res/layout/view_app_card_shimmer.xml | 87 + .../main/res/layout/view_app_dependent.xml | 47 + app/src/main/res/layout/view_app_list.xml | 68 + .../main/res/layout/view_app_list_shimmer.xml | 78 + app/src/main/res/layout/view_app_progress.xml | 30 + app/src/main/res/layout/view_app_shimmer.xml | 66 + app/src/main/res/layout/view_app_update.xml | 141 + app/src/main/res/layout/view_badge.xml | 45 + app/src/main/res/layout/view_black.xml | 70 + app/src/main/res/layout/view_category.xml | 47 + app/src/main/res/layout/view_dash.xml | 51 + app/src/main/res/layout/view_dev_info.xml | 60 + app/src/main/res/layout/view_device.xml | 61 + app/src/main/res/layout/view_download.xml | 140 + app/src/main/res/layout/view_editor_head.xml | 31 + app/src/main/res/layout/view_editor_image.xml | 28 + app/src/main/res/layout/view_exodus.xml | 55 + app/src/main/res/layout/view_file.xml | 47 + app/src/main/res/layout/view_header.xml | 63 + .../main/res/layout/view_header_shimmer.xml | 70 + .../main/res/layout/view_header_update.xml | 63 + app/src/main/res/layout/view_installer.xml | 60 + app/src/main/res/layout/view_link.xml | 62 + app/src/main/res/layout/view_locale.xml | 51 + app/src/main/res/layout/view_more_badge.xml | 57 + app/src/main/res/layout/view_no_app.xml | 42 + app/src/main/res/layout/view_no_app_alt.xml | 33 + app/src/main/res/layout/view_rating.xml | 45 + app/src/main/res/layout/view_review.xml | 84 + app/src/main/res/layout/view_screenshot.xml | 29 + .../main/res/layout/view_screenshot_large.xml | 30 + .../res/layout/view_search_suggestion.xml | 57 + app/src/main/res/layout/view_state_button.xml | 59 + app/src/main/res/layout/view_theme.xml | 50 + .../main/res/layout/view_toolbar_action.xml | 51 + app/src/main/res/layout/view_toolbar_main.xml | 64 + .../main/res/layout/view_toolbar_native.xml | 33 + .../main/res/layout/view_toolbar_search.xml | 63 + app/src/main/res/layout/view_two_column.xml | 49 + app/src/main/res/menu/menu_app.xml | 37 + app/src/main/res/menu/menu_bottom_nav.xml | 37 + app/src/main/res/menu/menu_details.xml | 34 + app/src/main/res/menu/menu_download_main.xml | 36 + .../main/res/menu/menu_download_single.xml | 36 + app/src/main/res/menu/menu_drawer.xml | 67 + app/src/main/res/menu/menu_splash.xml | 30 + .../res/mipmap-anydpi-v26/ic_launcher.xml | 23 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 23 + app/src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 4093 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 4093 bytes app/src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 2546 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 2546 bytes app/src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 5806 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 5806 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 9047 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 9047 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 12890 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 12890 bytes .../main/res/navigation/mobile_navigation.xml | 43 + .../res/navigation/navigation_onboarding.xml | 51 + app/src/main/res/values-v21/styles_text.xml | 133 + app/src/main/res/values-v26/styles_text.xml | 70 + app/src/main/res/values/arrays.xml | 160 + app/src/main/res/values/attrs.xml | 48 + app/src/main/res/values/colors.xml | 88 + app/src/main/res/values/dimens.xml | 62 + app/src/main/res/values/font_certs.xml | 35 + .../res/values/ic_launcher_background.xml | 22 + app/src/main/res/values/strings.xml | 291 ++ .../res/values/strings_do_not_translate.xml | 43 + app/src/main/res/values/styles.xml | 89 + app/src/main/res/values/styles_accent.xml | 81 + app/src/main/res/values/styles_text.xml | 140 + app/src/main/res/values/styles_widget.xml | 75 + app/src/main/res/xml/paths.xml | 30 + app/src/main/res/xml/preferences_download.xml | 33 + app/src/main/res/xml/preferences_filter.xml | 35 + .../main/res/xml/preferences_installation.xml | 58 + app/src/main/res/xml/preferences_main.xml | 51 + app/src/main/res/xml/preferences_ui.xml | 39 + .../java/com/aurora/store/ExampleUnitTest.kt | 35 + build.gradle | 50 + gradle.properties | 57 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 54329 bytes gradle/wrapper/gradle-wrapper.properties | 25 + gradlew | 172 ++ gradlew.bat | 84 + settings.gradle | 21 + 472 files changed, 35142 insertions(+) create mode 100644 .gitignore create mode 100644 app/.gitignore create mode 100644 app/build.gradle create mode 100644 app/proguard-rules.pro create mode 100644 app/src/androidTest/java/com/aurora/store/ExampleInstrumentedTest.kt create mode 100644 app/src/main/AndroidManifest.xml create mode 100644 app/src/main/aidl/com/aurora/services/IPrivilegedCallback.aidl create mode 100644 app/src/main/aidl/com/aurora/services/IPrivilegedService.aidl create mode 100755 app/src/main/assets/accent.json create mode 100755 app/src/main/assets/dash.json create mode 100755 app/src/main/assets/exodus_trackers.json create mode 100755 app/src/main/assets/installers.json create mode 100755 app/src/main/assets/themes.json create mode 100644 app/src/main/ic_launcher-playstore.png create mode 100644 app/src/main/java/com/aurora/Constants.kt create mode 100644 app/src/main/java/com/aurora/store/AuroraApplication.kt create mode 100644 app/src/main/java/com/aurora/store/AuroraGlide.kt create mode 100644 app/src/main/java/com/aurora/store/Enumerations.kt create mode 100644 app/src/main/java/com/aurora/store/MainActivity.kt create mode 100644 app/src/main/java/com/aurora/store/data/SingletonHolder.kt create mode 100644 app/src/main/java/com/aurora/store/data/State.kt create mode 100644 app/src/main/java/com/aurora/store/data/downloader/DownloadManager.kt create mode 100644 app/src/main/java/com/aurora/store/data/downloader/RequestBuilder.kt create mode 100644 app/src/main/java/com/aurora/store/data/event/BusEvent.kt create mode 100644 app/src/main/java/com/aurora/store/data/installer/AppInstaller.kt create mode 100644 app/src/main/java/com/aurora/store/data/installer/IInstaller.kt create mode 100644 app/src/main/java/com/aurora/store/data/installer/InstallerBase.kt create mode 100644 app/src/main/java/com/aurora/store/data/installer/InstallerService.kt create mode 100644 app/src/main/java/com/aurora/store/data/installer/NativeInstaller.kt create mode 100644 app/src/main/java/com/aurora/store/data/installer/RootInstaller.kt create mode 100644 app/src/main/java/com/aurora/store/data/installer/ServiceInstaller.kt create mode 100644 app/src/main/java/com/aurora/store/data/installer/SessionInstaller.kt create mode 100644 app/src/main/java/com/aurora/store/data/model/Accent.kt create mode 100644 app/src/main/java/com/aurora/store/data/model/Black.kt create mode 100644 app/src/main/java/com/aurora/store/data/model/Dash.kt create mode 100644 app/src/main/java/com/aurora/store/data/model/Download.kt create mode 100644 app/src/main/java/com/aurora/store/data/model/Exodus.kt create mode 100644 app/src/main/java/com/aurora/store/data/model/Installer.kt create mode 100644 app/src/main/java/com/aurora/store/data/model/Link.kt create mode 100644 app/src/main/java/com/aurora/store/data/model/Theme.kt create mode 100644 app/src/main/java/com/aurora/store/data/network/FuelClient.kt create mode 100644 app/src/main/java/com/aurora/store/data/network/HttpClient.kt create mode 100644 app/src/main/java/com/aurora/store/data/network/OkHttpClient.kt create mode 100644 app/src/main/java/com/aurora/store/data/providers/AccountProvider.kt create mode 100644 app/src/main/java/com/aurora/store/data/providers/ApkProvider.kt create mode 100644 app/src/main/java/com/aurora/store/data/providers/AuthProvider.kt create mode 100644 app/src/main/java/com/aurora/store/data/providers/BlacklistProvider.kt create mode 100644 app/src/main/java/com/aurora/store/data/providers/EglExtensionProvider.kt create mode 100644 app/src/main/java/com/aurora/store/data/providers/ExodusDataProvider.kt create mode 100644 app/src/main/java/com/aurora/store/data/providers/NativeDeviceInfoProvider.kt create mode 100644 app/src/main/java/com/aurora/store/data/providers/NativeGsfVersionProvider.kt create mode 100644 app/src/main/java/com/aurora/store/data/providers/NetworkProvider.kt create mode 100644 app/src/main/java/com/aurora/store/data/providers/SpoofDeviceProvider.kt create mode 100644 app/src/main/java/com/aurora/store/data/providers/SpoofProvider.kt create mode 100644 app/src/main/java/com/aurora/store/data/receiver/BootReceiver.kt create mode 100644 app/src/main/java/com/aurora/store/data/receiver/DownloadCancelReceiver.kt create mode 100644 app/src/main/java/com/aurora/store/data/receiver/DownloadPauseReceiver.kt create mode 100644 app/src/main/java/com/aurora/store/data/receiver/DownloadResumeReceiver.kt create mode 100644 app/src/main/java/com/aurora/store/data/receiver/InstallReceiver.kt create mode 100644 app/src/main/java/com/aurora/store/data/receiver/PackageManagerReceiver.kt create mode 100644 app/src/main/java/com/aurora/store/data/receiver/UpdatesReceiver.kt create mode 100644 app/src/main/java/com/aurora/store/data/service/NotificationService.kt create mode 100644 app/src/main/java/com/aurora/store/util/AC2DMTask.kt create mode 100644 app/src/main/java/com/aurora/store/util/ApkCopier.kt create mode 100644 app/src/main/java/com/aurora/store/util/CertUtil.kt create mode 100644 app/src/main/java/com/aurora/store/util/CommonUtil.kt create mode 100644 app/src/main/java/com/aurora/store/util/Log.kt create mode 100644 app/src/main/java/com/aurora/store/util/NavigationUtil.kt create mode 100644 app/src/main/java/com/aurora/store/util/PackageUtil.kt create mode 100644 app/src/main/java/com/aurora/store/util/PathUtil.kt create mode 100644 app/src/main/java/com/aurora/store/util/Preferences.kt create mode 100644 app/src/main/java/com/aurora/store/util/Util.java create mode 100644 app/src/main/java/com/aurora/store/util/ViewUtil.kt create mode 100644 app/src/main/java/com/aurora/store/util/extensions/Collection.kt create mode 100644 app/src/main/java/com/aurora/store/util/extensions/Commons.kt create mode 100644 app/src/main/java/com/aurora/store/util/extensions/Context.kt create mode 100644 app/src/main/java/com/aurora/store/util/extensions/Glide.kt create mode 100644 app/src/main/java/com/aurora/store/util/extensions/Number.kt create mode 100644 app/src/main/java/com/aurora/store/util/extensions/ThemeEngine.kt create mode 100644 app/src/main/java/com/aurora/store/util/extensions/Threading.kt create mode 100644 app/src/main/java/com/aurora/store/util/extensions/Toast.kt create mode 100644 app/src/main/java/com/aurora/store/util/extensions/View.kt create mode 100644 app/src/main/java/com/aurora/store/view/custom/ActionButton.kt create mode 100644 app/src/main/java/com/aurora/store/view/custom/CubicBezierInterpolator.java create mode 100644 app/src/main/java/com/aurora/store/view/custom/RatingView.kt create mode 100644 app/src/main/java/com/aurora/store/view/custom/StateButton.kt create mode 100644 app/src/main/java/com/aurora/store/view/custom/layouts/ActionHeaderLayout.kt create mode 100644 app/src/main/java/com/aurora/store/view/custom/layouts/ViewDevInfo.kt create mode 100644 app/src/main/java/com/aurora/store/view/custom/preference/AuroraListPreference.kt create mode 100644 app/src/main/java/com/aurora/store/view/custom/recycler/EndlessRecyclerOnScrollListener.kt create mode 100644 app/src/main/java/com/aurora/store/view/epoxy/controller/CategoryCarouselController.kt create mode 100644 app/src/main/java/com/aurora/store/view/epoxy/controller/EarlyAccessCarouselController.kt create mode 100644 app/src/main/java/com/aurora/store/view/epoxy/controller/EditorChoiceController.kt create mode 100644 app/src/main/java/com/aurora/store/view/epoxy/controller/FlexLayoutManager.kt create mode 100644 app/src/main/java/com/aurora/store/view/epoxy/controller/GenericCarouselController.kt create mode 100644 app/src/main/java/com/aurora/store/view/epoxy/groups/CarouselHorizontal.kt create mode 100644 app/src/main/java/com/aurora/store/view/epoxy/groups/CarouselModelGroup.kt create mode 100644 app/src/main/java/com/aurora/store/view/epoxy/groups/CarouselShimmerGroup.kt create mode 100644 app/src/main/java/com/aurora/store/view/epoxy/groups/EditorChoiceModelGroup.kt create mode 100644 app/src/main/java/com/aurora/store/view/epoxy/views/AccentView.kt create mode 100644 app/src/main/java/com/aurora/store/view/epoxy/views/AppProgressView.kt create mode 100644 app/src/main/java/com/aurora/store/view/epoxy/views/BaseView.kt create mode 100644 app/src/main/java/com/aurora/store/view/epoxy/views/BlackView.kt create mode 100644 app/src/main/java/com/aurora/store/view/epoxy/views/CategoryView.kt create mode 100644 app/src/main/java/com/aurora/store/view/epoxy/views/DownloadView.kt create mode 100644 app/src/main/java/com/aurora/store/view/epoxy/views/EditorHeadView.kt create mode 100644 app/src/main/java/com/aurora/store/view/epoxy/views/EditorImageView.kt create mode 100644 app/src/main/java/com/aurora/store/view/epoxy/views/HeaderView.kt create mode 100644 app/src/main/java/com/aurora/store/view/epoxy/views/MinimalHeaderView.kt create mode 100644 app/src/main/java/com/aurora/store/view/epoxy/views/SearchSuggestionView.kt create mode 100644 app/src/main/java/com/aurora/store/view/epoxy/views/ThemeView.kt create mode 100644 app/src/main/java/com/aurora/store/view/epoxy/views/UpdateHeaderView.kt create mode 100644 app/src/main/java/com/aurora/store/view/epoxy/views/app/AppListView.kt create mode 100644 app/src/main/java/com/aurora/store/view/epoxy/views/app/AppUpdateView.kt create mode 100644 app/src/main/java/com/aurora/store/view/epoxy/views/app/AppView.kt create mode 100644 app/src/main/java/com/aurora/store/view/epoxy/views/app/NoAppAltView.kt create mode 100644 app/src/main/java/com/aurora/store/view/epoxy/views/app/NoAppView.kt create mode 100644 app/src/main/java/com/aurora/store/view/epoxy/views/details/AppDependentView.kt create mode 100644 app/src/main/java/com/aurora/store/view/epoxy/views/details/BadgeView.kt create mode 100644 app/src/main/java/com/aurora/store/view/epoxy/views/details/ExodusView.kt create mode 100644 app/src/main/java/com/aurora/store/view/epoxy/views/details/FileView.kt create mode 100644 app/src/main/java/com/aurora/store/view/epoxy/views/details/LargeScreenshotView.kt create mode 100644 app/src/main/java/com/aurora/store/view/epoxy/views/details/MoreBadgeView.kt create mode 100644 app/src/main/java/com/aurora/store/view/epoxy/views/details/ReviewView.kt create mode 100644 app/src/main/java/com/aurora/store/view/epoxy/views/details/ScreenshotView.kt create mode 100644 app/src/main/java/com/aurora/store/view/epoxy/views/preference/DashView.kt create mode 100644 app/src/main/java/com/aurora/store/view/epoxy/views/preference/DeviceView.kt create mode 100644 app/src/main/java/com/aurora/store/view/epoxy/views/preference/InstallerView.kt create mode 100644 app/src/main/java/com/aurora/store/view/epoxy/views/preference/LinkView.kt create mode 100644 app/src/main/java/com/aurora/store/view/epoxy/views/preference/LocaleView.kt create mode 100644 app/src/main/java/com/aurora/store/view/epoxy/views/shimmer/AppListViewShimmer.kt create mode 100644 app/src/main/java/com/aurora/store/view/epoxy/views/shimmer/AppViewShimmer.kt create mode 100644 app/src/main/java/com/aurora/store/view/epoxy/views/shimmer/HeaderViewShimmer.kt create mode 100644 app/src/main/java/com/aurora/store/view/ui/about/AboutActivity.kt create mode 100644 app/src/main/java/com/aurora/store/view/ui/account/AccountActivity.kt create mode 100644 app/src/main/java/com/aurora/store/view/ui/account/GoogleActivity.kt create mode 100644 app/src/main/java/com/aurora/store/view/ui/all/AppsGamesActivity.kt create mode 100644 app/src/main/java/com/aurora/store/view/ui/all/InstalledAppsFragment.kt create mode 100644 app/src/main/java/com/aurora/store/view/ui/all/LibraryAppsFragment.kt create mode 100644 app/src/main/java/com/aurora/store/view/ui/apps/AppsContainerFragment.kt create mode 100644 app/src/main/java/com/aurora/store/view/ui/apps/TopChartContainerFragment.kt create mode 100644 app/src/main/java/com/aurora/store/view/ui/apps/TopChartFragment.kt create mode 100644 app/src/main/java/com/aurora/store/view/ui/commons/BaseActivity.kt create mode 100644 app/src/main/java/com/aurora/store/view/ui/commons/BaseFragment.kt create mode 100644 app/src/main/java/com/aurora/store/view/ui/commons/BlacklistActivity.kt create mode 100644 app/src/main/java/com/aurora/store/view/ui/commons/CategoryBrowseActivity.kt create mode 100644 app/src/main/java/com/aurora/store/view/ui/commons/CategoryFragment.kt create mode 100644 app/src/main/java/com/aurora/store/view/ui/commons/EarlyAccessFragment.kt create mode 100644 app/src/main/java/com/aurora/store/view/ui/commons/EditorChoiceFragment.kt create mode 100644 app/src/main/java/com/aurora/store/view/ui/commons/ForYouFragment.kt create mode 100644 app/src/main/java/com/aurora/store/view/ui/commons/StreamBrowseActivity.kt create mode 100644 app/src/main/java/com/aurora/store/view/ui/details/AppDetailsActivity.kt create mode 100644 app/src/main/java/com/aurora/store/view/ui/details/BaseDetailsActivity.kt create mode 100644 app/src/main/java/com/aurora/store/view/ui/details/DetailsExodusActivity.kt create mode 100644 app/src/main/java/com/aurora/store/view/ui/details/DetailsMoreActivity.kt create mode 100644 app/src/main/java/com/aurora/store/view/ui/details/DetailsReviewActivity.kt create mode 100644 app/src/main/java/com/aurora/store/view/ui/details/DevAppsActivity.kt create mode 100644 app/src/main/java/com/aurora/store/view/ui/details/ScreenshotActivity.kt create mode 100644 app/src/main/java/com/aurora/store/view/ui/downloads/DownloadActivity.kt create mode 100644 app/src/main/java/com/aurora/store/view/ui/games/GamesContainerFragment.kt create mode 100644 app/src/main/java/com/aurora/store/view/ui/games/TopChartContainerFragment.kt create mode 100644 app/src/main/java/com/aurora/store/view/ui/games/TopChartFragment.kt create mode 100644 app/src/main/java/com/aurora/store/view/ui/onboarding/AccentFragment.kt create mode 100644 app/src/main/java/com/aurora/store/view/ui/onboarding/InstallerFragment.kt create mode 100644 app/src/main/java/com/aurora/store/view/ui/onboarding/OnboardingActivity.kt create mode 100644 app/src/main/java/com/aurora/store/view/ui/onboarding/ThemeFragment.kt create mode 100644 app/src/main/java/com/aurora/store/view/ui/onboarding/WelcomeFragment.kt create mode 100644 app/src/main/java/com/aurora/store/view/ui/preferences/DownloadPreference.kt create mode 100644 app/src/main/java/com/aurora/store/view/ui/preferences/FilterPreference.kt create mode 100644 app/src/main/java/com/aurora/store/view/ui/preferences/InstallationPreference.kt create mode 100644 app/src/main/java/com/aurora/store/view/ui/preferences/MainPreference.kt create mode 100644 app/src/main/java/com/aurora/store/view/ui/preferences/SettingsActivity.kt create mode 100644 app/src/main/java/com/aurora/store/view/ui/preferences/UIPreference.kt create mode 100644 app/src/main/java/com/aurora/store/view/ui/sale/AppSalesActivity.kt create mode 100644 app/src/main/java/com/aurora/store/view/ui/search/SearchResultsActivity.kt create mode 100644 app/src/main/java/com/aurora/store/view/ui/search/SearchSuggestionActivity.kt create mode 100644 app/src/main/java/com/aurora/store/view/ui/sheets/AppMenuSheet.kt create mode 100644 app/src/main/java/com/aurora/store/view/ui/sheets/AppPeekDialogSheet.kt create mode 100644 app/src/main/java/com/aurora/store/view/ui/sheets/BaseBottomSheet.kt create mode 100644 app/src/main/java/com/aurora/store/view/ui/sheets/DownloadMenuSheet.kt create mode 100644 app/src/main/java/com/aurora/store/view/ui/sheets/NetworkDialogSheet.kt create mode 100644 app/src/main/java/com/aurora/store/view/ui/splash/SplashActivity.kt create mode 100644 app/src/main/java/com/aurora/store/view/ui/spoof/DeviceSpoofFragment.kt create mode 100644 app/src/main/java/com/aurora/store/view/ui/spoof/LocaleSpoofFragment.kt create mode 100644 app/src/main/java/com/aurora/store/view/ui/spoof/SpoofActivity.kt create mode 100644 app/src/main/java/com/aurora/store/view/ui/updates/UpdatesFragment.kt create mode 100644 app/src/main/java/com/aurora/store/viewmodel/BaseAndroidViewModel.kt create mode 100644 app/src/main/java/com/aurora/store/viewmodel/all/BaseAppsViewModel.kt create mode 100644 app/src/main/java/com/aurora/store/viewmodel/all/BlacklistViewModel.kt create mode 100644 app/src/main/java/com/aurora/store/viewmodel/all/InstalledViewModel.kt create mode 100644 app/src/main/java/com/aurora/store/viewmodel/all/LibraryAppsViewModel.kt create mode 100644 app/src/main/java/com/aurora/store/viewmodel/all/UpdatesViewModel.kt create mode 100644 app/src/main/java/com/aurora/store/viewmodel/auth/AuthViewModel.kt create mode 100644 app/src/main/java/com/aurora/store/viewmodel/browse/StreamBrowseViewModel.kt create mode 100644 app/src/main/java/com/aurora/store/viewmodel/category/BaseCategoryViewModel.kt create mode 100644 app/src/main/java/com/aurora/store/viewmodel/category/CategoryViewModel.kt create mode 100644 app/src/main/java/com/aurora/store/viewmodel/editorschoice/BaseEditorChoiceViewModel.kt create mode 100644 app/src/main/java/com/aurora/store/viewmodel/editorschoice/EditorChoiceViewModel.kt create mode 100644 app/src/main/java/com/aurora/store/viewmodel/homestream/BaseClusterViewModel.kt create mode 100644 app/src/main/java/com/aurora/store/viewmodel/homestream/EarlyAccessViewModel.kt create mode 100644 app/src/main/java/com/aurora/store/viewmodel/homestream/ForYouViewModel.kt create mode 100644 app/src/main/java/com/aurora/store/viewmodel/review/ReviewViewModel.kt create mode 100644 app/src/main/java/com/aurora/store/viewmodel/sale/AppSalesViewModel.kt create mode 100644 app/src/main/java/com/aurora/store/viewmodel/search/SearchResultViewModel.kt create mode 100644 app/src/main/java/com/aurora/store/viewmodel/search/SearchSuggestionViewModel.kt create mode 100644 app/src/main/java/com/aurora/store/viewmodel/subcategory/SubCategoryClusterViewModel.kt create mode 100644 app/src/main/java/com/aurora/store/viewmodel/topchart/AppChartViewModel.kt create mode 100644 app/src/main/java/com/aurora/store/viewmodel/topchart/BaseChartViewModel.kt create mode 100644 app/src/main/java/com/aurora/store/viewmodel/topchart/GameChartViewModel.kt create mode 100644 app/src/main/res/anim/fade_in.xml create mode 100644 app/src/main/res/anim/fade_out.xml create mode 100644 app/src/main/res/color-v21/chip_txt_selector.xml create mode 100644 app/src/main/res/color/chip_stroke_selector.xml create mode 100644 app/src/main/res/color/chip_surface_selector.xml create mode 100644 app/src/main/res/color/chip_txt_selector.xml create mode 100644 app/src/main/res/drawable-v21/bg_bottomsheet.xml create mode 100644 app/src/main/res/drawable-v21/bg_outlined_padded.xml create mode 100644 app/src/main/res/drawable-v21/bg_placeholder.xml create mode 100644 app/src/main/res/drawable-v21/bg_rounded.xml create mode 100644 app/src/main/res/drawable-v21/bg_search.xml create mode 100644 app/src/main/res/drawable-v21/bg_sheet.xml create mode 100644 app/src/main/res/drawable-v21/divider.xml create mode 100644 app/src/main/res/drawable-v21/divider_alt.xml create mode 100644 app/src/main/res/drawable-v21/divider_line.xml create mode 100644 app/src/main/res/drawable-v21/ic_cancel.xml create mode 100644 app/src/main/res/drawable-v21/ic_shield.xml create mode 100644 app/src/main/res/drawable-v21/tab_default_onboarding.xml create mode 100644 app/src/main/res/drawable-v21/tab_indicator.xml create mode 100644 app/src/main/res/drawable-v21/tab_selected_onboarding.xml create mode 100644 app/src/main/res/drawable-v21/tab_selector_onboarding.xml create mode 100644 app/src/main/res/drawable-v24/bg_outlined_rounded.xml create mode 100644 app/src/main/res/drawable-v24/bg_progressbar.xml create mode 100644 app/src/main/res/drawable-v24/ic_arrow_left.xml create mode 100644 app/src/main/res/drawable-v24/ic_launcher_foreground.xml create mode 100644 app/src/main/res/drawable/bg_bottomsheet.xml create mode 100644 app/src/main/res/drawable/bg_cancel.xml create mode 100644 app/src/main/res/drawable/bg_changelog.xml create mode 100644 app/src/main/res/drawable/bg_circle.xml create mode 100644 app/src/main/res/drawable/bg_gradient_linear.xml create mode 100644 app/src/main/res/drawable/bg_outlined_padded.xml create mode 100644 app/src/main/res/drawable/bg_outlined_rounded.xml create mode 100644 app/src/main/res/drawable/bg_placeholder.xml create mode 100644 app/src/main/res/drawable/bg_progressbar.xml create mode 100644 app/src/main/res/drawable/bg_round_solid.xml create mode 100644 app/src/main/res/drawable/bg_rounded.xml create mode 100644 app/src/main/res/drawable/bg_rounded_outlined.xml create mode 100644 app/src/main/res/drawable/bg_search.xml create mode 100644 app/src/main/res/drawable/bg_sharp.xml create mode 100644 app/src/main/res/drawable/bg_sheet.xml create mode 100644 app/src/main/res/drawable/custom_ripple_circular_solid.xml create mode 100644 app/src/main/res/drawable/custom_ripple_rounded_solid.xml create mode 100644 app/src/main/res/drawable/divider.xml create mode 100644 app/src/main/res/drawable/divider_alt.xml create mode 100644 app/src/main/res/drawable/divider_line.xml create mode 100644 app/src/main/res/drawable/ic_account_manager.xml create mode 100644 app/src/main/res/drawable/ic_anonymous.xml create mode 100644 app/src/main/res/drawable/ic_apps.xml create mode 100644 app/src/main/res/drawable/ic_arrow_back.xml create mode 100644 app/src/main/res/drawable/ic_arrow_download.xml create mode 100644 app/src/main/res/drawable/ic_arrow_left.xml create mode 100644 app/src/main/res/drawable/ic_arrow_right.xml create mode 100644 app/src/main/res/drawable/ic_bhim.xml create mode 100644 app/src/main/res/drawable/ic_bitcoin_bch.xml create mode 100644 app/src/main/res/drawable/ic_bitcoin_btc.xml create mode 100644 app/src/main/res/drawable/ic_blacklist.xml create mode 100644 app/src/main/res/drawable/ic_cancel.xml create mode 100644 app/src/main/res/drawable/ic_check.xml create mode 100644 app/src/main/res/drawable/ic_child.xml create mode 100644 app/src/main/res/drawable/ic_code.xml create mode 100644 app/src/main/res/drawable/ic_dashboard_black_24dp.xml create mode 100644 app/src/main/res/drawable/ic_disclaimer.xml create mode 100644 app/src/main/res/drawable/ic_disk.xml create mode 100644 app/src/main/res/drawable/ic_download.xml create mode 100644 app/src/main/res/drawable/ic_download_cancel.png create mode 100644 app/src/main/res/drawable/ic_download_fail.png create mode 100644 app/src/main/res/drawable/ic_download_manager.xml create mode 100644 app/src/main/res/drawable/ic_download_pause.png create mode 100644 app/src/main/res/drawable/ic_ethereum_eth.xml create mode 100644 app/src/main/res/drawable/ic_expand.xml create mode 100644 app/src/main/res/drawable/ic_faq.xml create mode 100644 app/src/main/res/drawable/ic_fdroid.xml create mode 100644 app/src/main/res/drawable/ic_filter.xml create mode 100644 app/src/main/res/drawable/ic_games.xml create mode 100644 app/src/main/res/drawable/ic_gitlab.xml create mode 100644 app/src/main/res/drawable/ic_google.xml create mode 100644 app/src/main/res/drawable/ic_home_black_24dp.xml create mode 100644 app/src/main/res/drawable/ic_installation.xml create mode 100644 app/src/main/res/drawable/ic_launcher_background.xml create mode 100644 app/src/main/res/drawable/ic_launcher_foreground.xml create mode 100644 app/src/main/res/drawable/ic_libera_pay.xml create mode 100644 app/src/main/res/drawable/ic_license.xml create mode 100644 app/src/main/res/drawable/ic_logo.xml create mode 100644 app/src/main/res/drawable/ic_logout.xml create mode 100644 app/src/main/res/drawable/ic_mail.xml create mode 100644 app/src/main/res/drawable/ic_map.xml create mode 100644 app/src/main/res/drawable/ic_map_marker.xml create mode 100644 app/src/main/res/drawable/ic_menu_about.xml create mode 100644 app/src/main/res/drawable/ic_menu_settings.xml create mode 100644 app/src/main/res/drawable/ic_menu_unfold.xml create mode 100644 app/src/main/res/drawable/ic_network.xml create mode 100644 app/src/main/res/drawable/ic_notification_outlined.xml create mode 100644 app/src/main/res/drawable/ic_notifications_black_24dp.xml create mode 100644 app/src/main/res/drawable/ic_paypal.xml create mode 100644 app/src/main/res/drawable/ic_privacy.xml create mode 100644 app/src/main/res/drawable/ic_round_search.xml create mode 100644 app/src/main/res/drawable/ic_sale.xml create mode 100644 app/src/main/res/drawable/ic_search_append.xml create mode 100644 app/src/main/res/drawable/ic_search_suggestion.xml create mode 100644 app/src/main/res/drawable/ic_send.xml create mode 100644 app/src/main/res/drawable/ic_share.xml create mode 100644 app/src/main/res/drawable/ic_shield.xml create mode 100644 app/src/main/res/drawable/ic_size.xml create mode 100644 app/src/main/res/drawable/ic_star.xml create mode 100644 app/src/main/res/drawable/ic_telegram.xml create mode 100644 app/src/main/res/drawable/ic_ui.xml create mode 100644 app/src/main/res/drawable/ic_updates.xml create mode 100644 app/src/main/res/drawable/ic_xda.xml create mode 100644 app/src/main/res/drawable/messages.xml create mode 100644 app/src/main/res/drawable/progressbar_bg.xml create mode 100644 app/src/main/res/drawable/sync.xml create mode 100644 app/src/main/res/drawable/tab_default_onboarding.xml create mode 100644 app/src/main/res/drawable/tab_indicator.xml create mode 100644 app/src/main/res/drawable/tab_selected_onboarding.xml create mode 100644 app/src/main/res/drawable/tab_selector_onboarding.xml create mode 100644 app/src/main/res/font/gilroy_extra_bold.otf create mode 100644 app/src/main/res/font/montserrat_regular.otf create mode 100644 app/src/main/res/layout-land/activity_about.xml create mode 100644 app/src/main/res/layout-land/activity_account.xml create mode 100644 app/src/main/res/layout-v21/activity_main.xml create mode 100644 app/src/main/res/layout-v21/item_preference.xml create mode 100644 app/src/main/res/layout-v21/layout_details_install.xml create mode 100644 app/src/main/res/layout-v21/sheet_base.xml create mode 100644 app/src/main/res/layout-v21/view_app.xml create mode 100644 app/src/main/res/layout-v21/view_dash.xml create mode 100644 app/src/main/res/layout-v21/view_dev_info.xml create mode 100644 app/src/main/res/layout/activity_about.xml create mode 100644 app/src/main/res/layout/activity_account.xml create mode 100644 app/src/main/res/layout/activity_details.xml create mode 100644 app/src/main/res/layout/activity_details_more.xml create mode 100644 app/src/main/res/layout/activity_details_review.xml create mode 100644 app/src/main/res/layout/activity_download.xml create mode 100644 app/src/main/res/layout/activity_generic_pager.xml create mode 100644 app/src/main/res/layout/activity_generic_recycler.xml create mode 100644 app/src/main/res/layout/activity_google.xml create mode 100644 app/src/main/res/layout/activity_main.xml create mode 100644 app/src/main/res/layout/activity_onboarding.xml create mode 100644 app/src/main/res/layout/activity_screenshot.xml create mode 100644 app/src/main/res/layout/activity_search_result.xml create mode 100644 app/src/main/res/layout/activity_search_suggestion.xml create mode 100644 app/src/main/res/layout/activity_setting.xml create mode 100644 app/src/main/res/layout/activity_splash.xml create mode 100644 app/src/main/res/layout/fragment_apps_games.xml create mode 100644 app/src/main/res/layout/fragment_details_screenshots.xml create mode 100644 app/src/main/res/layout/fragment_for_you.xml create mode 100644 app/src/main/res/layout/fragment_generic_recycler.xml create mode 100644 app/src/main/res/layout/fragment_onboarding_accent.xml create mode 100644 app/src/main/res/layout/fragment_onboarding_installer.xml create mode 100644 app/src/main/res/layout/fragment_onboarding_theme.xml create mode 100644 app/src/main/res/layout/fragment_onboarding_welcome.xml create mode 100644 app/src/main/res/layout/fragment_top_chart.xml create mode 100644 app/src/main/res/layout/fragment_top_container.xml create mode 100644 app/src/main/res/layout/fragment_updates.xml create mode 100644 app/src/main/res/layout/item_preference.xml create mode 100644 app/src/main/res/layout/layout_details_app.xml create mode 100644 app/src/main/res/layout/layout_details_description.xml create mode 100644 app/src/main/res/layout/layout_details_dev.xml create mode 100644 app/src/main/res/layout/layout_details_info.xml create mode 100644 app/src/main/res/layout/layout_details_install.xml create mode 100644 app/src/main/res/layout/layout_details_privacy.xml create mode 100644 app/src/main/res/layout/layout_details_review.xml create mode 100644 app/src/main/res/layout/layout_nav_header.xml create mode 100644 app/src/main/res/layout/model_carousel_group.xml create mode 100644 app/src/main/res/layout/model_editorchoice_group.xml create mode 100644 app/src/main/res/layout/multi_recycler.xml create mode 100644 app/src/main/res/layout/settings_activity.xml create mode 100644 app/src/main/res/layout/sheet_app_menu.xml create mode 100644 app/src/main/res/layout/sheet_app_peek.xml create mode 100644 app/src/main/res/layout/sheet_base.xml create mode 100644 app/src/main/res/layout/sheet_download_menu.xml create mode 100644 app/src/main/res/layout/sheet_network.xml create mode 100644 app/src/main/res/layout/view_accent.xml create mode 100644 app/src/main/res/layout/view_action_button.xml create mode 100644 app/src/main/res/layout/view_action_header.xml create mode 100644 app/src/main/res/layout/view_app.xml create mode 100644 app/src/main/res/layout/view_app_card_shimmer.xml create mode 100644 app/src/main/res/layout/view_app_dependent.xml create mode 100644 app/src/main/res/layout/view_app_list.xml create mode 100644 app/src/main/res/layout/view_app_list_shimmer.xml create mode 100644 app/src/main/res/layout/view_app_progress.xml create mode 100644 app/src/main/res/layout/view_app_shimmer.xml create mode 100644 app/src/main/res/layout/view_app_update.xml create mode 100644 app/src/main/res/layout/view_badge.xml create mode 100644 app/src/main/res/layout/view_black.xml create mode 100644 app/src/main/res/layout/view_category.xml create mode 100644 app/src/main/res/layout/view_dash.xml create mode 100644 app/src/main/res/layout/view_dev_info.xml create mode 100644 app/src/main/res/layout/view_device.xml create mode 100644 app/src/main/res/layout/view_download.xml create mode 100644 app/src/main/res/layout/view_editor_head.xml create mode 100644 app/src/main/res/layout/view_editor_image.xml create mode 100644 app/src/main/res/layout/view_exodus.xml create mode 100644 app/src/main/res/layout/view_file.xml create mode 100644 app/src/main/res/layout/view_header.xml create mode 100644 app/src/main/res/layout/view_header_shimmer.xml create mode 100644 app/src/main/res/layout/view_header_update.xml create mode 100644 app/src/main/res/layout/view_installer.xml create mode 100644 app/src/main/res/layout/view_link.xml create mode 100644 app/src/main/res/layout/view_locale.xml create mode 100644 app/src/main/res/layout/view_more_badge.xml create mode 100644 app/src/main/res/layout/view_no_app.xml create mode 100644 app/src/main/res/layout/view_no_app_alt.xml create mode 100644 app/src/main/res/layout/view_rating.xml create mode 100644 app/src/main/res/layout/view_review.xml create mode 100644 app/src/main/res/layout/view_screenshot.xml create mode 100644 app/src/main/res/layout/view_screenshot_large.xml create mode 100644 app/src/main/res/layout/view_search_suggestion.xml create mode 100644 app/src/main/res/layout/view_state_button.xml create mode 100644 app/src/main/res/layout/view_theme.xml create mode 100644 app/src/main/res/layout/view_toolbar_action.xml create mode 100644 app/src/main/res/layout/view_toolbar_main.xml create mode 100644 app/src/main/res/layout/view_toolbar_native.xml create mode 100644 app/src/main/res/layout/view_toolbar_search.xml create mode 100644 app/src/main/res/layout/view_two_column.xml create mode 100644 app/src/main/res/menu/menu_app.xml create mode 100644 app/src/main/res/menu/menu_bottom_nav.xml create mode 100644 app/src/main/res/menu/menu_details.xml create mode 100644 app/src/main/res/menu/menu_download_main.xml create mode 100644 app/src/main/res/menu/menu_download_single.xml create mode 100644 app/src/main/res/menu/menu_drawer.xml create mode 100644 app/src/main/res/menu/menu_splash.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/navigation/mobile_navigation.xml create mode 100644 app/src/main/res/navigation/navigation_onboarding.xml create mode 100644 app/src/main/res/values-v21/styles_text.xml create mode 100644 app/src/main/res/values-v26/styles_text.xml create mode 100644 app/src/main/res/values/arrays.xml create mode 100644 app/src/main/res/values/attrs.xml create mode 100644 app/src/main/res/values/colors.xml create mode 100644 app/src/main/res/values/dimens.xml create mode 100644 app/src/main/res/values/font_certs.xml create mode 100644 app/src/main/res/values/ic_launcher_background.xml create mode 100644 app/src/main/res/values/strings.xml create mode 100644 app/src/main/res/values/strings_do_not_translate.xml create mode 100644 app/src/main/res/values/styles.xml create mode 100644 app/src/main/res/values/styles_accent.xml create mode 100644 app/src/main/res/values/styles_text.xml create mode 100644 app/src/main/res/values/styles_widget.xml create mode 100644 app/src/main/res/xml/paths.xml create mode 100644 app/src/main/res/xml/preferences_download.xml create mode 100644 app/src/main/res/xml/preferences_filter.xml create mode 100644 app/src/main/res/xml/preferences_installation.xml create mode 100644 app/src/main/res/xml/preferences_main.xml create mode 100644 app/src/main/res/xml/preferences_ui.xml create mode 100644 app/src/test/java/com/aurora/store/ExampleUnitTest.kt create mode 100644 build.gradle create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..74b3680c2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,43 @@ +# Built application files +*.apk +*.ap_ + +# Files for the Dalvik VM +*.dex + +# Java class files +*.class + +# Generated files +bin/ +gen/ + +# Gitlab files +.gitlab/ + +# Gradle files +.gradle/ +build/ +/*/build/ + +# Local configuration file (sdk path, etc) +local.properties + +# Proguard folder generated by Eclipse +proguard/ + +# Log Files +*.log + +# From .gitignore generated by Android Studio +*.iml +.DS_Store +/captures +/.idea +/.gradle* +gradle-app.setting +*.zip + +app/release/* +app/beta/* +app/alpha/* diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 000000000..42afabfd2 --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 000000000..b7eb68fc2 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,166 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-kapt' +apply plugin: "androidx.navigation.safeargs.kotlin" + +android { + compileSdkVersion 30 + buildToolsVersion "30.0.3" + + defaultConfig { + applicationId "com.aurora.store" + minSdkVersion 19 + targetSdkVersion 30 + + versionCode 30 + versionName "4.0.1" + + vectorDrawables.useSupportLibrary = true + multiDexEnabled true + } + + buildTypes { + release { + minifyEnabled true + shrinkResources true + zipAlignEnabled true + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + + beta { + initWith release + applicationIdSuffix = ".beta" + } + + debug { + applicationIdSuffix = ".debug" + } + } + + buildFeatures { + viewBinding true + } + + kotlinOptions { + jvmTarget = '1.8' + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } +} + +buildscript { + ext { + versions = [ + okhttp3 : "4.9.0", + fetch2 : "3.1.6", + fuel : "2.3.0", + glide : "4.11.0", + lifecycle: "2.3.0", + epoxy : "4.3.1", + libsu : "2.5.1" + ] + } +} + +kapt { + correctErrorTypes = true +} + +dependencies { + implementation fileTree(dir: "libs", include: ["*.jar"]) + + //MultiDex for Kitkat support + implementation("androidx.multidex:multidex:2.0.1") + + //Protobuf + implementation("com.google.protobuf:protobuf-java:3.14.0") + + //Apache's Goodies + implementation("commons-io:commons-io:2.7") + implementation("org.apache.commons:commons-text:1.8") + + //Google's Goodies + implementation("com.google.android.material:material:1.3.0") + implementation("com.google.android:flexbox:2.0.1") + implementation("com.google.code.gson:gson:2.8.6") + + //AndroidX + implementation("androidx.core:core-ktx:1.3.2") + implementation("androidx.viewpager2:viewpager2:1.0.0") + implementation("androidx.vectordrawable:vectordrawable:1.1.0") + implementation("androidx.preference:preference-ktx:1.1.1") + implementation("androidx.swiperefreshlayout:swiperefreshlayout:1.1.0") + + //Arch LifeCycle + implementation("androidx.lifecycle:lifecycle-extensions:2.2.0") + implementation("androidx.lifecycle:lifecycle-runtime-ktx:${versions.lifecycle}") + implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:${versions.lifecycle}") + implementation("androidx.lifecycle:lifecycle-viewmodel-savedstate:${versions.lifecycle}") + + //Arch Navigation + implementation("androidx.navigation:navigation-fragment-ktx:${navigation}") + implementation("androidx.navigation:navigation-runtime-ktx:${navigation}") + implementation("androidx.navigation:navigation-ui-ktx:${navigation}") + + //UI Addons + implementation("com.github.florent37:expansionpanel:1.2.4") + + //Easy Permission + implementation("com.github.quickpermissions:quickpermissions-kotlin:0.4.0") + + //Glide + implementation("com.github.bumptech.glide:glide:${versions.glide}") + kapt("com.github.bumptech.glide:compiler:${versions.glide}") + + //Shimmer + implementation("com.facebook.shimmer:shimmer:0.5.0") + + //Epoxy + implementation("com.airbnb.android:epoxy:${versions.epoxy}") + kapt("com.airbnb.android:epoxy-processor:${versions.epoxy}") + + //Merlin + implementation("com.novoda:merlin:1.2.0") + + //HTTP Clients + implementation("com.github.kittinunf.fuel:fuel:${versions.fuel}") + implementation("com.squareup.okhttp3:okhttp:${versions.okhttp3}") + + //Fetch - Downloader + implementation "androidx.tonyodev.fetch2:xfetch2:${versions.fetch2}" + + //Kovenant + implementation("nl.komponents.kovenant:kovenant:3.3.0") + implementation("nl.komponents.kovenant:kovenant-android:3.3.0") + + //EventBus + implementation("org.greenrobot:eventbus:3.2.0") + + //Lib-SU + implementation "com.github.topjohnwu.libsu:core:${versions.libsu}" + + //Love <3 + api("com.gitlab.AuroraOSS:gplayapi:514f061739") +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 000000000..a3bba898e --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,120 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile +# ServiceLoader support +-keepnames class kotlinx.coroutines.internal.MainDispatcherFactory {} +-keepnames class kotlinx.coroutines.CoroutineExceptionHandler {} + +# Most of volatile fields are updated with AFU and should not be mangled +-keepclassmembernames class kotlinx.** { + volatile ; +} + +# Same story for the standard library's SafeContinuation that also uses AtomicReferenceFieldUpdater +-keepclassmembernames class kotlin.coroutines.SafeContinuation { + volatile ; +} + +# These classes are only required by kotlinx.coroutines.debug.AgentPremain, which is only loaded when +# kotlinx-coroutines-core is used as a Java agent, so these are not needed in contexts where ProGuard is used. +-dontwarn java.lang.instrument.ClassFileTransformer +-dontwarn sun.misc.SignalHandler +-dontwarn java.lang.instrument.Instrumentation +-dontwarn sun.misc.Signal + +# JSR 305 annotations are for embedding nullability information. +-dontwarn javax.annotation.** + +# A resource is loaded with a relative path so the package of this class must be preserved. +-keepnames class okhttp3.internal.publicsuffix.PublicSuffixDatabase + +# Animal Sniffer compileOnly dependency to ensure APIs are compatible with older versions of Java. +-dontwarn org.codehaus.mojo.animal_sniffer.* + +# OkHttp platform used only on JVM and when Conscrypt dependency is available. +-dontwarn okhttp3.internal.platform.ConscryptPlatform +-dontwarn org.conscrypt.ConscryptHostnameVerifier + +# Animal Sniffer compileOnly dependency to ensure APIs are compatible with older versions of Java. +-dontwarn org.codehaus.mojo.animal_sniffer.* + +# Retrofit does reflection on generic parameters. InnerClasses is required to use Signature and +# EnclosingMethod is required to use InnerClasses. +-keepattributes Signature, InnerClasses, EnclosingMethod + +# Retrofit does reflection on method and parameter annotations. +-keepattributes RuntimeVisibleAnnotations, RuntimeVisibleParameterAnnotations + +# Retain service method parameters when optimizing. +-keepclassmembers,allowshrinking,allowobfuscation interface * { + @retrofit2.http.* ; +} + +# Ignore annotation used for build tooling. +-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement + +# Ignore JSR 305 annotations for embedding nullability information. +-dontwarn javax.annotation.** + +# Guarded by a NoClassDefFoundError try/catch and only used when on the classpath. +-dontwarn kotlin.Unit + +# Top-level functions that can only be used by Kotlin. +-dontwarn retrofit2.KotlinExtensions +-dontwarn retrofit2.KotlinExtensions$* + +#Kovenant +-dontwarn rx.internal.util.unsafe.** +-dontwarn nl.komponents.kovenant.unsafe.** + +-dontwarn okio.** +-keep class com.google.** +-dontwarn com.google.** +-keep class com.google.gson.Gson {*;} + +# With R8 full mode, it sees no subtypes of Retrofit interfaces since they are created with a Proxy +# and replaces all potential values with null. Explicitly keeping the interfaces prevents this. +-if interface * { @retrofit2.http.* ; } +-keep,allowobfuscation interface <1> + +#Event Bus +-keepattributes *Annotation* +-keepclassmembers class * { + @org.greenrobot.eventbus.Subscribe ; +} +-keep enum org.greenrobot.eventbus.ThreadMode { *; } + +-keepclassmembers class * extends org.greenrobot.eventbus.util.ThrowableFailureEvent { + (java.lang.Throwable); +} + +-keepclassmembers enum * { *; } +-keep class com.aurora.store.view.ui.preferences.** +-dontwarn com.aurora.store.view.ui.preferences.** + +-keepclassmembers class * { + private ; +} + +#GPlay API +-keep public class com.aurora.gplayapi.** { + *; +} \ No newline at end of file diff --git a/app/src/androidTest/java/com/aurora/store/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/aurora/store/ExampleInstrumentedTest.kt new file mode 100644 index 000000000..070d4c90f --- /dev/null +++ b/app/src/androidTest/java/com/aurora/store/ExampleInstrumentedTest.kt @@ -0,0 +1,41 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import org.junit.Assert.* +import org.junit.Test +import org.junit.runner.RunWith + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.aurora.aurorastore", appContext.packageName) + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 000000000..84fbacb63 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,128 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/aidl/com/aurora/services/IPrivilegedCallback.aidl b/app/src/main/aidl/com/aurora/services/IPrivilegedCallback.aidl new file mode 100644 index 000000000..4b4b71b2e --- /dev/null +++ b/app/src/main/aidl/com/aurora/services/IPrivilegedCallback.aidl @@ -0,0 +1,24 @@ +/* + * Copyright (C) 2015-2016 Dominik Schürmann + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.aurora.services; + +interface IPrivilegedCallback { + void handleResult( + in String packageName, + in int returnCode + ); +} \ No newline at end of file diff --git a/app/src/main/aidl/com/aurora/services/IPrivilegedService.aidl b/app/src/main/aidl/com/aurora/services/IPrivilegedService.aidl new file mode 100644 index 000000000..2c1cc63ff --- /dev/null +++ b/app/src/main/aidl/com/aurora/services/IPrivilegedService.aidl @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2015-2016 Dominik Schürmann + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.aurora.services; + +import com.aurora.services.IPrivilegedCallback; + +interface IPrivilegedService { + + boolean hasPrivilegedPermissions(); + + oneway void installPackage( + in String packageName, + in Uri uri, + in int flags, + in String installerPackageName, + in IPrivilegedCallback callback + ); + + oneway void installSplitPackage( + in String packageName, + in List uriList, + in int flags, + in String installerPackageName, + in IPrivilegedCallback callback + ); + + oneway void deletePackage( + in String packageName, + in int flags, + in IPrivilegedCallback callback + ); +} \ No newline at end of file diff --git a/app/src/main/assets/accent.json b/app/src/main/assets/accent.json new file mode 100755 index 000000000..f8d88bdce --- /dev/null +++ b/app/src/main/assets/accent.json @@ -0,0 +1,50 @@ +[ + { + "id": "1", + "accent": "#6C63FF" + }, + { + "id": "2", + "accent": "#FF00FF" + }, + { + "id": "3", + "accent": "#F50057" + }, + { + "id": "4", + "accent": "#F9A826" + }, + { + "id": "5", + "accent": "#49A942" + }, + { + "id": "6", + "accent": "#6633cc" + }, + { + "id": "7", + "accent": "#52565e" + }, + { + "id": "8", + "accent": "#ee70a6" + }, + { + "id": "9", + "accent": "#b5c327" + }, + { + "id": "10", + "accent": "#f38654" + }, + { + "id": "11", + "accent": "#61A7E0" + }, + { + "id": "12", + "accent": "#7289da" + } +] \ No newline at end of file diff --git a/app/src/main/assets/dash.json b/app/src/main/assets/dash.json new file mode 100755 index 000000000..7a774f7f3 --- /dev/null +++ b/app/src/main/assets/dash.json @@ -0,0 +1,37 @@ +[ + { + "id": "0", + "title": "FAQs", + "subtitle": "Have questions? Find out the answers.", + "icon": "ic_faq", + "url": "https://gitlab.com/AuroraOSS/AuroraStore" + }, + { + "id": "1", + "title": "Source code", + "icon": "ic_code", + "subtitle": "Find out what's inside.", + "url": "https://gitlab.com/AuroraOSS/AuroraStore/-/blob/master" + }, + { + "id": "2", + "title": "License", + "subtitle": "Yes we do have one ;)", + "icon": "ic_license", + "url": "https://gitlab.com/AuroraOSS/AuroraStore/-/raw/master/LICENSE" + }, + { + "id": "3", + "title": "Privacy", + "subtitle": "Find out if we have your nudes :p", + "icon": "ic_privacy", + "url": "https://gitlab.com/AuroraOSS/AuroraStore/-/raw/master/PRIVACY" + }, + { + "id": "4", + "title": "Disclaimer", + "subtitle": "Hold your own beer!", + "icon": "ic_disclaimer", + "url": "https://gitlab.com/AuroraOSS/AuroraStore/-/raw/master/DISCLAIMER" + } +] \ No newline at end of file diff --git a/app/src/main/assets/exodus_trackers.json b/app/src/main/assets/exodus_trackers.json new file mode 100755 index 000000000..7552ebd1b --- /dev/null +++ b/app/src/main/assets/exodus_trackers.json @@ -0,0 +1,2724 @@ +[ + { + "1": { + "id": 1, + "name": "Teemo", + "creation_date": "2017-09-24", + "code_signature": "com.databerries.|com.geolocstation.", + "network_signature": "databerries\\.com", + "website": "https://www.teemo.co" + }, + "2": { + "id": 2, + "name": "FidZup", + "creation_date": "2017-09-24", + "code_signature": "com.fidzup.", + "network_signature": "fidzup", + "website": "https://www.fidzup.com" + }, + "3": { + "id": 3, + "name": "Audience Studio (Krux)", + "creation_date": "2017-09-24", + "code_signature": "com.krux.androidsdk", + "network_signature": "krxd\\.net", + "website": "https://www.salesforce.com/products/marketing-cloud/data-management/?mc=DMP" + }, + "4": { + "id": 4, + "name": "Ad4Screen", + "creation_date": "2017-09-24", + "code_signature": "com.ad4screen.sdk", + "network_signature": "a4\\.tl|accengage\\.com|ad4push\\.com|ad4screen\\.com", + "website": "http://www.ad4screen.com" + }, + "5": { + "id": 5, + "name": "Google DoubleClick", + "creation_date": "2017-09-24", + "code_signature": "", + "network_signature": "2mdn\\.net|dmtry\\.com|doubleclick\\.com|doubleclick\\.net|mng-ads\\.com", + "website": "https://www.doubleclickbygoogle.com/" + }, + "6": { + "id": 6, + "name": "Weborama", + "creation_date": "2017-09-24", + "code_signature": "com.weborama.", + "network_signature": "weborama\\.fr|weborama\\.net", + "website": "https://www.weborama.com" + }, + "7": { + "id": 7, + "name": "Smart", + "creation_date": "2017-09-24", + "code_signature": "com.smartadserver.", + "network_signature": "adsrvr\\.org|akamai\\.smartadserver\\.com|cdn1\\.smartadserver\\.com|diff2\\.smartadserver\\.com|diff3\\.smartadserver\\.com|diff\\.smartadserver\\.com|eqx\\.smartadserver\\.com|gallery\\.smartadserver\\.com|im2\\.smartadserver\\.com|insight\\.adsrvr\\.org|itx5-publicidad\\.smartadserver\\.com|itx5\\.smartadserver\\.com|js\\.adsrvr\\.org|match\\.adsrvr\\.org|preview\\.smartadserver\\.com|rtb-csync\\.smartadserver\\.com|saspreview\\.com|smartadserver\\.com|smartadserver\\.ru|tmk\\.smartadserver\\.com|usw\\-lax\\.adsrvr\\.org", + "website": "http://smartadserver.com/" + }, + "8": { + "id": 8, + "name": "JW Player", + "creation_date": "2017-09-24", + "code_signature": "com.longtailvideo.jwplayer.", + "network_signature": "g\\.jwpsrv\\.com|jwpltx\\.com|p\\.jwpcdn\\.com", + "website": "https://jwplayer.com" + }, + "9": { + "id": 9, + "name": "Loggly", + "creation_date": "2017-09-24", + "code_signature": "com.github.tony19.timber.loggly|com.github.tony19.loggly|com.visiware.sync2ad.logger.loggly.", + "network_signature": "loggly\\.com", + "website": "http://loggly.com/" + }, + "10": { + "id": 10, + "name": "Xiti", + "creation_date": "2017-09-24", + "code_signature": "", + "network_signature": "", + "website": "http://Xiti.com" + }, + "11": { + "id": 11, + "name": "OutBrain", + "creation_date": "2017-09-24", + "code_signature": "com.outbrain.", + "network_signature": "outbrain\\.com", + "website": "http://www.outbrain.com/" + }, + "12": { + "id": 12, + "name": "AppsFlyer", + "creation_date": "2017-09-24", + "code_signature": "com.appsflyer.", + "network_signature": "appsflyer\\.com", + "website": "http://AppsFlyer.com" + }, + "13": { + "id": 13, + "name": "Ligatus", + "creation_date": "2017-09-24", + "code_signature": ".LigatusManager|.LigatusViewClient|com.ligatus.android.adframework", + "network_signature": "ligatus\\.com", + "website": "http://ligatus.com" + }, + "14": { + "id": 14, + "name": "Widespace", + "creation_date": "2017-09-24", + "code_signature": "com.widespace.", + "network_signature": "widespace\\.com", + "website": "http://widespace.com" + }, + "15": { + "id": 15, + "name": "AppNexus", + "creation_date": "2017-09-24", + "code_signature": "com.appnexus.opensdk.", + "network_signature": "247realmedia\\.com|adnxs\\.com|appnexus\\.com|appnexus\\.net", + "website": "https://www.appnexus.com/" + }, + "16": { + "id": 16, + "name": "Localytics", + "creation_date": "2017-09-24", + "code_signature": "com.localytics.android.", + "network_signature": "analytics\\.localytics\\.com|manifest\\.localytics\\.com|profile\\.localytics\\.com|sdk-assets\\.localytics\\.com", + "website": "http://localytics.com" + }, + "17": { + "id": 17, + "name": "Braze (formerly Appboy)", + "creation_date": "2017-09-24", + "code_signature": "com.appboy", + "network_signature": "appboy\\.com", + "website": "https://www.braze.com" + }, + "18": { + "id": 18, + "name": "mParticle", + "creation_date": "2017-09-24", + "code_signature": "com.mparticle", + "network_signature": "mparticle\\.com", + "website": "http://mparticle.com" + }, + "20": { + "id": 20, + "name": "S4M", + "creation_date": "2017-09-24", + "code_signature": "com.sam4mobile.", + "network_signature": "s4m\\.io|sam4m\\.com", + "website": "http://www.s4m.io/" + }, + "22": { + "id": 22, + "name": "Sizmek", + "creation_date": "2017-09-24", + "code_signature": ".sizmek.", + "network_signature": "serving-sys\\.com", + "website": "https://www.sizmek.com" + }, + "23": { + "id": 23, + "name": "Batch", + "creation_date": "2017-09-24", + "code_signature": "com.batch.android.", + "network_signature": "batch\\.com", + "website": "https://batch.com" + }, + "24": { + "id": 24, + "name": "Sync2Ad", + "creation_date": "2017-09-24", + "code_signature": "com.visiware.sync2ad.dmp.", + "network_signature": "sync2ad\\.com", + "website": "https://www.sync2ad.com/" + }, + "25": { + "id": 25, + "name": "Flurry", + "creation_date": "2017-09-24", + "code_signature": "com.flurry.", + "network_signature": "flurry\\.com", + "website": "http://www.flurry.com" + }, + "26": { + "id": 26, + "name": "HockeyApp", + "creation_date": "2017-09-24", + "code_signature": "net.hockeyapp.", + "network_signature": "hockeyapp\\.net", + "website": "http://hockeyapp.net" + }, + "27": { + "id": 27, + "name": "Google CrashLytics", + "creation_date": "2017-09-24", + "code_signature": "io.fabric.|com.crashlytics.|com.google.firebase.crashlytics", + "network_signature": "crashlytics\\.com", + "website": "http://crashlytics.com" + }, + "28": { + "id": 28, + "name": "LeanPlum", + "creation_date": "2017-09-24", + "code_signature": "com.leanplum.", + "network_signature": "leanplum\\.com", + "website": "https://www.leanplum.com/" + }, + "29": { + "id": 29, + "name": "Tinder Analytics", + "creation_date": "2017-09-24", + "code_signature": "com.tinder.analytics|com.tinder.ads", + "network_signature": "etl\\.tindersparks\\.com", + "website": "http://tinder.com" + }, + "30": { + "id": 30, + "name": "Schibsted", + "creation_date": "2017-09-25", + "code_signature": ".schibsted.", + "network_signature": "schibsted\\.com|schibsted\\.io", + "website": "http://www.schibsted.com/en/ir/" + }, + "31": { + "id": 31, + "name": "ATInternet", + "creation_date": "2017-09-25", + "code_signature": "com.atinternet.", + "network_signature": "ati-host\\.net", + "website": "https://www.atinternet.com/en/" + }, + "32": { + "id": 32, + "name": "Tealium", + "creation_date": "2017-09-25", + "code_signature": ".tealium.", + "network_signature": "tealiumiq\\.com|tiqcdn\\.com", + "website": "https://tealium.com/" + }, + "33": { + "id": 33, + "name": "Nexage", + "creation_date": "2017-09-27", + "code_signature": "com.nexage.android.|org.nexage.", + "network_signature": "nexage\\.com", + "website": "http://nexage.com/" + }, + "34": { + "id": 34, + "name": "Ogury Presage", + "creation_date": "2017-09-27", + "code_signature": "io.presage.", + "network_signature": "presage\\.io", + "website": "http://www.presage.io/" + }, + "35": { + "id": 35, + "name": "Twitter MoPub", + "creation_date": "2017-09-27", + "code_signature": "com.mopub.mobileads.|com.mopub.nativeads.", + "network_signature": "mopub\\.com", + "website": "https://www.mopub.com/" + }, + "36": { + "id": 36, + "name": "Add Apt Tr", + "creation_date": "2017-09-27", + "code_signature": "com.intentsoftware.addapptr.", + "network_signature": "aatkit\\.com", + "website": "https://www.addapptr.com" + }, + "37": { + "id": 37, + "name": "Vectaury", + "creation_date": "2017-09-28", + "code_signature": "io.vectaury.", + "network_signature": "vectaury\\.io", + "website": "http://vectaury.io/" + }, + "38": { + "id": 38, + "name": "Tune", + "creation_date": "2017-11-02", + "code_signature": "com.tune|com.mobileapptracker", + "network_signature": "mobileapptracking\\.com", + "website": "https://www.tune.com" + }, + "39": { + "id": 39, + "name": "Pushwoosh", + "creation_date": "2017-11-12", + "code_signature": "com.pushwoosh", + "network_signature": "pushwoosh\\.com", + "website": "https://www.pushwoosh.com/" + }, + "40": { + "id": 40, + "name": "Demdex", + "creation_date": "2017-11-15", + "code_signature": "com.adobe.mobile.Analytics", + "network_signature": "demdex\\.net", + "website": "https://www.adobe.com/data-analytics-cloud/audience-manager.html" + }, + "41": { + "id": 41, + "name": "AdsWizz", + "creation_date": "2017-11-15", + "code_signature": ".adswizz.", + "network_signature": "adswizz\\.com|cdn\\.adswizz.com\\.edgesuite\\.net", + "website": "http://www.adswizz.com/" + }, + "42": { + "id": 42, + "name": "ExactTarget", + "creation_date": "2017-11-16", + "code_signature": "com.exacttarget.", + "network_signature": "\\.exacttarget\\.", + "website": "http://help.exacttarget.com/en/technical_library/API_Overview/" + }, + "43": { + "id": 43, + "name": "Omniture", + "creation_date": "2017-11-16", + "code_signature": "com.omniture.|com.adobe.adms.measurement.", + "network_signature": "omniture\\.com|omtrdc\\.net", + "website": "https://www.adobe.com/analytics/adobe-analytics-features.html" + }, + "44": { + "id": 44, + "name": "OpenLocate", + "creation_date": "2017-11-16", + "code_signature": "com.safegraph.|com.openlocate", + "network_signature": "api\\.safegraph\\.com", + "website": "https://www.safegraph.com/" + }, + "45": { + "id": 45, + "name": "TagCommander (Commanders Act.)", + "creation_date": "2017-11-28", + "code_signature": "com.tagcommander.", + "network_signature": "\\.commander1\\.com|\\.tagcommander.com", + "website": "https://www.commandersact.com/" + }, + "46": { + "id": 46, + "name": "CrowdTangle", + "creation_date": "2017-12-03", + "code_signature": "", + "network_signature": "api\\.crowdtangle\\.com", + "website": "https://www.crowdtangle.com/" + }, + "47": { + "id": 47, + "name": "Facebook Audience", + "creation_date": "2017-12-03", + "code_signature": "com.facebook.audiencenetwork", + "network_signature": "\\.facebook\\.com", + "website": "https://developers.facebook.com/docs/android/" + }, + "48": { + "id": 48, + "name": "Google Analytics", + "creation_date": "2017-12-03", + "code_signature": "com.google.android.apps.analytics.|com.google.android.gms.analytics.", + "network_signature": "google-analytics\\.com", + "website": "http://www.google.com/analytics/" + }, + "49": { + "id": 49, + "name": "Google Firebase Analytics", + "creation_date": "2017-12-03", + "code_signature": "com.google.firebase.analytics.|com.google.android.gms.measurement.", + "network_signature": "firebase\\.com", + "website": "https://firebase.google.com/" + }, + "51": { + "id": 51, + "name": "Eulerian", + "creation_date": "2017-12-03", + "code_signature": "com.eulerian.android.sdk", + "network_signature": "eulerian\\.com", + "website": "https://www.eulerian.com/en/" + }, + "52": { + "id": 52, + "name": "Adjust", + "creation_date": "2017-12-03", + "code_signature": "com.adjust.sdk.", + "network_signature": "adj\\.st|adjust\\.com", + "website": "https://www.adjust.com/" + }, + "53": { + "id": 53, + "name": "ChartBoost", + "creation_date": "2017-12-03", + "code_signature": "com.chartboost.sdk.", + "network_signature": "\\.chartboost\\.com", + "website": "https://answers.chartboost.com/en-us/" + }, + "54": { + "id": 54, + "name": "Backelite", + "creation_date": "2017-12-03", + "code_signature": "com.backelite.android.|com.backelite.bkdroid.", + "network_signature": "backelite\\.com", + "website": "https://www.backelite.com/" + }, + "55": { + "id": 55, + "name": "Areametrics", + "creation_date": "2017-12-03", + "code_signature": "com.areametrics.areametricssdk|com.areametrics.nosdkandroid", + "network_signature": "areametrics\\.com", + "website": "https://areametrics.com/" + }, + "56": { + "id": 56, + "name": "ComScore", + "creation_date": "2017-12-03", + "code_signature": "com.comscore.", + "network_signature": "comscore\\.com", + "website": "https://comscore.com/" + }, + "57": { + "id": 57, + "name": "Cuebiq", + "creation_date": "2017-12-03", + "code_signature": "com.cuebiq.cuebiqsdk.model.Collector|com.cuebiq.cuebiqsdk.receiver.CoverageReceiver", + "network_signature": "cuebiq\\.com", + "website": "http://www.cuebiq.com/" + }, + "58": { + "id": 58, + "name": "HelpShift", + "creation_date": "2017-12-03", + "code_signature": "com.helpshift", + "network_signature": "helpshift\\.com", + "website": "https://www.helpshift.com" + }, + "59": { + "id": 59, + "name": "Kontakt", + "creation_date": "2017-12-03", + "code_signature": "com.kontakt.sdk.android.", + "network_signature": "kontakt\\.io", + "website": "https://kontakt.io/" + }, + "60": { + "id": 60, + "name": "Locuslabs", + "creation_date": "2017-12-03", + "code_signature": "com.locuslabs.sdk", + "network_signature": "locuslabs\\.com", + "website": "http://locuslabs.com" + }, + "61": { + "id": 61, + "name": "Moat", + "creation_date": "2017-12-03", + "code_signature": "com.moat.analytics.mobile.", + "network_signature": "apx\\.moatads\\.com|geo\\.moatads\\.com|js\\.moatads\\.com|mb\\.moatads\\.com|moat\\.com|pixel\\.moatads\\.com|px\\.moatads\\.com|sejs\\.moatads\\.com|yt\\.moatads\\.com|yts\\.moatads\\.com|z\\.moatads\\.com", + "website": "https://moat.com/analytics" + }, + "62": { + "id": 62, + "name": "Segment", + "creation_date": "2017-12-03", + "code_signature": "com.segment.analytics.", + "network_signature": "api\\.segment\\.io|segment\\.com", + "website": "https://segment.com/" + }, + "63": { + "id": 63, + "name": "Mobile Engagement", + "creation_date": "2017-12-04", + "code_signature": "com.ubikod.capptain.|com.microsoft.azure.engagement.", + "network_signature": "login\\.microsoftonline\\.com|management\\.azure\\.com", + "website": "https://docs.microsoft.com/en-us/azure/mobile-engagement/mobile-engagement-android-sdk-overview" + }, + "64": { + "id": 64, + "name": "Colocator", + "creation_date": "2017-12-04", + "code_signature": "net.crowdconnected.androidcolocator", + "network_signature": "colocator\\.net", + "website": "https://developers.colocator.net" + }, + "65": { + "id": 65, + "name": "Facebook Ads", + "creation_date": "2017-12-05", + "code_signature": "com.facebook.ads", + "network_signature": "\\.facebook\\.com", + "website": "https://developers.facebook.com/docs/android" + }, + "66": { + "id": 66, + "name": "Facebook Analytics", + "creation_date": "2017-12-05", + "code_signature": "com.facebook.appevents", + "network_signature": "\\.facebook\\.com", + "website": "https://developers.facebook.com/docs/android" + }, + "67": { + "id": 67, + "name": "Facebook Login", + "creation_date": "2017-12-05", + "code_signature": "com.facebook.login", + "network_signature": "\\.facebook\\.com", + "website": "https://developers.facebook.com/docs/android" + }, + "68": { + "id": 68, + "name": "Facebook Notifications", + "creation_date": "2017-12-05", + "code_signature": "com.facebook.notifications", + "network_signature": "\\.facebook\\.com", + "website": "https://developers.facebook.com/docs/android" + }, + "69": { + "id": 69, + "name": "Facebook Places", + "creation_date": "2017-12-05", + "code_signature": "com.facebook.places", + "network_signature": "\\.facebook\\.com", + "website": "https://developers.facebook.com/docs/android" + }, + "70": { + "id": 70, + "name": "Facebook Share", + "creation_date": "2017-12-05", + "code_signature": "com.facebook.share", + "network_signature": "\\.facebook\\.com", + "website": "https://developers.facebook.com/docs/android" + }, + "71": { + "id": 71, + "name": "Google Ads", + "creation_date": "2017-12-05", + "code_signature": "", + "network_signature": "\\.google\\.com", + "website": "https://developers.google.com/admob/android" + }, + "72": { + "id": 72, + "name": "AppLovin (MAX and SparkLabs)", + "creation_date": "2018-01-05", + "code_signature": "com.applovin", + "network_signature": "applovin\\.com|applvn\\.com", + "website": "https://www.applovin.com/" + }, + "73": { + "id": 73, + "name": "Glispa Connect (Formerly Avocarrot)", + "creation_date": "2018-01-05", + "code_signature": "com.avocarrot.sdk", + "network_signature": "\\.avocarrot\\.com|ads\\.glispa\\.com|exp\\.glispa\\.com|rtb\\.platform\\.glispa\\.com|templates\\.glispaconnect\\.com|trk\\.glispa\\.com", + "website": "https://www.glispa.com" + }, + "74": { + "id": 74, + "name": "NativeX", + "creation_date": "2018-01-05", + "code_signature": "com.nativex", + "network_signature": "nativex\\.com", + "website": "http://www.nativex.com/" + }, + "75": { + "id": 75, + "name": "Baidu Maps", + "creation_date": "2018-01-05", + "code_signature": "com.baidu.BaiduMap", + "network_signature": "map\\.baidu\\.com", + "website": "https://map.baidu.com" + }, + "76": { + "id": 76, + "name": "WeChat Location", + "creation_date": "2018-01-05", + "code_signature": "com.tencent.map.geolocation|com.tencent.mm.plugin.location.|com.tencent.mm.plugin.location_soso.|com.tencent.mm.plugin.location_google", + "network_signature": "map\\.qq\\.com", + "website": "https://wechat.com" + }, + "77": { + "id": 77, + "name": "HyperTrack", + "creation_date": "2018-01-05", + "code_signature": "com.hypertrack|com.hypertracklive.|io.hypertrack", + "network_signature": "api\\.hypertrack\\.com|hypertrack\\.amazonaws\\.com|trck\\.at", + "website": "http://hypertrack.com" + }, + "78": { + "id": 78, + "name": "Uber Analytics", + "creation_date": "2018-01-05", + "code_signature": "com.ubercab.analytics.|com.ubercab.library.metrics.analytics.|com.ubercab.client.core.analytics.", + "network_signature": "events\\.uber\\.com", + "website": "https://uber.com" + }, + "79": { + "id": 79, + "name": "Lisnr", + "creation_date": "2018-01-05", + "code_signature": "com.lisnr.", + "network_signature": "lisnr\\.com", + "website": "http://lisnr.com" + }, + "80": { + "id": 80, + "name": "SilverPush", + "creation_date": "2018-01-05", + "code_signature": "com.silverpush.|com.silverpush.location|com.silverpush.sdk.android.SPService", + "network_signature": "54\\.243\\.73\\.253:8080/SilverPush/|silverpush\\.co|silverpush\\.com", + "website": "http://silverpush.co" + }, + "81": { + "id": 81, + "name": "Shopkick", + "creation_date": "2018-01-05", + "code_signature": "com.shopkick.sdk.api.|com.shopkick.fetchers.", + "network_signature": "sdk\\.shopkick\\.com|shopkick\\.com|shopkick\\.de", + "website": "https://shopkick.com" + }, + "82": { + "id": 82, + "name": "Alphonso", + "creation_date": "2018-01-05", + "code_signature": "tv.alphonso.service", + "network_signature": "api\\.alphonso\\.tv|prov\\.alphonso\\.tv", + "website": "http://alphonso.tv" + }, + "83": { + "id": 83, + "name": "Smaato", + "creation_date": "2018-01-05", + "code_signature": "com.smaato.soma.", + "network_signature": "smaato\\.net|soma\\.smaato\\.net", + "website": "https://smaato.com" + }, + "84": { + "id": 84, + "name": "Scandit", + "creation_date": "2018-01-05", + "code_signature": "com.scandit.", + "network_signature": "scandit\\.com", + "website": "https://scandit.com" + }, + "85": { + "id": 85, + "name": "Inrix", + "creation_date": "2018-01-06", + "code_signature": "com.inrix.sdk", + "network_signature": "inrix\\.com|inrix\\.io", + "website": "http://inrix.com/" + }, + "86": { + "id": 86, + "name": "Signal360", + "creation_date": "2018-01-13", + "code_signature": "com.signal360.sdk.core.|com.sonicnotify.sdk.core.|com.rnsignal360", + "network_signature": "signal360\\.com|sonicnotify\\.com", + "website": "http://www.signal360.com" + }, + "87": { + "id": 87, + "name": "TeleQuid", + "creation_date": "2018-03-04", + "code_signature": "com.telequid.", + "network_signature": "mars\\.telequid\\.com", + "website": "http://www.telequid.com/" + }, + "88": { + "id": 88, + "name": "Retency", + "creation_date": "2018-03-04", + "code_signature": "com.retency.sdk.android", + "network_signature": "", + "website": "http://retency.com" + }, + "89": { + "id": 89, + "name": "MAdvertise", + "creation_date": "2018-03-04", + "code_signature": "com.mngads.sdk|com.mngads.views|com.mngads.", + "network_signature": "dispatcher\\.mng\\-ads\\.com|mobile\\.mng\\-ads\\.com", + "website": "http://madvertise.com" + }, + "90": { + "id": 90, + "name": "AdColony", + "creation_date": "2018-03-04", + "code_signature": "com.adcolony", + "network_signature": "adc3-launch\\.adcolony\\.com|adcolony\\.com|ads30\\.adcolony\\.com|androidads20\\.adcolony\\.com|androidads21\\.adcolony\\.com|androidads23\\.adcolony\\.com|events3alt\\.adcolony\\.com|wd\\.adcolony\\.com", + "website": "http://adcolony.com/" + }, + "91": { + "id": 91, + "name": "AccountKit", + "creation_date": "2018-03-04", + "code_signature": "com.facebook.accountkit", + "network_signature": "graph\\.accountkit\\.com", + "website": "https://www.accountkit.com/" + }, + "92": { + "id": 92, + "name": "Amazon Advertisement", + "creation_date": "2018-03-04", + "code_signature": "com.amazon.device.ads", + "network_signature": "", + "website": "https://developer.amazon.com/public/apis/earn/mobile-ads/docs/quick-start" + }, + "93": { + "id": 93, + "name": "Amazon Mobile Associates", + "creation_date": "2018-03-04", + "code_signature": "com.amazon.device.associates", + "network_signature": "", + "website": "https://developer.amazon.com/mobile-associates" + }, + "94": { + "id": 94, + "name": "Radius Networks", + "creation_date": "2018-03-04", + "code_signature": "com.radiusnetworks", + "network_signature": "proximitykit\\.radiusnetworks\\.com", + "website": "https://www.radiusnetworks.com/" + }, + "95": { + "id": 95, + "name": "Amazon Analytics (Amazon insights)", + "creation_date": "2018-03-04", + "code_signature": "com.amazon.insights|com.amazonaws.mobileconnectors.pinpoint.analytics.|com.amazonaws.mobileconnectors.amazonmobileanalytics", + "network_signature": "mobileanalytics\\.us-east-1\\.amazonaws\\.com", + "website": "https://developer.amazon.com/docs/apps-and-games/sdk-downloads.html" + }, + "96": { + "id": 96, + "name": "Baidu APPX", + "creation_date": "2018-03-04", + "code_signature": "com.baidu.appx", + "network_signature": "", + "website": "https://app.baidu.com/" + }, + "97": { + "id": 97, + "name": "Baidu Location", + "creation_date": "2018-03-04", + "code_signature": "com.baidu.location", + "network_signature": "", + "website": "https://developer.baidu.com/" + }, + "99": { + "id": 99, + "name": "Baidu Map", + "creation_date": "2018-03-04", + "code_signature": "com.baidu.mapapi", + "network_signature": "", + "website": "http://lbsyun.baidu.com/" + }, + "100": { + "id": 100, + "name": "Baidu Mobile Ads", + "creation_date": "2018-03-04", + "code_signature": "com.baidu.mobads", + "network_signature": "", + "website": "https://developer.baidu.com/" + }, + "101": { + "id": 101, + "name": "Baidu Mobile Stat", + "creation_date": "2018-03-04", + "code_signature": "com.baidu.mobstat", + "network_signature": "", + "website": "https://developer.baidu.com/" + }, + "102": { + "id": 102, + "name": "Estimote", + "creation_date": "2018-03-04", + "code_signature": "com.estimote.", + "network_signature": ".*\\.estimote\\.com", + "website": "https://estimote.com/" + }, + "103": { + "id": 103, + "name": "Baidu Navigation", + "creation_date": "2018-03-04", + "code_signature": "com.baidu.navi", + "network_signature": "", + "website": "http://lbsyun.baidu.com/index.php?title=android-navsdk" + }, + "104": { + "id": 104, + "name": "Fyber", + "creation_date": "2018-03-04", + "code_signature": "com.fyber.", + "network_signature": "adproxy\\.fyber\\.com|appengage-video\\.fyber\\.com|banner\\.fyber\\.com|engine\\.fyber\\.com|interstitial\\.fyber\\.com|mbe-cdn\\.fyber\\.com|offer\\.fyber\\.com|service\\.fyber\\.com|tracker\\.fyber\\.com|video-interstitial-assets-cdn\\.fyber\\.com|video\\.fyber\\.com", + "website": "https://www.fyber.com/" + }, + "105": { + "id": 105, + "name": "Google Tag Manager", + "creation_date": "2018-03-04", + "code_signature": "com.google.tagmanager|com.google.android.gms.tagmanager", + "network_signature": "www\\.googletagmanager\\.com|www\\.googletagservices\\.com", + "website": "https://www.google.com/analytics/tag-manager/" + }, + "106": { + "id": 106, + "name": "Inmobi", + "creation_date": "2018-03-04", + "code_signature": "com.inmobi", + "network_signature": "c\\.w\\.inmobi\\.com|china\\.inmobi\\.com|config-ltvp\\.inmobi\\.com|config\\.inmobi\\.com|et\\.w\\.inmobi\\.com|i\\.l\\.inmobicdn\\.net|i\\.w\\.inmobi\\.com|inmobi\\.cn|inmobi\\.com|inmobi\\.info|inmobi\\.net|inmobi\\.us|inmobicdn\\.com|inmobicdn\\.net|inmobisdk\\-a\\.akamaihd\\.net|japan\\.inmobi\\.com|r\\.w\\.inmobi\\.com|sdkm\\.w\\.inmobi\\.com|sdktm\\.w\\.inmobi\\.com|w\\.inmobi\\.com", + "website": "http://inmobi.com" + }, + "107": { + "id": 107, + "name": "Millennial Media", + "creation_date": "2018-03-04", + "code_signature": "com.millennialmedia.", + "network_signature": "adtech\\.de|contextual\\.media\\.net|media\\.net|millennialmedia\\.com", + "website": "https://www.millennialmedia.com/" + }, + "108": { + "id": 108, + "name": "Snowplow", + "creation_date": "2018-03-04", + "code_signature": "com.snowplowanalytics.", + "network_signature": "", + "website": "https://snowplowanalytics.com/" + }, + "109": { + "id": 109, + "name": "Fyber SponsorPay", + "creation_date": "2018-03-04", + "code_signature": "com.sponsorpay", + "network_signature": "appengage-video\\.sponsorpay\\.com|cdn1\\.sponsorpay\\.com|cdn2\\.sponsorpay\\.com|cdn3\\.sponsorpay\\.com|cdn4\\.sponsorpay\\.com|engine\\.sponsorpay\\.com", + "website": "http://www.sponsorpay.com" + }, + "110": { + "id": 110, + "name": "Supersonic Ads", + "creation_date": "2018-03-04", + "code_signature": "com.supersonic.adapters.supersonicads|com.supersonicads.sdk", + "network_signature": "click-haproxy\\.supersonicads\\.com|cx\\.ssacdn\\.com|init\\.supersonicads\\.com|logs\\.supersonic\\.com|outcome\\.supersonicads\\.com|ow-gateway\\.supersonicads\\.com|pixel-tracking\\.sonic-us\\.supersonicads\\.com|rv-gateway\\.supersonicads\\.com|static\\.ssacdn\\.com|supersonic\\.com|supersonicads-a\\.akamaihd\\.net|tag\\-mediation.supersonic.com|ua\\.supersonicads\\.com|v\\.ssacdn\\.com|www\\.supersonicads\\.com", + "website": "https://www.supersonic.com/" + }, + "111": { + "id": 111, + "name": "Carnival", + "creation_date": "2018-03-04", + "code_signature": "com.carnival.sdk|com.carnivalmobile", + "network_signature": "devices\\.carnivalmobile\\.com", + "website": "http://carnival.io/" + }, + "112": { + "id": 112, + "name": "Tencent Map LBS", + "creation_date": "2018-03-04", + "code_signature": "com.tencent.lbs", + "network_signature": "", + "website": "https://lbs.qq.com/" + }, + "113": { + "id": 113, + "name": "Tencent MobWin", + "creation_date": "2018-03-04", + "code_signature": "com.tencent.mobwin", + "network_signature": "", + "website": "https://www.tencent.com/en-us/" + }, + "114": { + "id": 114, + "name": "Tencent MTA", + "creation_date": "2018-03-04", + "code_signature": "com.tencent.mta", + "network_signature": "", + "website": "https://mta.qq.com/" + }, + "115": { + "id": 115, + "name": "Apptentive", + "creation_date": "2018-03-04", + "code_signature": "com.apptentive.", + "network_signature": "api\\.apptentive\\.com", + "website": "https://www.apptentive.com/" + }, + "116": { + "id": 116, + "name": "Tencent Stats", + "creation_date": "2018-03-04", + "code_signature": "com.tencent.stat|com.tencent.wxop.stat", + "network_signature": "", + "website": "http://stat.qq.com/" + }, + "117": { + "id": 117, + "name": "Tencent Weiyun", + "creation_date": "2018-03-04", + "code_signature": "com.tencent.weiyun", + "network_signature": "", + "website": "https://www.weiyun.com" + }, + "118": { + "id": 118, + "name": "MixPanel", + "creation_date": "2018-03-04", + "code_signature": "com.mixpanel.", + "network_signature": "api\\.mixpanel\\.com|decide\\.mixpanel\\.com|mixpanel\\.com|switchboard\\.mixpanel\\.com", + "website": "https://mixpanel.com/" + }, + "119": { + "id": 119, + "name": "Umeng Analytics", + "creation_date": "2018-03-04", + "code_signature": "com.umeng.analytics", + "network_signature": "alog\\.umeng\\.com|alogs\\.umeng\\.com|ar\\.umeng\\.com|oc\\.umeng\\.com|umeng\\.com|uop\\.umeng\\.com", + "website": "https://www.umeng.com/analytics" + }, + "120": { + "id": 120, + "name": "Umeng Feedback", + "creation_date": "2018-03-04", + "code_signature": "com.umeng.fb", + "network_signature": "alog\\.umeng\\.com|alogs\\.umeng\\.com|ar\\.umeng\\.com|oc\\.umeng\\.com|umeng\\.com|uop\\.umeng\\.com", + "website": "http://dev.umeng.com/feedback" + }, + "121": { + "id": 121, + "name": "Unity3d Ads", + "creation_date": "2018-03-04", + "code_signature": "com.unity3d.services|com.unity3d.ads", + "network_signature": "adserver\\.unityads\\.unity3d\\.com|analytics\\.social\\.unity\\.com|api\\.uca\\.cloud\\.unity3d\\.com|auction\\.unityads\\.unity3d\\.com|cdn-highwinds\\.unityads\\.unity3d\\.com|cdn\\.unityads\\.unity3d\\.com|config\\.uca\\.cloud\\.unity3d\\.com|config\\.unityads\\.unity3d\\.com|stats\\.unity3d\\.com|webview\\.unityads\\.unity3d\\.com", + "website": "https://unity3d.com/" + }, + "122": { + "id": 122, + "name": "Countly", + "creation_date": "2018-03-04", + "code_signature": "ly.count.android.", + "network_signature": "", + "website": "https://count.ly/" + }, + "123": { + "id": 123, + "name": "Urbanairship", + "creation_date": "2018-03-04", + "code_signature": "com.urbanairship", + "network_signature": "device-api\\.urbanairship\\.com|urbanairship\\.com", + "website": "https://www.urbanairship.com/" + }, + "124": { + "id": 124, + "name": "Yandex Ad", + "creation_date": "2018-03-04", + "code_signature": "com.yandex.mobile.ads", + "network_signature": "analytics\\.mobile\\.yandex\\.net|appmetrica\\.yandex\\.com|banners-slb\\.mobile\\.yandex\\.net|banners\\.mobile\\.yandex\\.net|mc\\.yandex\\.ru|report\\.appmetrica\\.yandex\\.net|startup\\.mobile\\.yandex\\.net", + "website": "https://www.yandex.com/" + }, + "125": { + "id": 125, + "name": "Amplitude", + "creation_date": "2018-03-04", + "code_signature": "com.amplitude.", + "network_signature": "amplitude\\.com|api\\.amplitude\\.com", + "website": "http://www.amplitude.com" + }, + "126": { + "id": 126, + "name": "AppSee", + "creation_date": "2018-03-04", + "code_signature": "com.appsee.", + "network_signature": "api\\.appsee\\.com", + "website": "https://www.appsee.com/" + }, + "127": { + "id": 127, + "name": "Kochava", + "creation_date": "2018-03-04", + "code_signature": "com.kochava.base.Tracker|com.kochava.android.tracker.", + "network_signature": "control\\.kochava\\.com|kvinit\\-prod\\.api\\.kochava\\.com", + "website": "https://www.kochava.com/" + }, + "129": { + "id": 129, + "name": "Webtrends", + "creation_date": "2018-03-04", + "code_signature": "com.webtrends.mobile.analytics.|com.webtrends.mobile.android", + "network_signature": "dc\\.webtrends\\.com|webtrends\\.com", + "website": "https://www.webtrends.com/" + }, + "130": { + "id": 130, + "name": "New Relic", + "creation_date": "2018-03-04", + "code_signature": "com.newrelic.agent.", + "network_signature": "js-agent\\.newrelic\\.com|mobile-collector\\.newrelic\\.com|newrelic\\.com|nr-data\\.net", + "website": "http://www.newrelic.com" + }, + "131": { + "id": 131, + "name": "AppAnalytics", + "creation_date": "2018-03-04", + "code_signature": "io.appanalytics.sdk", + "network_signature": "", + "website": "http://appanalytics.io/" + }, + "132": { + "id": 132, + "name": "Applause", + "creation_date": "2018-03-04", + "code_signature": "com.applause.android.", + "network_signature": "", + "website": "http://www.applause.com" + }, + "133": { + "id": 133, + "name": "Quantcast", + "creation_date": "2018-03-04", + "code_signature": "com.quantcast.measurement.service.", + "network_signature": "quantcast\\.com|quantcast\\.net", + "website": "http://www.quantcast.com" + }, + "135": { + "id": 135, + "name": "Apptimize", + "creation_date": "2018-03-04", + "code_signature": "com.apptimize.", + "network_signature": "brahe\\.apptimize\\.com|md-a-c\\.apptimize\\.com|md-a-s\\.apptimize\\.com", + "website": "http://www.apptimize.com" + }, + "136": { + "id": 136, + "name": "AppBrain", + "creation_date": "2018-03-04", + "code_signature": "com.appbrain.", + "network_signature": "sdk\\.appbrain\\.com", + "website": "https://www.appbrain.com/info/help/sdk/index.html" + }, + "137": { + "id": 137, + "name": "Dynatrace", + "creation_date": "2018-03-04", + "code_signature": "com.dynatrace.android.app|com.dynatrace.agent|com.dynatrace.tools", + "network_signature": "\\.dynatrace\\.com", + "website": "https://www.dynatrace.com" + }, + "138": { + "id": 138, + "name": "Matomo (Piwik)", + "creation_date": "2018-03-04", + "code_signature": "org.piwik|org.piwik.mobile|org.matomo", + "network_signature": "matomo\\.org", + "website": "https://matomo.org/mobile" + }, + "140": { + "id": 140, + "name": "AppMetrica", + "creation_date": "2018-03-04", + "code_signature": "com.yandex.metrica.", + "network_signature": "analytics\\.mobile\\.yandex\\.net|appmetrica\\.yandex\\.com|banners-slb\\.mobile\\.yandex\\.net|banners\\.mobile\\.yandex\\.net|mc\\.yandex\\.ru|report\\.appmetrica\\.yandex\\.net|startup\\.mobile\\.yandex\\.net", + "website": "https://tech.yandex.com/metrica-mobile-sdk/" + }, + "142": { + "id": 142, + "name": "Singlespot", + "creation_date": "2018-08-16", + "code_signature": "com.sptproximitykit.", + "network_signature": "singlespot\\.com", + "website": "https://www.singlespot.com/" + }, + "143": { + "id": 143, + "name": "Sensoro", + "creation_date": "2018-08-16", + "code_signature": "com.sensoro.beacon.kit.|com.sensoro.cloud", + "network_signature": "", + "website": "https://www.sensoro.com/" + }, + "144": { + "id": 144, + "name": "Sense360", + "creation_date": "2018-08-16", + "code_signature": "com.sense360.android.quinoa.lib.Sense360", + "network_signature": "android-quinoa-config-prod\\.sense360eng\\.com|incoming-data-sense360\\.s3\\.amazonaws\\.com|quinoa-personal-identify-prod\\.sense360eng\\.com", + "website": "https://sense360.com/" + }, + "145": { + "id": 145, + "name": "Rubicon Project", + "creation_date": "2018-08-16", + "code_signature": "com.rfm.sdk", + "network_signature": "ads\\.rubiconproject\\.com|fastlane\\.rubiconproject\\.com|optimized-by\\.rubiconproject\\.com|pixel\\.rubiconproject\\.com|stats\\.aws\\.rubiconproject\\.com|tap2-cdn\\.rubiconproject\\.com|video-ads\\.rubiconproject\\.com", + "website": "https://rubiconproject.com" + }, + "146": { + "id": 146, + "name": "ironSource", + "creation_date": "2018-08-16", + "code_signature": "com.ironsource.", + "network_signature": "", + "website": "https://www.ironsrc.com" + }, + "147": { + "id": 147, + "name": "Heyzap (bought by Fyber)", + "creation_date": "2018-08-16", + "code_signature": "com.heyzap.sdk.ads.", + "network_signature": "ads\\.heyzap\\.com|fyc\\.heyzap\\.com|img-cloudflare-2\\.haizap\\.com|img-cloudflare\\.haizap\\.com|med\\.heyzap\\.com|x\\.heyzap\\.com", + "website": "https://www.heyzap.com" + }, + "148": { + "id": 148, + "name": "Gigya", + "creation_date": "2018-08-16", + "code_signature": "com.gigya.", + "network_signature": "cdn1\\.gigya\\.com|cdn2\\.gigya\\.com|cdn3\\.gigya\\.com|cdn\\.gigya\\.com|cdns\\.us1\\.gigya\\.com", + "website": "https://www.gigya.com" + }, + "149": { + "id": 149, + "name": "Foresee", + "creation_date": "2018-08-16", + "code_signature": "com.foresee.sdk.ForeSee", + "network_signature": "4seeresults\\.com|analytics\\.foresee\\.com|foresee\\.com|foreseeresults\\.com|i\\.4see\\.mobi|rec\\.replay\\.answerscloud\\.com", + "website": "https://www.foresee.com" + }, + "150": { + "id": 150, + "name": "Fiksu", + "creation_date": "2018-08-16", + "code_signature": "com.fiksu.asotracking", + "network_signature": "a\\.fiksu\\.com|sdk\\.fiksu\\.com", + "website": "https://fiksu.com" + }, + "151": { + "id": 151, + "name": "Ensighten", + "creation_date": "2018-08-16", + "code_signature": "com.ensighten.", + "network_signature": "nexus\\.ensighten\\.com", + "website": "https://www.ensighten.com" + }, + "152": { + "id": 152, + "name": "Dynamic Yield", + "creation_date": "2018-08-16", + "code_signature": "com.dynamicyield.", + "network_signature": "adm\\.dynamicyield\\.com|api\\.dynamicyield\\.com|cdn\\.dynamicyield\\.com|px\\.dynamicyield\\.com|st\\.dynamicyield\\.com", + "website": "https://www.dynamicyield.com" + }, + "153": { + "id": 153, + "name": "BlueKai (acquired by Oracle)", + "creation_date": "2018-08-16", + "code_signature": "com.bluekai.sdk.", + "network_signature": "stags\\.bluekai\\.com|tags\\.bluekai\\.com", + "website": "http://bluekai.com/registry/" + }, + "154": { + "id": 154, + "name": "BlueConic", + "creation_date": "2018-08-16", + "code_signature": "com.blueconic", + "network_signature": "", + "website": "https://www.blueconic.com" + }, + "155": { + "id": 155, + "name": "Apteligent by VMWare (formerly Crittercism)", + "creation_date": "2018-08-16", + "code_signature": "com.crittercism.app.Crittercism", + "network_signature": "api\\.crittercism\\.com|appload\\.ingest\\.crittercism\\.com|txn\\.ingest\\.crittercism\\.com", + "website": "http://www.apteligent.com" + }, + "156": { + "id": 156, + "name": "AdFit (Daum)", + "creation_date": "2018-08-16", + "code_signature": "com.kakao.adfit.ads.", + "network_signature": "analytics\\.ad\\.daum\\.net|statistics\\.videofarm\\.daum\\.net", + "website": "https://www.daum.net" + }, + "157": { + "id": 157, + "name": "Adform", + "creation_date": "2018-08-16", + "code_signature": "com.adform.sdk.", + "network_signature": "adform\\.com|adformdsp\\.net|adx\\.adform\\.net|files\\.adform\\.net|track\\.adform\\.net", + "website": "https://site.adform.com" + }, + "158": { + "id": 158, + "name": "Adfurikun", + "creation_date": "2018-08-16", + "code_signature": "jp.tjkapp.adfurikunsdk.", + "network_signature": "adfurikun\\.jp|ginf\\.adfurikun\\.jp", + "website": "https://adfurikun.jp/adfurikun/" + }, + "159": { + "id": 159, + "name": "Mobvista", + "creation_date": "2019-03-10", + "code_signature": "com.mobvista.", + "network_signature": "mobvista\\.com", + "website": "https://www.mobvista.com/" + }, + "160": { + "id": 160, + "name": "Placed", + "creation_date": "2019-03-10", + "code_signature": "com.placed.client", + "network_signature": "", + "website": "http://placed.com/" + }, + "161": { + "id": 161, + "name": "Adot", + "creation_date": "2019-03-10", + "code_signature": "com.adotmob", + "network_signature": "sdk\\.adotmob\\.com|sync\\.adotmob\\.com|tracker\\.adotmob\\.com", + "website": "https://we-are-adot.com/" + }, + "162": { + "id": 162, + "name": "Appodeal", + "creation_date": "2019-03-10", + "code_signature": "com.appodeal.ads.", + "network_signature": "appodeal\\.com|appodealx\\.com", + "website": "https://www.appodeal.com" + }, + "163": { + "id": 163, + "name": "AppMonet", + "creation_date": "2019-03-10", + "code_signature": "com.monet.", + "network_signature": "", + "website": "http://appmonet.com" + }, + "164": { + "id": 164, + "name": "Soomla", + "creation_date": "2019-03-10", + "code_signature": "com.soomla.", + "network_signature": "soom\\.la", + "website": "https://soomla.com/" + }, + "165": { + "id": 165, + "name": "Adincube", + "creation_date": "2019-03-10", + "code_signature": "com.adincube.sdk.", + "network_signature": "sdk\\.adincube\\.com", + "website": "https://www.adincube.com/" + }, + "166": { + "id": 166, + "name": "Persona.ly", + "creation_date": "2019-03-10", + "code_signature": "ly.persona.sdk", + "network_signature": "dev-api\\.persona\\.ly|dev\\.dsp\\.persona\\.ly|dev\\.persona\\.ly|dsp\\.persona\\.ly|persona\\.ly|rtb\\.persona\\.ly|sdk\\.persona\\.ly", + "website": "http://persona.ly/" + }, + "167": { + "id": 167, + "name": "Branch", + "creation_date": "2019-03-10", + "code_signature": "io.branch.", + "network_signature": "api\\.branch\\.io", + "website": "https://branch.io/" + }, + "168": { + "id": 168, + "name": "Cheetah Ads", + "creation_date": "2019-03-10", + "code_signature": "com.cmcm.", + "network_signature": "cmcm\\.com", + "website": "https://www.cmcm.com/" + }, + "169": { + "id": 169, + "name": "Vungle", + "creation_date": "2019-03-10", + "code_signature": "com.vungle.publisher.", + "network_signature": "ads\\.api\\.vungle\\.com|akamai\\.vungle\\-cdn\\.vungle\\.com|api\\.vungle\\.akadns\\.net|api\\.vungle\\.com|bd\\.vungle\\.com|billboard\\.vungle\\.com|cdn\\-lb\\.vungle\\.com|ci\\.vungle\\.com|data\\.vungle\\.com|ingest\\.vungle\\.com|jaeger\\.vungle\\.com|ltv\\-data\\-api\\.kube\\-prod\\.vungle\\.com|monitoring\\.vungle\\.com|ssl\\.vungle\\.com|v\\.vungle\\.com", + "website": "https://vungle.com" + }, + "170": { + "id": 170, + "name": "Criteo", + "creation_date": "2019-03-10", + "code_signature": "com.criteo.", + "network_signature": "criteo\\.com", + "website": "https://www.criteo.com/" + }, + "171": { + "id": 171, + "name": "Mapbox", + "creation_date": "2019-03-10", + "code_signature": "com.mapbox.mapboxsdk.", + "network_signature": "a\\.tiles\\.mapbox\\.com|api\\.tiles\\.mapbox\\.com", + "website": "https://www.mapbox.com/" + }, + "172": { + "id": 172, + "name": "Optimizely", + "creation_date": "2019-03-10", + "code_signature": "com.optimizely.", + "network_signature": "optimizely\\.com", + "website": "https://www.optimizely.com/" + }, + "173": { + "id": 173, + "name": "Taboola", + "creation_date": "2019-03-10", + "code_signature": "com.taboola.", + "network_signature": "taboola\\.com", + "website": "https://www.taboola.com/" + }, + "174": { + "id": 174, + "name": "CleverTap", + "creation_date": "2019-03-10", + "code_signature": "com.clevertap.", + "network_signature": "wzrkt\\.com", + "website": "https://clevertap.com/" + }, + "175": { + "id": 175, + "name": "myTracker", + "creation_date": "2019-03-10", + "code_signature": "com.my.tracker.", + "network_signature": "tracker-api\\.my\\.com", + "website": "https://tracker.my.com/" + }, + "176": { + "id": 176, + "name": "Cloudmobi", + "creation_date": "2019-03-10", + "code_signature": "com.cloudtech.", + "network_signature": "api\\.cloudmobi\\.net|cloudmobi\\.net|logger\\.cloudmobi\\.net|vast\\.cloudmobi\\.net", + "website": "http://www.cloudmobi.net/" + }, + "177": { + "id": 177, + "name": "ADLIB", + "creation_date": "2019-03-10", + "code_signature": "com.mocoplex.adlib.", + "network_signature": "adlibr\\.com", + "website": "https://adlibr.com" + }, + "178": { + "id": 178, + "name": "Brightcove", + "creation_date": "2019-03-19", + "code_signature": "com.brightcove", + "network_signature": "metrics\\.brightcove\\.com", + "website": "https://www.brightcove.com" + }, + "179": { + "id": 179, + "name": "DOV-E", + "creation_date": "2019-03-19", + "code_signature": "com.dv.", + "network_signature": "\\.dov-e\\.com", + "website": "https://www.dov-e.com/" + }, + "180": { + "id": 180, + "name": "InMarket", + "creation_date": "2019-03-19", + "code_signature": "com.inmarket", + "network_signature": "m2m-api\\.inmarket\\.com", + "website": "https://inmarket.com/" + }, + "181": { + "id": 181, + "name": "Pilgrim by Foursquare", + "creation_date": "2019-03-19", + "code_signature": "com.foursquare.pilgrim|com.foursquare.pilgrimsdk.android", + "network_signature": "sdk\\.foursquare\\.com", + "website": "https://enterprise.foursquare.com/products/pilgrim" + }, + "182": { + "id": 182, + "name": "OtherLevels", + "creation_date": "2019-03-19", + "code_signature": "com.otherlevels.", + "network_signature": "api\\.otherlevels\\.com|geodata\\.otherlevels\\.com|mdn\\.otherlevels\\.com|rich\\.otherlevels\\.com|tags\\.otherlevels\\.com|ws\\.otherlevels\\.com", + "website": "https://www.otherlevels.com/" + }, + "183": { + "id": 183, + "name": "PubNative", + "creation_date": "2019-03-19", + "code_signature": "net.pubnative", + "network_signature": "pubnative\\.net", + "website": "https://pubnative.net/" + }, + "184": { + "id": 184, + "name": "Appnext", + "creation_date": "2019-03-19", + "code_signature": "com.appnext.", + "network_signature": "appnext.com", + "website": "https://www.appnext.com/" + }, + "185": { + "id": 185, + "name": "MobFox", + "creation_date": "2019-03-19", + "code_signature": "com.mobfox.", + "network_signature": "", + "website": "https://www.mobfox.com/" + }, + "186": { + "id": 186, + "name": "ShallWeAD", + "creation_date": "2019-03-19", + "code_signature": "com.jm.co.shallwead.sdk.|com.co.shallwead.sdk.", + "network_signature": "", + "website": "http://www.shallwead.com" + }, + "187": { + "id": 187, + "name": "deltaDNA", + "creation_date": "2019-03-19", + "code_signature": "com.deltadna", + "network_signature": "deltadna\\.net", + "website": "https://deltadna.com/" + }, + "188": { + "id": 188, + "name": "Display", + "creation_date": "2019-03-19", + "code_signature": "io.display.", + "network_signature": "display.io", + "website": "https://www.display.io/en/" + }, + "189": { + "id": 189, + "name": "HyprMX", + "creation_date": "2019-04-15", + "code_signature": "com.hyprmx.android.sdk.", + "network_signature": "hyprmx\\.com", + "website": "https://www.hyprmx.com" + }, + "190": { + "id": 190, + "name": "Bugly", + "creation_date": "2019-04-15", + "code_signature": "com.tencent.bugly.", + "network_signature": "bugly\\.qq\\.com", + "website": "https://bugly.qq.com/v2/" + }, + "191": { + "id": 191, + "name": "Duapps", + "creation_date": "2019-04-15", + "code_signature": "com.duapps.", + "network_signature": "duapps\\.com", + "website": "http://ad.duapps.com/" + }, + "192": { + "id": 192, + "name": "Swrve", + "creation_date": "2019-04-15", + "code_signature": "com.swrve.sdk", + "network_signature": "api\\.swrve\\.com|content\\.swrve\\.com", + "website": "https://www.swrve.com/" + }, + "193": { + "id": 193, + "name": "OneSignal", + "creation_date": "2019-04-15", + "code_signature": "com.onesignal.", + "network_signature": "onesignal\\.com", + "website": "https://onesignal.com/" + }, + "194": { + "id": 194, + "name": "Appdynamics", + "creation_date": "2019-04-15", + "code_signature": "com.appdynamics.", + "network_signature": "", + "website": "https://www.appdynamics.com/" + }, + "195": { + "id": 195, + "name": "Startapp", + "creation_date": "2019-04-15", + "code_signature": "com.startapp.android.publish", + "network_signature": "c2i\\.startappnetwork\\.com|c2s\\.startappnetwork\\.com|click\\.startappservice\\.com|dts\\.startappservice\\.com|events\\.startappservice\\.com|images\\.startappservice\\.com|imp\\.startappservice\\.com|info\\.static\\.startappservice\\.com|init\\.startappservice\\.com|req\\.startappservice\\.com|soda\\.startappservice\\.com|startappservice\\.com|va\\.origin\\.startappservice\\.com", + "website": "https://www.startapp.com" + }, + "196": { + "id": 196, + "name": "AerServ", + "creation_date": "2019-04-15", + "code_signature": "com.aerserv.sdk.", + "network_signature": "ads\\.aerserv\\.com|debug\\.aerserv\\.com", + "website": "https://www.aerserv.com/" + }, + "197": { + "id": 197, + "name": "INFOnline", + "creation_date": "2019-04-15", + "code_signature": "de.infonline.", + "network_signature": "de\\.ioam\\.de", + "website": "https://www.infonline.de" + }, + "198": { + "id": 198, + "name": "myTarget", + "creation_date": "2019-04-15", + "code_signature": "com.my.target.", + "network_signature": ".target\\.my\\.com", + "website": "https://target.my.com/" + }, + "199": { + "id": 199, + "name": "Tapjoy", + "creation_date": "2019-04-15", + "code_signature": "com.tapjoy.", + "network_signature": "tapjoy\\.com|tapjoyads\\.com|www\\.5rocks\\.io", + "website": "https://www.tapjoy.com/" + }, + "200": { + "id": 200, + "name": "Mintegral", + "creation_date": "2019-04-15", + "code_signature": "com.mintegral.", + "network_signature": "analytics\\.rayjump\\.com|cdn-adn\\.rayjump\\.com|de01\\.rayjump\\.com|de\\.rayjump\\.com|detect\\.rayjump\\.com|fk-mtrack\\.rayjump\\.com|hybird\\.rayjump\\.com|jssdk\\.rayjump\\.com|net\\.rayjump\\.com|online\\.rayjump\\.com|rayjump\\.com|setting\\.rayjump\\.com|sg-mtrack\\.rayjump\\.com|sg01\\.rayjump\\.com|sg\\.rayjump\\.com|tknet\\.rayjump\\.com|us01\\.rayjump\\.com", + "website": "https://www.mintegral.com/en/" + }, + "201": { + "id": 201, + "name": "Gimbal", + "creation_date": "2019-04-15", + "code_signature": "com.gimbal.android", + "network_signature": "analytics-server\\.gimbal\\.com|api\\.gimbal\\.com|registration\\.gimbal\\.com|sdk-info\\.gimbal\\.com", + "website": "https://gimbal.com/" + }, + "202": { + "id": 202, + "name": "Conviva", + "creation_date": "2019-04-15", + "code_signature": "com.conviva.", + "network_signature": "cws\\.conviva\\.com", + "website": "https://www.conviva.com/" + }, + "203": { + "id": 203, + "name": "Auditude", + "creation_date": "2019-05-02", + "code_signature": "com.auditude.ads", + "network_signature": "auditude\\.com", + "website": "https://www.adobe.com/privacy/policies/auditude.html" + }, + "204": { + "id": 204, + "name": "Instreamatic (Adman)", + "creation_date": "2019-05-02", + "code_signature": "com.instreamatic", + "network_signature": "instreamatic\\.com", + "website": "http://instreamatic.com/" + }, + "205": { + "id": 205, + "name": "GameAnalytics", + "creation_date": "2019-06-12", + "code_signature": "com.gameanalytics.sdk", + "network_signature": "", + "website": "https://gameanalytics.com/features" + }, + "206": { + "id": 206, + "name": "Instabug", + "creation_date": "2019-06-12", + "code_signature": "com.instabug.library.tracking|com.instabug.bug", + "network_signature": "", + "website": "https://instabug.com/crash-reporting" + }, + "207": { + "id": 207, + "name": "Bugsnag", + "creation_date": "2019-06-12", + "code_signature": "com.bugsnag.", + "network_signature": "", + "website": "https://www.bugsnag.com/" + }, + "208": { + "id": 208, + "name": "Moodmedia", + "creation_date": "2019-06-20", + "code_signature": "com.moodmedia", + "network_signature": "moodpresence\\.com", + "website": "https://us.moodmedia.com/" + }, + "209": { + "id": 209, + "name": "Houndify", + "creation_date": "2019-06-20", + "code_signature": "com.hound", + "network_signature": "houndify\\.com", + "website": "https://www.houndify.com/" + }, + "210": { + "id": 210, + "name": "OpenX", + "creation_date": "2019-06-20", + "code_signature": "com.openx.view.plugplay|com.openx.android_sdk_openx", + "network_signature": "openx\\.com|openx\\.net|openx\\.org|us-ads\\.openx\\.net", + "website": "https://www.openx.com/" + }, + "211": { + "id": 211, + "name": "Taplytics", + "creation_date": "2019-06-20", + "code_signature": "com.taplytics.sdk", + "network_signature": "api\\.taplytics\\.com|ping\\.tapylitics\\.com", + "website": "https://taplytics.com" + }, + "212": { + "id": 212, + "name": "Yinzcam Sobek", + "creation_date": "2019-06-20", + "code_signature": "com.yinzcam.sobek", + "network_signature": "analytics\\.yinzcam\\.com", + "website": "http://www.yinzcam.com/" + }, + "213": { + "id": 213, + "name": "Ooyala", + "creation_date": "2019-07-01", + "code_signature": "com.ooyala", + "network_signature": "ooyala\\.com", + "website": "https://www.ooyala.com/" + }, + "214": { + "id": 214, + "name": "Kiip", + "creation_date": "2019-08-20", + "code_signature": "me.kiip.sdk", + "network_signature": "kiip\\.me", + "website": "https://www.ninthdecimal.com/" + }, + "215": { + "id": 215, + "name": "MobPower", + "creation_date": "2019-08-20", + "code_signature": "com.mobpower.", + "network_signature": "api\\.mobpowertech\\.com|log\\.mobpowertech\\.com|mobpowertech\\.com|scheme\\.mobpowertech\\.com", + "website": "https://home.mobpowertech.com/" + }, + "216": { + "id": 216, + "name": "AdBuddiz", + "creation_date": "2019-08-20", + "code_signature": "com.purplebrain.adbuddiz.sdk.", + "network_signature": "sdk\\.adbuddiz\\.com", + "website": "http://www.adbuddiz.com/abuse?hl=fr" + }, + "218": { + "id": 218, + "name": "Integral Ad Science", + "creation_date": "2019-10-26", + "code_signature": "com.integralads.avid.library", + "network_signature": "adsafeprotected\\.com|iasds01\\.com|integralads\\.com", + "website": "https://integralads.com" + }, + "219": { + "id": 219, + "name": "AltBeacon", + "creation_date": "2019-10-26", + "code_signature": "org.altbeacon.beacon.|com.altbeacon.beacon.", + "network_signature": "", + "website": "https://altbeacon.org" + }, + "220": { + "id": 220, + "name": "Salesforce Marketing Cloud", + "creation_date": "2019-10-26", + "code_signature": "com.salesforce.marketingcloud", + "network_signature": "", + "website": "https://www.salesforce.com/products/marketing-cloud/" + }, + "221": { + "id": 221, + "name": "Mozilla Telemetry", + "creation_date": "2019-10-26", + "code_signature": "org.mozilla.telemetry|org.mozilla.gecko.telemetry", + "network_signature": "", + "website": "https://wiki.mozilla.org/Telemetry" + }, + "222": { + "id": 222, + "name": "nend", + "creation_date": "2019-10-26", + "code_signature": "net.nend.android", + "network_signature": "", + "website": "https://nend.net/en/" + }, + "223": { + "id": 223, + "name": "Pusher", + "creation_date": "2019-10-26", + "code_signature": "com.pusher.client.", + "network_signature": "", + "website": "https://pusher.com/" + }, + "224": { + "id": 224, + "name": "FreeWheel", + "creation_date": "2019-10-26", + "code_signature": "tv.freewheel.ad.", + "network_signature": "fwmrm\\.net", + "website": "http://freewheel.tv/" + }, + "225": { + "id": 225, + "name": "TNK Factory", + "creation_date": "2019-10-26", + "code_signature": "com.tnkfactory.ad", + "network_signature": "", + "website": "http://www.tnkfactory.com" + }, + "226": { + "id": 226, + "name": "Axonix", + "creation_date": "2019-10-26", + "code_signature": "com.axonix.android.sdk|com.mobclix.android.sdk", + "network_signature": "ads\\.mobclix\\.com|axonix\\.com|data\\.mobclix\\.com|mobclix\\.com|s\\.mobclix\\.com", + "website": "http://axonix.com/" + }, + "227": { + "id": 227, + "name": "Gemius HeatMap", + "creation_date": "2019-10-26", + "code_signature": "com.gemius.sdk", + "network_signature": "gemius\\.pl", + "website": "https://heatmap.gemius.com" + }, + "228": { + "id": 228, + "name": "YouAppi", + "creation_date": "2019-10-26", + "code_signature": "com.youappi.sdk.", + "network_signature": "", + "website": "https://www.youappi.com" + }, + "229": { + "id": 229, + "name": "Adobe Experience Cloud", + "creation_date": "2019-10-26", + "code_signature": "com.adobe.marketing.mobile", + "network_signature": "", + "website": "https://www.adobe.com/experience-cloud.html" + }, + "230": { + "id": 230, + "name": "Teads", + "creation_date": "2019-10-26", + "code_signature": "tv.teads.", + "network_signature": "teads\\.tv", + "website": "https://www.teads.tv" + }, + "231": { + "id": 231, + "name": "In Loco", + "creation_date": "2019-10-26", + "code_signature": "com.inlocomedia.android", + "network_signature": "inlocomedia\\.com", + "website": "https://inloco.com.br" + }, + "232": { + "id": 232, + "name": "IQzone", + "creation_date": "2019-10-26", + "code_signature": "com.iqzone", + "network_signature": "", + "website": "https://iqzone.com" + }, + "233": { + "id": 233, + "name": "Bugfender", + "creation_date": "2019-10-26", + "code_signature": "com.bugfender.sdk.", + "network_signature": "", + "website": "https://bugfender.com/" + }, + "234": { + "id": 234, + "name": "Wootric", + "creation_date": "2019-10-26", + "code_signature": "com.wootric.androidsdk.", + "network_signature": ".wootric\\.com\\.herokudns\\.com|wootric\\.com", + "website": "http://wootric.com" + }, + "235": { + "id": 235, + "name": "KIDOZ", + "creation_date": "2019-10-26", + "code_signature": "com.kidoz.sdk", + "network_signature": "", + "website": "https://kidoz.net/kidoz-sdk/" + }, + "236": { + "id": 236, + "name": "PubMatic", + "creation_date": "2019-10-26", + "code_signature": "com.pubmatic.sdk", + "network_signature": "ads\\.pubmatic\\.com|aktrack\\.pubmatic\\.com|gads\\.pubmatic\\.com|image2\\.pubmatic\\.com|simage2\\.pubmatic\\.com", + "website": "https://pubmatic.com/" + }, + "237": { + "id": 237, + "name": "Kissmetrics", + "creation_date": "2019-11-09", + "code_signature": "com.kissmetrics", + "network_signature": "", + "website": "https://www.kissmetricshq.com/" + }, + "238": { + "id": 238, + "name": "Microsoft Visual Studio App Center Crashes", + "creation_date": "2019-11-09", + "code_signature": "com.microsoft.appcenter.crashes", + "network_signature": "", + "website": "https://appcenter.ms/" + }, + "239": { + "id": 239, + "name": "Webtrekk", + "creation_date": "2019-11-09", + "code_signature": "com.webtrekk.webtrekksdk", + "network_signature": "", + "website": "https://www.webtrekk.com/" + }, + "240": { + "id": 240, + "name": "Google Analytics Plugin (Cordova)", + "creation_date": "2019-11-09", + "code_signature": "com.danielcwilson.plugins.analytics", + "network_signature": "", + "website": "https://analytics.withgoogle.com/" + }, + "241": { + "id": 241, + "name": "Bugsee", + "creation_date": "2019-11-09", + "code_signature": "com.bugsee.library.Bugsee", + "network_signature": "", + "website": "https://www.bugsee.com/" + }, + "242": { + "id": 242, + "name": "Splunk MINT", + "creation_date": "2019-11-09", + "code_signature": "com.splunk.mint", + "network_signature": "", + "website": "https://mint.splunk.com/" + }, + "243": { + "id": 243, + "name": "Microsoft Visual Studio App Center Analytics", + "creation_date": "2019-11-09", + "code_signature": "com.microsoft.appcenter.analytics|com.microsoft.azure.mobile.analytics", + "network_signature": "", + "website": "https://appcenter.ms/" + }, + "244": { + "id": 244, + "name": "Nielsen", + "creation_date": "2019-11-09", + "code_signature": "com.nielsen.app", + "network_signature": "", + "website": "https://www.nielsen.com" + }, + "245": { + "id": 245, + "name": "Reveal Mobile", + "creation_date": "2019-11-09", + "code_signature": "com.stepleaderdigital.reveal", + "network_signature": "", + "website": "https://revealmobile.com/" + }, + "246": { + "id": 246, + "name": "Metrics", + "creation_date": "2019-11-09", + "code_signature": "com.codahale.metrics", + "network_signature": "", + "website": "https://metrics.dropwizard.io" + }, + "247": { + "id": 247, + "name": "Repro", + "creation_date": "2019-11-09", + "code_signature": "io.repro.android.Repro", + "network_signature": "", + "website": "https://repro.io/" + }, + "248": { + "id": 248, + "name": "Sensors Analytics", + "creation_date": "2019-11-09", + "code_signature": "com.sensorsdata.analytics.android.sdk", + "network_signature": "", + "website": "https://www.sensorsdata.cn" + }, + "249": { + "id": 249, + "name": "Tenjin", + "creation_date": "2019-11-09", + "code_signature": "com.tenjin.android.TenjinSDK", + "network_signature": "", + "website": "https://www.tenjin.com/" + }, + "250": { + "id": 250, + "name": "Tapstream", + "creation_date": "2019-11-09", + "code_signature": "com.tapstream.sdk", + "network_signature": "", + "website": "https://www.tapstream.com/" + }, + "251": { + "id": 251, + "name": "Singular", + "creation_date": "2019-11-09", + "code_signature": "com.singular.sdk", + "network_signature": "", + "website": "https://singular.net/" + }, + "252": { + "id": 252, + "name": "CallDorado", + "creation_date": "2019-11-09", + "code_signature": "com.calldorado.android", + "network_signature": "", + "website": "http://calldorado.com" + }, + "253": { + "id": 253, + "name": "UXCam", + "creation_date": "2019-11-09", + "code_signature": "com.uxcam.UXCam", + "network_signature": "", + "website": "https://uxcam.com/" + }, + "254": { + "id": 254, + "name": "Upsight", + "creation_date": "2019-11-09", + "code_signature": "com.upsight.android", + "network_signature": "", + "website": "https://www.upsight.com/" + }, + "255": { + "id": 255, + "name": "Appcelerator Analytics", + "creation_date": "2019-11-09", + "code_signature": "com.appcelerator.aps.|org.appcelerator.titanium.analytics", + "network_signature": "appcelerator\\.com|appcelerator\\.net", + "website": "https://www.appcelerator.com" + }, + "256": { + "id": 256, + "name": "Adbrix", + "creation_date": "2019-11-09", + "code_signature": "com.igaworks.adbrix", + "network_signature": "ad-brix\\.com", + "website": "http://ad-brix.com/" + }, + "257": { + "id": 257, + "name": "Cauly", + "creation_date": "2019-11-09", + "code_signature": "com.fsn.cauly|com.trid.tridad", + "network_signature": "ad\\.cauly\\.co\\.kr", + "website": "https://www.cauly.net" + }, + "258": { + "id": 258, + "name": "Tapdaq", + "creation_date": "2019-11-09", + "code_signature": "com.tapdaq.sdk.", + "network_signature": "ads\\.tapdaq\\.com", + "website": "https://www.tapdaq.com/" + }, + "259": { + "id": 259, + "name": "Verve", + "creation_date": "2019-11-09", + "code_signature": "com.vervewireless.advert.", + "network_signature": "", + "website": "https://www.verve.com" + }, + "260": { + "id": 260, + "name": "Apsalar", + "creation_date": "2019-11-09", + "code_signature": "com.apsalar.sdk.", + "network_signature": "e-ssl\\.apsalar\\.com|e\\.apsalar\\.com", + "website": "https://singular.net" + }, + "261": { + "id": 261, + "name": "PingStart", + "creation_date": "2019-11-09", + "code_signature": "com.pingstart.adsdk.", + "network_signature": "api\\.pingstart\\.com", + "website": "http://pingstart.com" + }, + "262": { + "id": 262, + "name": "Keen", + "creation_date": "2019-11-09", + "code_signature": "io.keen.client.", + "network_signature": "api\\.keen\\.io", + "website": "https://keen.io" + }, + "263": { + "id": 263, + "name": "Revmob", + "creation_date": "2020-01-02", + "code_signature": "com.revmob.ads.", + "network_signature": "", + "website": "https://www.crunchbase.com/organization/revmob" + }, + "264": { + "id": 264, + "name": "Emarsys Predict", + "creation_date": "2020-01-02", + "code_signature": "com.emarsys.predict", + "network_signature": "recommender\\.scarabresearch\\.com", + "website": "https://help.emarsys.com/hc/categories/115000670425-Predict" + }, + "265": { + "id": 265, + "name": "Lotame", + "creation_date": "2020-01-02", + "code_signature": "com.lotame.android", + "network_signature": "ad\\.crwdcntrl\\.net", + "website": "https://www.lotame.com/" + }, + "266": { + "id": 266, + "name": "FollowAnalytics", + "creation_date": "2020-01-02", + "code_signature": "com.followanalytics.", + "network_signature": "sdk\\.follow-apps\\.com", + "website": "https://www.followanalytics.com" + }, + "267": { + "id": 267, + "name": "Chartbeat", + "creation_date": "2020-01-02", + "code_signature": "com.chartbeat.androidsdk", + "network_signature": ".chartbeat\\.com|.chartbeat\\.net", + "website": "https://chartbeat.com/" + }, + "268": { + "id": 268, + "name": "MoEngage", + "creation_date": "2020-01-02", + "code_signature": "com.moengage.", + "network_signature": "apiv2\\.moengage\\.com", + "website": "https://www.moengage.com/" + }, + "269": { + "id": 269, + "name": "Altamob", + "creation_date": "2020-01-02", + "code_signature": "com.altamob.sdk", + "network_signature": "api\\.altamob\\.com", + "website": "https://www.altamob.com/en/index.html" + }, + "270": { + "id": 270, + "name": "Tealeaf", + "creation_date": "2020-02-25", + "code_signature": "com.tl.uic.Tealeaf", + "network_signature": "", + "website": "https://acoustic.co/products/experience-analytics/" + }, + "271": { + "id": 271, + "name": "AMoAd", + "creation_date": "2020-02-25", + "code_signature": "com.amoad.", + "network_signature": "", + "website": "https://www.amoad.com" + }, + "272": { + "id": 272, + "name": "AdAdapted", + "creation_date": "2020-02-25", + "code_signature": "com.adadapted.android.sdk.", + "network_signature": "ads\\.adadapted\\.com", + "website": "https://www.adadapted.com" + }, + "273": { + "id": 273, + "name": "AdMuing", + "creation_date": "2020-02-25", + "code_signature": "com.admuing.danmaku.", + "network_signature": "", + "website": "https://github.com/admuing" + }, + "274": { + "id": 274, + "name": "Adcash", + "creation_date": "2020-02-25", + "code_signature": "com.adcash.mobileads.", + "network_signature": "", + "website": "https://adcash.com" + }, + "275": { + "id": 275, + "name": "Admixer", + "creation_date": "2020-02-25", + "code_signature": "com.admixer|net.admixer.sdk", + "network_signature": "admixer\\.co\\.kr", + "website": "http://admixer.co.kr/" + }, + "276": { + "id": 276, + "name": "Admost", + "creation_date": "2020-02-25", + "code_signature": "admost.sdk.|admost.adserver.ads.", + "network_signature": "cdn\\-api\\.admost\\.com|go\\.admost\\.com|med\\-api\\.admost\\.com", + "website": "https://admost.com/" + }, + "277": { + "id": 277, + "name": "Alohalytics", + "creation_date": "2020-02-25", + "code_signature": "org.alohalytics.", + "network_signature": "", + "website": "https://github.com/biodranik/Alohalytics" + }, + "278": { + "id": 278, + "name": "Amobee", + "creation_date": "2020-02-25", + "code_signature": "com.amobee.", + "network_signature": "amobee\\.com", + "website": "https://amobee.com" + }, + "279": { + "id": 279, + "name": "Anagog", + "creation_date": "2020-02-25", + "code_signature": "com.anagog.jedai", + "network_signature": "", + "website": "https://anagog.com" + }, + "280": { + "id": 280, + "name": "Bazaarvoice", + "creation_date": "2020-02-25", + "code_signature": "com.bazaarvoice.bvandroidsdk", + "network_signature": "", + "website": "https://www.bazaarvoice.com/" + }, + "281": { + "id": 281, + "name": "BeaconsInSpace (Fysical)", + "creation_date": "2020-02-25", + "code_signature": "com.beaconsinspace.android.beacon.detector.", + "network_signature": "", + "website": "https://beaconsinspace.com" + }, + "282": { + "id": 282, + "name": "Conversant", + "creation_date": "2020-02-25", + "code_signature": "com.conversantmedia", + "network_signature": "conversantmedia\\.com", + "website": "https://www.conversantmedia.com" + }, + "283": { + "id": 283, + "name": "Glympse", + "creation_date": "2020-02-25", + "code_signature": "com.glympse.android.", + "network_signature": "\\.glympse\\.com", + "website": "https://glympse.com" + }, + "284": { + "id": 284, + "name": "Herow", + "creation_date": "2020-02-25", + "code_signature": "com.connecthings.herow", + "network_signature": "", + "website": "https://herow.io/" + }, + "285": { + "id": 285, + "name": "Placer", + "creation_date": "2020-02-25", + "code_signature": "com.placer.client.Placer", + "network_signature": "", + "website": "https://placer.io/" + }, + "286": { + "id": 286, + "name": "PushSpring", + "creation_date": "2020-02-25", + "code_signature": "com.pushspring.sdk.PushSpring", + "network_signature": "api\\.pushspring\\.com", + "website": "http://www.pushspring.com/" + }, + "287": { + "id": 287, + "name": "Pyze", + "creation_date": "2020-02-25", + "code_signature": "com.pyze.", + "network_signature": "pyze\\.com", + "website": "https://pyze.com/" + }, + "288": { + "id": 288, + "name": "Radar", + "creation_date": "2020-02-25", + "code_signature": "io.radar.sdk.Radar", + "network_signature": "", + "website": "https://radar.io/" + }, + "289": { + "id": 289, + "name": "Roximity", + "creation_date": "2020-02-25", + "code_signature": "com.roximity.sdk.", + "network_signature": "app\\.roximity\\.com", + "website": "http://roximity.com/" + }, + "290": { + "id": 290, + "name": "Sentiance", + "creation_date": "2020-02-25", + "code_signature": "com.sentiance.sdk.", + "network_signature": "api\\.sentiance\\.com", + "website": "https://www.sentiance.com/" + }, + "291": { + "id": 291, + "name": "SmartLook", + "creation_date": "2020-02-25", + "code_signature": "com.smartlook.sdk.smartlook.", + "network_signature": "smartlook\\.com", + "website": "https://www.smartlook.com/" + }, + "292": { + "id": 292, + "name": "Square Metrics", + "creation_date": "2020-02-25", + "code_signature": "com.beaconinside.proximitysdk.ProximityService", + "network_signature": "api\\.beaconinside\\.com", + "website": "https://www.squaremetrics.com" + }, + "293": { + "id": 293, + "name": "TalkingData", + "creation_date": "2020-02-25", + "code_signature": "com.talkingdata.sdk.|com.tendcloud.tenddata.|com.talkingdata.appanalytics.|com.talkingdata.adtracking.|com.tendcloud.appcpa.|com.apptalkingdata.push.|com.gametalkingdata.push.", + "network_signature": "account\\.talkingdata\\.com|av1\\.xdrig\\.com|cloud\\.xdrig\\.com|m\\.talkingdata\\.com|push\\.xdrig\\.com", + "website": "https://www.talkingdata.com/" + }, + "294": { + "id": 294, + "name": "flymob", + "creation_date": "2020-02-25", + "code_signature": "com.flymob.sdk.", + "network_signature": "", + "website": "https://flymob.com/" + }, + "295": { + "id": 295, + "name": "AdFalcon", + "creation_date": "2020-06-08", + "code_signature": "com.noqoush.adfalcon.android.sdk", + "network_signature": "", + "website": "http://adfalcon.com" + }, + "296": { + "id": 296, + "name": "Bitly", + "creation_date": "2020-06-08", + "code_signature": "com.bitly.Bitly", + "network_signature": "", + "website": "https://bitly.com/" + }, + "297": { + "id": 297, + "name": "Enhance", + "creation_date": "2020-06-08", + "code_signature": "co.enhance.Enhance", + "network_signature": "app-config\\.enhance\\.co|data-location\\.enhance\\.co", + "website": "https://enhance.co" + }, + "298": { + "id": 298, + "name": "Esri ArcGIS", + "creation_date": "2020-06-08", + "code_signature": "com.esri.arcgisruntime.", + "network_signature": "", + "website": "https://www.arcgis.com/" + }, + "299": { + "id": 299, + "name": "GIPHY Analytics", + "creation_date": "2020-06-08", + "code_signature": "com.giphy.sdk.analytics", + "network_signature": "api\\.giphy\\.com|pingback\\.giphy\\.com", + "website": "https://giphy.com/" + }, + "300": { + "id": 300, + "name": "Heap", + "creation_date": "2020-06-08", + "code_signature": "com.heapanalytics", + "network_signature": "heapanalytics.com", + "website": "https://heap.io/" + }, + "301": { + "id": 301, + "name": "Inneractive", + "creation_date": "2020-06-08", + "code_signature": "com.inneractive.api.ads", + "network_signature": "", + "website": "https://www.crunchbase.com/organization/inneractive" + }, + "302": { + "id": 302, + "name": "MDOTM", + "creation_date": "2020-06-08", + "code_signature": "com.mdotm.android", + "network_signature": "ads\\.mdotm\\.com", + "website": "http://mdotm.com/" + }, + "303": { + "id": 303, + "name": "Metaps", + "creation_date": "2020-06-08", + "code_signature": "com.metaps", + "network_signature": "", + "website": "http://www.metaps.com" + }, + "304": { + "id": 304, + "name": "Parse.ly", + "creation_date": "2020-06-08", + "code_signature": "com.parsely.parselyandroid", + "network_signature": "", + "website": "https://www.parse.ly/" + }, + "305": { + "id": 305, + "name": "Pollfish", + "creation_date": "2020-06-08", + "code_signature": "com.pollfish", + "network_signature": "", + "website": "https://www.pollfish.com" + }, + "306": { + "id": 306, + "name": "Qualtrics", + "creation_date": "2020-06-08", + "code_signature": "com.qualtrics.digital.", + "network_signature": "qualtrics\\.com", + "website": "http://www.qualtrics.com/" + }, + "307": { + "id": 307, + "name": "Tamoco", + "creation_date": "2020-06-08", + "code_signature": "com.tamoco.sdk", + "network_signature": "evt\\.tamoco\\.com", + "website": "https://www.tamoco.com/" + }, + "308": { + "id": 308, + "name": "Vpon", + "creation_date": "2020-06-08", + "code_signature": "com.vpon.ads", + "network_signature": "", + "website": "https://www.vpon.com/" + }, + "309": { + "id": 309, + "name": "YuMe", + "creation_date": "2020-06-08", + "code_signature": "com.yume.android", + "network_signature": "", + "website": "https://www.appbrain.com/stats/libraries/details/yume/yume" + }, + "310": { + "id": 310, + "name": "Zapr", + "creation_date": "2020-06-08", + "code_signature": "com.redbricklane.zapr", + "network_signature": ".zapr\\.in", + "website": "https://www.zapr.in/" + }, + "311": { + "id": 311, + "name": "mediba", + "creation_date": "2020-06-08", + "code_signature": "com.mediba.jp|mediba.ad.sdk.android.openx", + "network_signature": "", + "website": "https://www.mediba.jp" + }, + "312": { + "id": 312, + "name": "Google AdMob", + "creation_date": "2020-06-24", + "code_signature": "com.google.ads.|com.google.android.gms.ads.AdView|com.google.android.gms.ads.AdActivity|com.google.android.gms.ads.AdRequest|com.google.android.gms.ads.mediation|com.google.android.gms.ads.doubleclick", + "network_signature": "2mdn\\.net|\\.google\\.com|dmtry\\.com|doubleclick\\.com|doubleclick\\.net|mng-ads\\.com", + "website": "https://admob.google.com" + }, + "313": { + "id": 313, + "name": "Unacast Pure", + "creation_date": "2020-07-13", + "code_signature": "com.pure.internal.|com.pure.sdk.", + "network_signature": "", + "website": "https://www.unacast.com/" + }, + "314": { + "id": 314, + "name": "Factual", + "creation_date": "2020-09-19", + "code_signature": "com.factual.engine", + "network_signature": "api.factual.com", + "website": "https://www.factual.com" + }, + "315": { + "id": 315, + "name": "Footmarks", + "creation_date": "2020-09-19", + "code_signature": "com.footmarks.footmarkssdkm2", + "network_signature": "", + "website": "https://www.footmarks.com" + }, + "316": { + "id": 316, + "name": "OzTAM", + "creation_date": "2020-09-19", + "code_signature": "au.com.oztam.", + "network_signature": "deliver.oztam.com.au", + "website": "https://oztam.com.au/" + }, + "317": { + "id": 317, + "name": "Receptiv (formerly Mediabrix)", + "creation_date": "2020-09-19", + "code_signature": "com.mediabrix.android", + "network_signature": "", + "website": "https://www.receptiv.com/" + }, + "318": { + "id": 318, + "name": "Tutela", + "creation_date": "2020-09-19", + "code_signature": "com.tutelatechnologies.sdk", + "network_signature": "", + "website": "https://www.tutela.com/" + }, + "319": { + "id": 319, + "name": "Twine Data", + "creation_date": "2020-09-19", + "code_signature": "com.twine.sdk", + "network_signature": "", + "website": "https://www.truedata.co/" + }, + "320": { + "id": 320, + "name": "Verizon Ads", + "creation_date": "2020-09-19", + "code_signature": "com.verizon.ads", + "network_signature": "", + "website": "https://www.verizonmedia.com/" + }, + "321": { + "id": 321, + "name": "adPOPcorn", + "creation_date": "2020-09-19", + "code_signature": "com.igaworks.adpopcorn", + "network_signature": "", + "website": "https://adpopcorn.com/" + }, + "322": { + "id": 322, + "name": "maio by i-mobile", + "creation_date": "2020-09-19", + "code_signature": "jp.maio.sdk.android.", + "network_signature": "", + "website": "https://adpf-info.i-mobile.co.jp/en/" + }, + "323": { + "id": 323, + "name": "360Dialog", + "creation_date": "2020-09-19", + "code_signature": "com.threesixtydialog.sdk.", + "network_signature": "", + "website": "https://www.360dialog.com" + }, + "324": { + "id": 324, + "name": "ABTasty", + "creation_date": "2020-09-19", + "code_signature": "com.abtasty", + "network_signature": "abtasty\\.com", + "website": "https://www.abtasty.com" + }, + "325": { + "id": 325, + "name": "ACRCloud", + "creation_date": "2020-09-19", + "code_signature": "com.acrcloud", + "network_signature": "acrcloud.com|hb-minify-juc1ugur1qwqqqo4.stackpathdns.com", + "website": "https://acrcloud.com/" + }, + "326": { + "id": 326, + "name": "Aarki", + "creation_date": "2020-09-19", + "code_signature": "com.aarki", + "network_signature": "", + "website": "https://www.aarki.com" + }, + "327": { + "id": 327, + "name": "Actv8me", + "creation_date": "2020-09-19", + "code_signature": "me.actv8", + "network_signature": "actv8technologies\\.com", + "website": "https://www.actv8me.com/" + }, + "328": { + "id": 328, + "name": "IAB Open Measurement", + "creation_date": "2020-09-19", + "code_signature": "com.iab.omid.library", + "network_signature": "", + "website": "https://iabtechlab.com/" + }, + "333": { + "id": 333, + "name": "Huawei Mobile Services (HMS) Core", + "creation_date": "2020-11-25", + "code_signature": "com.huawei.hms.analytics|com.huawei.hms.location|com.huawei.hms.plugin.analytics", + "network_signature": "", + "website": "https://developer.huawei.com/consumer/en/hms" + }, + "334": { + "id": 334, + "name": "Akamai MAP", + "creation_date": "2020-11-27", + "code_signature": "com.akamai.android.sdk.AkaMap", + "network_signature": "", + "website": "https://www.akamai.com/" + }, + "336": { + "id": 336, + "name": "Mail.ru", + "creation_date": "2020-11-27", + "code_signature": "ru.mail.mrgservice.advertising|ru.mail.mrgservice.analytics", + "network_signature": "", + "website": "http://mail.ru" + }, + "337": { + "id": 337, + "name": "Airpush", + "creation_date": "2020-12-13", + "code_signature": "com.airpush.", + "network_signature": "api\\.airpush\\.com|apidm\\.airpush\\.com|apistaging\\.airpush\\.com|apportal\\.airpush\\.com|appwall\\.api\\.airpush\\.com|beta\\.airpush\\.com|cdnap\\.airpush\\.com|m\\.airpush\\.com", + "website": "https://airpush.com/" + }, + "338": { + "id": 338, + "name": "Alimama (formerly AdsMogo)", + "creation_date": "2020-12-13", + "code_signature": "com.adsmogo.|com.alimama.", + "network_signature": "\\.alimama\\.|\\.adsmogo\\.", + "website": "https://www.alimama.com/" + }, + "339": { + "id": 339, + "name": "AnySDK", + "creation_date": "2020-12-13", + "code_signature": "com.anysdk.framework.AnalyticsWrapper|com.anysdk.framework.AdsWrapper", + "network_signature": "", + "website": "http://www.anysdk.com/" + }, + "340": { + "id": 340, + "name": "Button", + "creation_date": "2020-12-13", + "code_signature": "com.usebutton.sdk.", + "network_signature": "\\.usebutton.com", + "website": "https://www.usebutton.com" + }, + "341": { + "id": 341, + "name": "Carto (formerly Nutiteq)", + "creation_date": "2020-12-13", + "code_signature": "com.nutiteq|com.carto", + "network_signature": "", + "website": "https://carto.com" + }, + "342": { + "id": 342, + "name": "Didomi", + "creation_date": "2020-12-13", + "code_signature": "io.didomi.sdk.Didomi", + "network_signature": "", + "website": "https://www.didomi.io/" + }, + "343": { + "id": 343, + "name": "JiGuang Aurora Mobile JPush", + "creation_date": "2020-12-13", + "code_signature": "cn.jpush.android", + "network_signature": ".*\\.jiguang\\.cn", + "website": "https://ir.jiguang.cn/corporate-profile" + }, + "344": { + "id": 344, + "name": "Jumio", + "creation_date": "2020-12-13", + "code_signature": "com.jumio.MobileSDK", + "network_signature": "mobile-sdk-resources.jumio.com|nv-sdk.jumio.com", + "website": "https://www.jumio.com/" + }, + "345": { + "id": 345, + "name": "Lenddo", + "creation_date": "2020-12-13", + "code_signature": "com.lenddo.mobile", + "network_signature": "\\.partner-service\\.link", + "website": "https://www.lenddo.com/" + }, + "346": { + "id": 346, + "name": "POKKT", + "creation_date": "2020-12-13", + "code_signature": "com.pokkt.sdk.", + "network_signature": "", + "website": "https://www.pokkt.com" + }, + "347": { + "id": 347, + "name": "Prebid Mobile", + "creation_date": "2020-12-13", + "code_signature": "org.prebid.mobile", + "network_signature": ".prebid.org", + "website": "https://prebid.org" + }, + "348": { + "id": 348, + "name": "SK planet Tad", + "creation_date": "2020-12-13", + "code_signature": "com.skplanet.tad", + "network_signature": "", + "website": "https://www.skplanet.com/eng" + }, + "349": { + "id": 349, + "name": "Split", + "creation_date": "2020-12-13", + "code_signature": "io.split.android.", + "network_signature": "event.split.io|sdk.split.io", + "website": "https://www.split.io/" + }, + "350": { + "id": 350, + "name": "Exponea", + "creation_date": "2021-01-24", + "code_signature": "com.infinario.android.infinariosdk.", + "network_signature": "api.infinario.com|sygic-api.infinario.com", + "website": "https://exponea.com" + }, + "351": { + "id": 351, + "name": "IPQualityScore", + "creation_date": "2021-01-24", + "code_signature": "com.ipqualityscore.", + "network_signature": "ipqualityscore\\.com", + "website": "https://www.ipqualityscore.com" + }, + "352": { + "id": 352, + "name": "Opensignal", + "creation_date": "2021-01-24", + "code_signature": "com.opensignal.datacollection.OpenSignalNdcSdk", + "network_signature": "", + "website": "https://www.opensignal.com" + }, + "353": { + "id": 353, + "name": "SignalFrame", + "creation_date": "2021-01-24", + "code_signature": "com.wirelessregistry.observersdk.", + "network_signature": "", + "website": "https://signalframe.com/" + }, + "354": { + "id": 354, + "name": "X-Mode", + "creation_date": "2021-01-24", + "code_signature": "io.xmode.BcnConfig|io.xmode.locationsdk|io.mysdk.", + "network_signature": "api\\.myendpoint\\.io|bin5y4muil\\.execute-api\\.us-east-1\\.amazonaws\\.com|api\\.smartechmetrics\\.com", + "website": "https://xmode.io/" + } + } +] \ No newline at end of file diff --git a/app/src/main/assets/installers.json b/app/src/main/assets/installers.json new file mode 100755 index 000000000..315f0f798 --- /dev/null +++ b/app/src/main/assets/installers.json @@ -0,0 +1,28 @@ +[ + { + "id": "0", + "title": "Session installer", + "subtitle": "Session based installer for bundled/split APKs", + "description": "Best suited for devices running Android 5.0+.", + "url": "https://developer.android.com/reference/android/content/pm/PackageInstaller.Session" + }, + { + "id": "1", + "title": "Native installer", + "subtitle": "Intent based installer, available on all devices", + "description": "Best suited for devices running below Android 4.4 or OEM modified ROMs like MIUI, One-UI.", + "url": "https://developer.android.com/reference/android/content/Intent#setDataAndType" + }, + { + "id": "2", + "title": "Root installer", + "subtitle": "Installer for background installations", + "description": "Requires root, supports all Android versions." + }, + { + "id": "3", + "title": "Aurora Service", + "subtitle": "Installer for background installations", + "description": "Requires Aurora Services to be installed as system app." + } +] \ No newline at end of file diff --git a/app/src/main/assets/themes.json b/app/src/main/assets/themes.json new file mode 100755 index 000000000..6b5beb7c5 --- /dev/null +++ b/app/src/main/assets/themes.json @@ -0,0 +1,32 @@ +[ + { + "id": "0", + "title": "System", + "subtitle": "Follow system themes." + }, + { + "id": "1", + "title": "Light", + "subtitle": "White UI must die, may slap on your face at night." + }, + { + "id": "2", + "title": "Dark", + "subtitle": "As dark as your humour, suitable for night-owls." + }, + { + "id": "3", + "title": "Pitch Black", + "subtitle": "The black, that matters." + }, + { + "id": "4", + "title": "Dark-X", + "subtitle": "The dark, that looks cool?" + }, + { + "id": "5", + "title": "Disskord", + "subtitle": "Kanged from discord" + } +] \ No newline at end of file diff --git a/app/src/main/ic_launcher-playstore.png b/app/src/main/ic_launcher-playstore.png new file mode 100644 index 0000000000000000000000000000000000000000..92a70daffcf2c6406d69aece8fb92952f02a0a88 GIT binary patch literal 19502 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 I~QX0;=qgL51gx8O~al{3AEC=kW5 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 WkcHe59_tLX0ZVh+(@Zn>=zjn}E6&yc literal 0 HcmV?d00001 diff --git a/app/src/main/java/com/aurora/Constants.kt b/app/src/main/java/com/aurora/Constants.kt new file mode 100644 index 000000000..82f9c3792 --- /dev/null +++ b/app/src/main/java/com/aurora/Constants.kt @@ -0,0 +1,51 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora + +object Constants { + const val INT_EXTRA = "INT_EXTRA" + const val FLOAT_EXTRA = "FLOAT_EXTRA" + const val STRING_APP = "STRING_APP" + const val STRING_EXTRA = "STRING_EXTRA" + const val BROWSE_EXTRA = "BROWSE_EXTRA" + + const val FETCH_GROUP_ID = "FETCH_GROUP_ID" + + const val CONNECTIVITY_CHECK_URL = "https://connectivitycheck.android.com/generate_204" + const val EXODUS_BASE_URL = "https://reports.exodus-privacy.eu.org/api/search/" + const val EXODUS_REPORT_URL = "https://reports.exodus-privacy.eu.org/reports/" + const val SHARE_URL = "http://play.google.com/store/apps/details?id=" + + const val NOTIFICATION_CHANNEL_ALERT = "NOTIFICATION_CHANNEL_ALERT" + const val NOTIFICATION_CHANNEL_GENERAL = "NOTIFICATION_CHANNEL_GENERAL" + + const val URL_DISPENSER = "http://goolag.store:1337/api/auth" + + //ACCOUNTS + const val ACCOUNT_SIGNED_IN = "ACCOUNT_SIGNED_IN" + const val ACCOUNT_SIGNED_TIMESTAMP = "ACCOUNT_SIGNED_TIMESTAMP" + const val ACCOUNT_TYPE = "ACCOUNT_TYPE" + const val ACCOUNT_EMAIL_PLAIN = "ACCOUNT_EMAIL_PLAIN" + const val ACCOUNT_AAS_PLAIN = "ACCOUNT_AAS_PLAIN" + + const val PAGE_TYPE = "PAGE_TYPE" + const val TOP_CHART_TYPE = "TOP_CHART_TYPE" + const val TOP_CHART_CATEGORY = "TOP_CHART_CATEGORY" +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/AuroraApplication.kt b/app/src/main/java/com/aurora/store/AuroraApplication.kt new file mode 100644 index 000000000..8aeef42f6 --- /dev/null +++ b/app/src/main/java/com/aurora/store/AuroraApplication.kt @@ -0,0 +1,78 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store + +import androidx.appcompat.app.AppCompatDelegate +import androidx.multidex.MultiDexApplication +import com.aurora.store.data.downloader.DownloadManager +import com.aurora.store.data.providers.NetworkProvider +import com.aurora.store.data.receiver.PackageManagerReceiver +import com.aurora.store.data.service.NotificationService +import com.aurora.store.util.CommonUtil +import com.aurora.store.util.PackageUtil +import com.tonyodev.fetch2.Fetch +import nl.komponents.kovenant.android.startKovenant +import nl.komponents.kovenant.android.stopKovenant + +class AuroraApplication : MultiDexApplication() { + + private lateinit var fetch: Fetch + private lateinit var packageManagerReceiver: PackageManagerReceiver + + override fun onCreate() { + super.onCreate() + + AppCompatDelegate.setCompatVectorFromResourcesEnabled(true) + + NotificationService.startService(this) + + fetch = DownloadManager.with(this).fetch + + packageManagerReceiver = object : PackageManagerReceiver() { + + } + + //Register broadcast receiver for package install/uninstall + registerReceiver(packageManagerReceiver, PackageUtil.getFilter()) + + NetworkProvider + .with(this) + .bind() + + startKovenant() + + CommonUtil.cleanupInstallationSessions(applicationContext) + } + + override fun onTerminate() { + NetworkProvider + .with(this) + .unbind() + stopKovenant() + super.onTerminate() + } + + override fun onLowMemory() { + NetworkProvider + .with(this) + .unbind() + super.onLowMemory() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/AuroraGlide.kt b/app/src/main/java/com/aurora/store/AuroraGlide.kt new file mode 100644 index 000000000..2f6ea97a4 --- /dev/null +++ b/app/src/main/java/com/aurora/store/AuroraGlide.kt @@ -0,0 +1,53 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store + +import android.content.Context +import android.graphics.Bitmap +import com.bumptech.glide.GlideBuilder +import com.bumptech.glide.annotation.GlideModule +import com.bumptech.glide.load.DecodeFormat +import com.bumptech.glide.load.engine.cache.InternalCacheDiskCacheFactory +import com.bumptech.glide.load.engine.cache.LruResourceCache +import com.bumptech.glide.module.AppGlideModule +import com.bumptech.glide.request.RequestOptions +import com.bumptech.glide.signature.ObjectKey + +@GlideModule +class AuroraGlide : AppGlideModule() { + override fun applyOptions(context: Context, builder: GlideBuilder) { + val memoryCacheSizeBytes = 1024 * 1024 * 50 + builder.setMemoryCache(LruResourceCache(memoryCacheSizeBytes.toLong())) + builder.setDiskCache(InternalCacheDiskCacheFactory(context, memoryCacheSizeBytes.toLong())) + builder.setDefaultRequestOptions(requestOptions(context)) + } + + companion object { + private fun requestOptions(context: Context): RequestOptions { + return RequestOptions() + .signature(ObjectKey(System.currentTimeMillis() / (24 * 60 * 60 * 1000))) + .centerCrop() + .encodeFormat(Bitmap.CompressFormat.PNG) + .encodeQuality(60) + .format(DecodeFormat.PREFER_ARGB_8888) + .skipMemoryCache(false) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/Enumerations.kt b/app/src/main/java/com/aurora/store/Enumerations.kt new file mode 100644 index 000000000..b2e72c267 --- /dev/null +++ b/app/src/main/java/com/aurora/store/Enumerations.kt @@ -0,0 +1,25 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store + +enum class AccountType { + ANONYMOUS, + GOOGLE +} diff --git a/app/src/main/java/com/aurora/store/MainActivity.kt b/app/src/main/java/com/aurora/store/MainActivity.kt new file mode 100644 index 000000000..606de519a --- /dev/null +++ b/app/src/main/java/com/aurora/store/MainActivity.kt @@ -0,0 +1,243 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store + +import android.Manifest +import android.content.Intent +import android.os.Bundle +import android.view.Menu +import android.view.MenuItem +import android.view.View +import android.widget.ImageView +import android.widget.TextView +import android.widget.Toast +import androidx.annotation.IdRes +import androidx.annotation.NonNull +import androidx.core.graphics.ColorUtils +import androidx.core.view.GravityCompat +import androidx.navigation.NavController +import androidx.navigation.NavDestination +import androidx.navigation.Navigation +import androidx.navigation.ui.NavigationUI +import com.aurora.gplayapi.data.models.AuthData +import com.aurora.store.data.providers.AuthProvider +import com.aurora.store.databinding.ActivityMainBinding +import com.aurora.store.util.Log +import com.aurora.store.util.ViewUtil +import com.aurora.store.util.ViewUtil.getStyledAttribute +import com.aurora.store.util.extensions.isQAndAbove +import com.aurora.store.util.extensions.load +import com.aurora.store.util.extensions.open +import com.aurora.store.view.ui.about.AboutActivity +import com.aurora.store.view.ui.account.AccountActivity +import com.aurora.store.view.ui.all.AppsGamesActivity +import com.aurora.store.view.ui.commons.BaseActivity +import com.aurora.store.view.ui.commons.BlacklistActivity +import com.aurora.store.view.ui.downloads.DownloadActivity +import com.aurora.store.view.ui.preferences.SettingsActivity +import com.aurora.store.view.ui.sale.AppSalesActivity +import com.aurora.store.view.ui.search.SearchSuggestionActivity +import com.aurora.store.view.ui.spoof.SpoofActivity +import com.bumptech.glide.load.resource.bitmap.RoundedCorners +import com.google.android.material.bottomnavigation.BottomNavigationView +import com.livinglifetechway.quickpermissions_kotlin.runWithPermissions + + +class MainActivity : BaseActivity() { + + private lateinit var B: ActivityMainBinding + private lateinit var navController: NavController + private lateinit var authData: AuthData + + private var lastBackPressed = 0L + + companion object { + @JvmStatic + private fun matchDestination( + @NonNull destination: NavDestination?, + @IdRes destId: Int + ): Boolean { + var currentDestination = destination + while (currentDestination?.id != destId && currentDestination?.parent != null) { + currentDestination = currentDestination.parent + } + return currentDestination?.id == destId + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + B = ActivityMainBinding.inflate(layoutInflater) + + setContentView(B.root) + + authData = AuthProvider.with(this).getAuthData() + + attachToolbar() + attachNavigation() + attachDrawer() + attachSearch() + + checkPermission() + } + + private fun checkPermission() = runWithPermissions( + Manifest.permission.READ_EXTERNAL_STORAGE, + Manifest.permission.WRITE_EXTERNAL_STORAGE + ) { + Log.i("External Storage Access Available") + + if (isQAndAbove()) { + //pickExternalFileDir() + } + } + + private fun attachToolbar() { + B.viewToolbar.imgActionPrimary.setOnClickListener { + B.drawerLayout.openDrawer(GravityCompat.START, true) + } + + B.viewToolbar.imgActionSecondary.setOnClickListener { + val userAppIntent = Intent(this, DownloadActivity::class.java) + startActivity(userAppIntent, ViewUtil.getEmptyActivityBundle(this)) + } + } + + private fun attachSearch() { + B.searchFab.setOnClickListener { + startActivity( + Intent(this, SearchSuggestionActivity::class.java), + ViewUtil.getEmptyActivityBundle(this) + ) + } + } + + private fun attachNavigation() { + val bottomNavigationView: BottomNavigationView = B.navView + navController = Navigation.findNavController(this, R.id.nav_host_fragment) + + val backGroundColor = getStyledAttribute(this, android.R.attr.colorBackground) + bottomNavigationView.setBackgroundColor(ColorUtils.setAlphaComponent(backGroundColor, 245)) + + + bottomNavigationView.setOnNavigationItemSelectedListener { item -> + if (item.itemId == bottomNavigationView.selectedItemId) + return@setOnNavigationItemSelectedListener false + NavigationUI.onNavDestinationSelected(item, navController) + true + } + + navController.addOnDestinationChangedListener { _: NavController?, destination: NavDestination?, _: Bundle? -> + val menu: Menu = bottomNavigationView.menu + val size: Int = menu.size() + for (i in 0 until size) { + val item: MenuItem = menu.getItem(i) + if (matchDestination(destination, item.itemId)) { + item.isChecked = true + } + } + } + } + + private fun attachDrawer() { + val headerView: View = B.navigation.getHeaderView(0) + + headerView.let { + it.findViewById(R.id.img)?.load(R.mipmap.ic_launcher) { + transform(RoundedCorners(8)) + } + it.findViewById(R.id.txt_name)?.text = getString(R.string.app_name) + it.findViewById(R.id.txt_email)?.text = + ("v${BuildConfig.VERSION_NAME}.${BuildConfig.VERSION_CODE}") + } + + B.navigation.setNavigationItemSelectedListener { item: MenuItem -> + when (item.itemId) { + R.id.menu_apps_games -> { + open(AppsGamesActivity::class.java) + } + R.id.menu_apps_sale -> { + open(AppSalesActivity::class.java) + } + R.id.menu_blacklist_manager -> { + open(BlacklistActivity::class.java) + } + R.id.menu_download_manager -> { + open(DownloadActivity::class.java) + } + R.id.menu_spoof_manager -> { + open(SpoofActivity::class.java) + } + R.id.menu_account_manager -> { + open(AccountActivity::class.java) + } + R.id.menu_settings -> { + open(SettingsActivity::class.java) + } + R.id.menu_about -> { + open(AboutActivity::class.java) + } + } + false + } + } + + override fun onSupportNavigateUp(): Boolean { + return navController.navigateUp() + } + + override fun onBackPressed() { + if (!navController.navigateUp()) { + if (lastBackPressed + 1000 > System.currentTimeMillis()) { + super.onBackPressed() + } else { + lastBackPressed = System.currentTimeMillis() + Toast.makeText(this, "Click twice to exit", Toast.LENGTH_SHORT).show() + } + } + } + + override fun onConnected() { + hideNetworkConnectivitySheet() + } + + override fun onDisconnected() { + showNetworkConnectivitySheet() + } + + override fun onReconnected() { + + } + + private fun pickExternalFileDir() { + val getContentIntent = Intent(Intent.ACTION_GET_CONTENT) + getContentIntent.type = "*/*" + getContentIntent.putExtra(Intent.EXTRA_ALLOW_MULTIPLE, true) + getContentIntent.putExtra(Intent.EXTRA_LOCAL_ONLY, true) + getContentIntent.addCategory(Intent.CATEGORY_OPENABLE) + startActivityForResult( + Intent.createChooser( + getContentIntent, + "Aurora Store - External Storage Access" + ), 1337 + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/data/SingletonHolder.kt b/app/src/main/java/com/aurora/store/data/SingletonHolder.kt new file mode 100644 index 000000000..7f2236fd6 --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/SingletonHolder.kt @@ -0,0 +1,36 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.data + +open class SingletonHolder(private val constructor: (A) -> T) { + + @Volatile + private var instance: T? = null + + fun with(arg: A): T { + return when { + instance != null -> instance!! + else -> synchronized(this) { + if (instance == null) instance = constructor(arg) + instance!! + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/data/State.kt b/app/src/main/java/com/aurora/store/data/State.kt new file mode 100644 index 000000000..732508a41 --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/State.kt @@ -0,0 +1,44 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.data + + +sealed class ViewState { + object Loading : ViewState() + object Empty : ViewState() + data class Error(val error: String?) : ViewState() + data class Status(val status: String?) : ViewState() + data class Success(val data: T) : ViewState() +} + +sealed class RequestState { + object Init : RequestState() + object Pending : RequestState() + object Complete : RequestState() +} + +sealed class AuthState { + object Available : AuthState() + object Unavailable : AuthState() + object SignedIn : AuthState() + object SignedOut : AuthState() + object Valid : AuthState() + data class Status(val status: String?) : AuthState() +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/data/downloader/DownloadManager.kt b/app/src/main/java/com/aurora/store/data/downloader/DownloadManager.kt new file mode 100644 index 000000000..78539a084 --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/downloader/DownloadManager.kt @@ -0,0 +1,90 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.data.downloader + +import android.content.Context +import com.aurora.Constants +import com.aurora.store.data.SingletonHolder +import com.aurora.store.util.Preferences +import com.tonyodev.fetch2.* +import com.tonyodev.fetch2core.DefaultStorageResolver +import com.tonyodev.fetch2core.getFileTempDir + +class DownloadManager private constructor(var context: Context) { + + companion object : SingletonHolder(::DownloadManager) + + var fetch: Fetch + + init { + fetch = Fetch.getInstance(getFetchConfiguration(context)) + } + + fun getFetchInstance(): Fetch { + return fetch + } + + private fun getFetchConfiguration(context: Context): FetchConfiguration { + var maxActive = Preferences.getInteger(context, Preferences.PREFERENCE_DOWNLOAD_ACTIVE) + if (maxActive == 0) + maxActive = 1 + return FetchConfiguration.Builder(context) + .setDownloadConcurrentLimit(maxActive) + .enableLogging(BuildConfig.DEBUG) + .enableHashCheck(true) + .enableFileExistChecks(true) + .enableRetryOnNetworkGain(true) + .enableAutoStart(true) + .setInternetAccessUrlCheck(Constants.CONNECTIVITY_CHECK_URL) + .setAutoRetryMaxAttempts(3) + .setProgressReportingInterval(3000) + .setNamespace(BuildConfig.APPLICATION_ID) + .setStorageResolver(DefaultStorageResolver(context, getFileTempDir(context))) + .build() + } + + fun isDownloading(fetchGroup: FetchGroup): Boolean { + return fetchGroup.downloadingDownloads.isNotEmpty() + || fetchGroup.queuedDownloads.isNotEmpty() + || fetchGroup.addedDownloads.isNotEmpty() + } + + fun isCanceled(fetchGroup: FetchGroup): Boolean { + return fetchGroup.cancelledDownloads.isNotEmpty() + || fetchGroup.removedDownloads.isNotEmpty() + || fetchGroup.deletedDownloads.isNotEmpty() + } + + fun updateOngoingDownloads( + fetch: Fetch, packageList: MutableList, download: Download, + fetchListener: FetchListener? + ) { + if (packageList.contains(download.tag)) { + val packageName = download.tag + if (packageName != null) { + fetch.deleteGroup(packageName.hashCode()) + packageList.remove(packageName) + } + } + if (packageList.size == 0) { + fetch.removeListener(fetchListener!!) + } + } +} diff --git a/app/src/main/java/com/aurora/store/data/downloader/RequestBuilder.kt b/app/src/main/java/com/aurora/store/data/downloader/RequestBuilder.kt new file mode 100644 index 000000000..c21bdc866 --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/downloader/RequestBuilder.kt @@ -0,0 +1,68 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.data.downloader + +import android.content.Context +import com.aurora.Constants +import com.aurora.gplayapi.data.models.App +import com.aurora.gplayapi.data.models.File +import com.aurora.store.util.PathUtil +import com.google.gson.GsonBuilder +import com.tonyodev.fetch2.EnqueueAction +import com.tonyodev.fetch2.NetworkType +import com.tonyodev.fetch2.Request +import com.tonyodev.fetch2core.Extras +import java.lang.reflect.Modifier + +private inline fun Request.attachMetaData(app: App) { + apply { + groupId = app.id + tag = app.packageName + enqueueAction = EnqueueAction.UPDATE_ACCORDINGLY + networkType = NetworkType.ALL + } +} + +private inline fun Request.attachExtra(app: App) { + val stringMap: MutableMap = mutableMapOf() + val gson = GsonBuilder() + .excludeFieldsWithModifiers(Modifier.TRANSIENT) + .create() + stringMap[Constants.STRING_EXTRA] = gson.toJson(app) + apply { + extras = Extras(stringMap) + } +} + +object RequestBuilder { + + fun buildRequest(context: Context, app: App, file: File): Request { + val fileName = when (file.type) { + File.FileType.BASE, + File.FileType.SPLIT -> PathUtil.getApkDownloadFile(context, app, file) + File.FileType.OBB, + File.FileType.PATCH -> PathUtil.getObbDownloadFile(context, app, file) + } + return Request(file.url, fileName).apply { + attachMetaData(app) + attachExtra(app) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/data/event/BusEvent.kt b/app/src/main/java/com/aurora/store/data/event/BusEvent.kt new file mode 100644 index 000000000..32acba5e1 --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/event/BusEvent.kt @@ -0,0 +1,31 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.data.event + +sealed class BusEvent { + data class InstallEvent(var packageName: String, var error: String = String()) : BusEvent() + data class UninstallEvent(var packageName: String, var error: String = String()) : BusEvent() + data class Blacklisted(var packageName: String, var error: String = String()) : BusEvent() + data class GoogleAAS( + var success: Boolean, + var email: String = String(), + var aasToken: String = String() + ) : BusEvent() +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/data/installer/AppInstaller.kt b/app/src/main/java/com/aurora/store/data/installer/AppInstaller.kt new file mode 100644 index 000000000..dd38a6587 --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/installer/AppInstaller.kt @@ -0,0 +1,49 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.data.installer + +import android.content.Context +import android.os.Build +import com.aurora.store.data.SingletonHolder +import com.aurora.store.util.Preferences +import com.aurora.store.util.Preferences.PREFERENCE_INSTALLER_ID + +open class AppInstaller private constructor(var context: Context) { + + companion object : SingletonHolder(::AppInstaller) + + fun getPreferredInstaller(): IInstaller { + val prefValue = Preferences.getInteger( + context, + PREFERENCE_INSTALLER_ID + ) + + return when (prefValue) { + 1 -> NativeInstaller(context) + 2 -> RootInstaller(context) + 3 -> ServiceInstaller(context) + else -> if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + SessionInstaller(context) + } else { + NativeInstaller(context) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/data/installer/IInstaller.kt b/app/src/main/java/com/aurora/store/data/installer/IInstaller.kt new file mode 100644 index 000000000..bdabb8999 --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/installer/IInstaller.kt @@ -0,0 +1,29 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.data.installer + +interface IInstaller { + fun install(packageName: String, files: List) + fun uninstall(packageName: String) + + fun clearQueue() + fun isAlreadyQueued(packageName: String): Boolean + fun removeFromInstallQueue(packageName: String) +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/data/installer/InstallerBase.kt b/app/src/main/java/com/aurora/store/data/installer/InstallerBase.kt new file mode 100644 index 000000000..20514489d --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/installer/InstallerBase.kt @@ -0,0 +1,70 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.data.installer + +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.Build +import androidx.core.content.FileProvider +import com.aurora.store.BuildConfig +import java.io.File + +abstract class InstallerBase(protected var context: Context) : IInstaller { + + private val enqueuedInstalls: MutableSet = mutableSetOf() + + override fun clearQueue() { + enqueuedInstalls.clear() + } + + override fun isAlreadyQueued(packageName: String): Boolean { + return enqueuedInstalls.contains(packageName) + } + + override fun removeFromInstallQueue(packageName: String) { + enqueuedInstalls.remove(packageName) + } + + override fun uninstall(packageName: String) { + val uri = Uri.fromParts("package", packageName, null) + val intent = Intent().apply { + data = uri + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + } + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) { + intent.action = Intent.ACTION_DELETE + } else { + intent.action = Intent.ACTION_UNINSTALL_PACKAGE + intent.putExtra(Intent.EXTRA_RETURN_RESULT, true) + } + + context.startActivity(intent) + } + + open fun getUri(file: File): Uri { + return FileProvider.getUriForFile( + context, + "${BuildConfig.APPLICATION_ID}.fileProvider", + file + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/data/installer/InstallerService.kt b/app/src/main/java/com/aurora/store/data/installer/InstallerService.kt new file mode 100644 index 000000000..af0a3d2af --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/installer/InstallerService.kt @@ -0,0 +1,76 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.data.installer + +import android.app.Service +import android.content.Intent +import android.content.pm.PackageInstaller +import android.os.Build +import android.os.IBinder +import androidx.annotation.RequiresApi +import org.apache.commons.lang3.StringUtils + +class InstallerService : Service() { + + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { + val status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, -1) + val packageName = intent.getStringExtra(PackageInstaller.EXTRA_PACKAGE_NAME) + + //Send broadcast for the installation status of the package + sendStatusBroadcast(status, packageName) + + //Launch user confirmation activity + if (status == PackageInstaller.STATUS_PENDING_USER_ACTION) { + val confirmationIntent = intent.getParcelableExtra(Intent.EXTRA_INTENT) + confirmationIntent!!.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true) + confirmationIntent.putExtra( + Intent.EXTRA_INSTALLER_PACKAGE_NAME, + "com.android.vending" + ) + confirmationIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + try { + startActivity(confirmationIntent) + } catch (e: Exception) { + sendStatusBroadcast(PackageInstaller.STATUS_FAILURE, packageName) + } + } + stopSelf() + return START_NOT_STICKY + } + + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + private fun sendStatusBroadcast(status: Int, packageName: String?) { + if (StringUtils.isNotEmpty(packageName)) { + val statusIntent = Intent(ACTION_SESSION_INSTALLER) + statusIntent.putExtra(PackageInstaller.EXTRA_STATUS, status) + statusIntent.putExtra(PackageInstaller.EXTRA_PACKAGE_NAME, packageName) + sendBroadcast(statusIntent) + } + } + + override fun onBind(intent: Intent): IBinder? { + return null + } + + companion object { + private const val ACTION_SESSION_INSTALLER = "ACTION_SESSION_INSTALLER" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/data/installer/NativeInstaller.kt b/app/src/main/java/com/aurora/store/data/installer/NativeInstaller.kt new file mode 100644 index 000000000..c7f825683 --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/installer/NativeInstaller.kt @@ -0,0 +1,66 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.data.installer + +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.Build +import com.aurora.store.util.Log +import java.io.File + +class NativeInstaller(context: Context) : InstallerBase(context) { + + override fun install(packageName: String, files: List) { + if (isAlreadyQueued(packageName)) { + Log.i("$packageName already queued") + } else { + files.map { + when (it) { + is File -> it + is String -> File(it) + else -> { + throw Exception("Invalid data, expecting listOf() File or String") + } + } + }.forEach { + xInstall(packageName, it) + } + } + } + + private fun xInstall(packageName: String, file: File) { + val intent: Intent + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + intent = Intent(Intent.ACTION_INSTALL_PACKAGE) + intent.data = getUri(file) + intent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_ACTIVITY_NEW_TASK + } else { + intent = Intent(Intent.ACTION_VIEW) + intent.setDataAndType(Uri.fromFile(file), "application/vnd.android.package-archive"); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + } + + intent.putExtra(Intent.EXTRA_NOT_UNKNOWN_SOURCE, true) + intent.putExtra(Intent.EXTRA_INSTALLER_PACKAGE_NAME, "com.android.vending") + context.startActivity(intent) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/data/installer/RootInstaller.kt b/app/src/main/java/com/aurora/store/data/installer/RootInstaller.kt new file mode 100644 index 000000000..438e7aea9 --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/installer/RootInstaller.kt @@ -0,0 +1,99 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.data.installer + +import android.content.Context +import com.aurora.store.R +import com.aurora.store.util.Log +import com.aurora.store.util.extensions.isLAndAbove +import com.aurora.store.util.extensions.toast +import com.topjohnwu.superuser.Shell +import java.io.File +import java.util.regex.Pattern + +class RootInstaller(context: Context) : InstallerBase(context) { + + override fun install(packageName: String, files: List) { + if (isAlreadyQueued(packageName)) { + Log.i("$packageName already queued") + } else { + if (Shell.getShell().isRoot) { + files.map { + when (it) { + is File -> it + is String -> File(it) + else -> { + throw Exception("Invalid data, expecting listOf() File or String") + } + } + }.let { + if (isLAndAbove()) + xInstall(packageName, it) + else + xInstallLegacy(packageName, it) + } + } else { + context.toast(context.getString(R.string.installer_root_unavailable)) + Log.e(" >>>>>>>>>>>>>>>>>>>>>>>>>> NO ROOT ACCESS <<<<<<<<<<<<<<<<<<<<<<<<<<<<<") + } + } + } + + private fun xInstall(packageName: String, files: List) { + var totalSize = 0 + + for (file in files) + totalSize += file.length().toInt() + + val result: Shell.Result = + Shell.su("pm install-create -i com.android.vending --user 0 -r -S $totalSize") + .exec() + + val response = result.out + + val sessionIdPattern = Pattern.compile("(\\d+)") + val sessionIdMatcher = sessionIdPattern.matcher(response[0]) + val found = sessionIdMatcher.find() + + if (found) { + val sessionId = sessionIdMatcher.group(1).toInt() + if (Shell.getShell().isRoot) { + for (file in files) { + Shell.su("cat \"${file.absoluteFile}\" | pm install-write -S ${file.length()} $sessionId \"${file.name}\"") + .exec() + } + + Shell.su("pm install-commit $sessionId").exec() + } else { + removeFromInstallQueue(packageName) + } + } else { + removeFromInstallQueue(packageName) + } + } + + private fun xInstallLegacy(packageName: String, files: List) { + if (Shell.getShell().isRoot) { + Shell.su("pm install -i com.android.vending --user 0 \"${files[0].name}\"").exec() + } else { + removeFromInstallQueue(packageName) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/data/installer/ServiceInstaller.kt b/app/src/main/java/com/aurora/store/data/installer/ServiceInstaller.kt new file mode 100644 index 000000000..b9dd86cf6 --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/installer/ServiceInstaller.kt @@ -0,0 +1,106 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.data.installer + +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.ServiceConnection +import android.net.Uri +import android.os.Build +import android.os.IBinder +import android.os.RemoteException +import androidx.annotation.RequiresApi +import com.aurora.services.IPrivilegedCallback +import com.aurora.services.IPrivilegedService +import com.aurora.store.BuildConfig +import com.aurora.store.util.Log +import java.io.File + +class ServiceInstaller(context: Context) : InstallerBase(context) { + + companion object { + const val ACTION_INSTALL_REPLACE_EXISTING = 2 + const val PRIVILEGED_EXTENSION_PACKAGE_NAME = "com.aurora.services" + const val PRIVILEGED_EXTENSION_SERVICE_INTENT = "com.aurora.services.IPrivilegedService" + } + + @RequiresApi(Build.VERSION_CODES.LOLLIPOP) + override fun install(packageName: String, files: List) { + if (isAlreadyQueued(packageName)) { + Log.i("$packageName already queued") + } else { + Log.i("Received service install request for $packageName") + val uriList = files.map { + when (it) { + is File -> getUri(it) + is String -> getUri(File(it)) + else -> { + throw Exception("Invalid data, expecting listOf() File or String") + } + } + } + + xInstall(packageName, uriList) + } + } + + @RequiresApi(Build.VERSION_CODES.LOLLIPOP) + private fun xInstall(packageName: String, uriList: List) { + + val serviceConnection = object : ServiceConnection { + override fun onServiceConnected(name: ComponentName, binder: IBinder) { + val service = IPrivilegedService.Stub.asInterface(binder) + val callback = object : IPrivilegedCallback.Stub() { + override fun handleResult(packageName: String, returnCode: Int) { + removeFromInstallQueue(packageName) + } + } + + try { + service.installSplitPackage( + packageName, + uriList, + ACTION_INSTALL_REPLACE_EXISTING, + BuildConfig.APPLICATION_ID, + callback + ) + } catch (e: RemoteException) { + removeFromInstallQueue(packageName) + Log.e("Failed to connect Aurora Services") + } + } + + override fun onServiceDisconnected(name: ComponentName) { + removeFromInstallQueue(packageName) + Log.e("Disconnected from Aurora Services") + } + } + + val intent = Intent(PRIVILEGED_EXTENSION_SERVICE_INTENT) + intent.setPackage(PRIVILEGED_EXTENSION_PACKAGE_NAME) + + context.bindService( + intent, + serviceConnection, + Context.BIND_AUTO_CREATE + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/data/installer/SessionInstaller.kt b/app/src/main/java/com/aurora/store/data/installer/SessionInstaller.kt new file mode 100644 index 000000000..c87138a9a --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/installer/SessionInstaller.kt @@ -0,0 +1,119 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.data.installer + +import android.app.PendingIntent +import android.content.Context +import android.content.Intent +import android.content.pm.PackageInstaller.SessionParams +import android.net.Uri +import android.os.Build +import androidx.annotation.RequiresApi +import androidx.core.content.FileProvider +import com.aurora.store.BuildConfig +import com.aurora.store.util.Log +import org.apache.commons.io.IOUtils +import java.io.File + +class SessionInstaller(context: Context) : InstallerBase(context) { + + @RequiresApi(Build.VERSION_CODES.LOLLIPOP) + override fun install(packageName: String, files: List) { + if (isAlreadyQueued(packageName)) { + Log.i("$packageName already queued") + } else { + Log.i("Received service install request for $packageName") + val uriList = files.map { + when (it) { + is File -> getUri(it) + is String -> getUri(File(it)) + else -> { + throw Exception("Invalid data, expecting listOf() File or String") + } + } + } + + xInstall(packageName, uriList) + } + } + + @RequiresApi(Build.VERSION_CODES.LOLLIPOP) + private fun xInstall(packageName: String, uriList: List) { + val packageInstaller = context.packageManager.packageInstaller + val sessionParams = SessionParams(SessionParams.MODE_FULL_INSTALL) + val sessionId = packageInstaller.createSession(sessionParams) + val session = packageInstaller.openSession(sessionId) + + try { + + Log.i("Writing splits to session for $packageName") + var apkId = 1 + for (uri in uriList) { + val inputStream = context.contentResolver.openInputStream(uri) + val outputStream = session.openWrite( + "${packageName}_${apkId++}", + 0, + -1 + ) + + IOUtils.copy(inputStream, outputStream) + + session.fsync(outputStream) + + IOUtils.close(inputStream) + IOUtils.close(outputStream) + } + + val intent = Intent(context, InstallerService::class.java) + val pendingIntent = PendingIntent.getService( + context, + sessionId, + intent, + PendingIntent.FLAG_UPDATE_CURRENT + ) + + Log.i("Starting install session for $packageName") + session.commit(pendingIntent.intentSender) + session.close() + } catch (e: Exception) { + session.abandon() + removeFromInstallQueue(packageName) + Log.e("Failed to install $packageName : %s", e.message) + } + } + + override fun getUri(file: File): Uri { + val uri = FileProvider.getUriForFile( + context, + "${BuildConfig.APPLICATION_ID}.fileProvider", + file + ) + + uri.apply { + context.grantUriPermission( + BuildConfig.APPLICATION_ID, + uri, + Intent.FLAG_GRANT_READ_URI_PERMISSION + ) + } + + return uri + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/data/model/Accent.kt b/app/src/main/java/com/aurora/store/data/model/Accent.kt new file mode 100644 index 000000000..89971e126 --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/model/Accent.kt @@ -0,0 +1,36 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.data.model + +data class Accent( + var id: Int, + var accent: String +) { + override fun equals(other: Any?): Boolean { + return when (other) { + is Accent -> other.id == id + else -> false + } + } + + override fun hashCode(): Int { + return id.hashCode() + } +} diff --git a/app/src/main/java/com/aurora/store/data/model/Black.kt b/app/src/main/java/com/aurora/store/data/model/Black.kt new file mode 100644 index 000000000..93d80ecef --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/model/Black.kt @@ -0,0 +1,40 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.data.model + +import android.graphics.drawable.Drawable + +data class Black(val packageName: String) { + var displayName: String = String() + var drawable: Drawable? = null + var versionName: String = String() + var versionCode: Int = 0 + + override fun hashCode(): Int { + return packageName.hashCode() + } + + override fun equals(other: Any?): Boolean { + return when (other) { + is Black -> other.packageName == packageName + else -> false + } + } +} diff --git a/app/src/main/java/com/aurora/store/data/model/Dash.kt b/app/src/main/java/com/aurora/store/data/model/Dash.kt new file mode 100644 index 000000000..50d363c2b --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/model/Dash.kt @@ -0,0 +1,39 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.data.model + +data class Dash( + var id: Int, + var title: String, + var subtitle: String, + var icon: String, + var url: String +) { + override fun equals(other: Any?): Boolean { + return when (other) { + is Dash -> other.id == id + else -> false + } + } + + override fun hashCode(): Int { + return id.hashCode() + } +} diff --git a/app/src/main/java/com/aurora/store/data/model/Download.kt b/app/src/main/java/com/aurora/store/data/model/Download.kt new file mode 100644 index 000000000..309e3c5cb --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/model/Download.kt @@ -0,0 +1,36 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.data.model + +import com.tonyodev.fetch2.Download + +data class DownloadFile(val download: Download) { + + override fun hashCode(): Int { + return download.id + } + + override fun equals(other: Any?): Boolean { + return when (other) { + is DownloadFile -> other.download.status == download.status && other.download.progress == download.progress + else -> false + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/data/model/Exodus.kt b/app/src/main/java/com/aurora/store/data/model/Exodus.kt new file mode 100644 index 000000000..422f43056 --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/model/Exodus.kt @@ -0,0 +1,72 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.data.model + +import java.text.DateFormat +import java.text.ParseException +import java.text.SimpleDateFormat +import java.util.* + +class ExodusReport { + val creator: String = String() + val name: String = String() + val reports: List = listOf() +} + +class Report { + val id: Int = 0 + val downloads: String = String() + val version: String = String() + val creationDate: String = String() + val updatedAt: String = String() + val versionCode: String = String() + val trackers: List = listOf() + + fun getFormattedCreationDate(): String { + return try { + val simpleDateFormat: DateFormat = SimpleDateFormat( + "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", + Locale.getDefault() + ) + simpleDateFormat.parse(creationDate).toString() + } catch (e: ParseException) { + "" + } + } +} + +class ExodusTracker { + var id: Int = 0 + var name: String = String() + var url: String = String() + var signature: String = String() + var date: String = String() + + override fun hashCode(): Int { + return id + } + + override fun equals(other: Any?): Boolean { + return when (other) { + is ExodusTracker -> other.id == id + else -> false + } + } +} diff --git a/app/src/main/java/com/aurora/store/data/model/Installer.kt b/app/src/main/java/com/aurora/store/data/model/Installer.kt new file mode 100644 index 000000000..64105b826 --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/model/Installer.kt @@ -0,0 +1,39 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.data.model + +data class Installer( + var id: Int, + var title: String, + var subtitle: String, + var description: String, + var url: String +) { + override fun equals(other: Any?): Boolean { + return when (other) { + is Installer -> other.id == id + else -> false + } + } + + override fun hashCode(): Int { + return id.hashCode() + } +} diff --git a/app/src/main/java/com/aurora/store/data/model/Link.kt b/app/src/main/java/com/aurora/store/data/model/Link.kt new file mode 100644 index 000000000..b7ad6ed69 --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/model/Link.kt @@ -0,0 +1,39 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.data.model + +data class Link( + var id: Int, + var title: String, + var subtitle: String, + var url: String, + var icon: Int, +) { + override fun equals(other: Any?): Boolean { + return when (other) { + is Link -> other.id == id + else -> false + } + } + + override fun hashCode(): Int { + return id.hashCode() + } +} diff --git a/app/src/main/java/com/aurora/store/data/model/Theme.kt b/app/src/main/java/com/aurora/store/data/model/Theme.kt new file mode 100644 index 000000000..48dd94570 --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/model/Theme.kt @@ -0,0 +1,37 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.data.model + +data class Theme( + var id: Int, + var title: String, + var subtitle: String +) { + override fun equals(other: Any?): Boolean { + return when (other) { + is Theme -> other.id == id + else -> false + } + } + + override fun hashCode(): Int { + return id.hashCode() + } +} diff --git a/app/src/main/java/com/aurora/store/data/network/FuelClient.kt b/app/src/main/java/com/aurora/store/data/network/FuelClient.kt new file mode 100644 index 000000000..72b7621ea --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/network/FuelClient.kt @@ -0,0 +1,126 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.data.network + +import com.aurora.gplayapi.GooglePlayApi +import com.aurora.gplayapi.data.models.PlayResponse +import com.aurora.gplayapi.network.IHttpClient +import com.aurora.store.BuildConfig +import com.aurora.store.util.Log +import com.github.kittinunf.fuel.Fuel +import com.github.kittinunf.fuel.core.* +import java.nio.charset.Charset + +object FuelClient : IHttpClient { + + override fun get(url: String, headers: Map): PlayResponse { + return get(url, headers, hashMapOf()) + } + + override fun get( + url: String, + headers: Map, + params: Map + ): PlayResponse { + val parameters = params + .map { it.key to it.value } + .toList() + val (request, response, result) = Fuel.get(url, parameters) + .header(headers) + .response() + return buildPlayResponse(response, request) + } + + override fun getAuth(url: String): PlayResponse { + val (request, response, result) = Fuel.get(url) + .appendHeader( + "User-Agent", + "${BuildConfig.APPLICATION_ID}-${BuildConfig.VERSION_NAME}-${BuildConfig.VERSION_CODE}" + ) + .response() + return buildPlayResponse(response, request) + } + + override fun get( + url: String, + headers: Map, + paramString: String + ): PlayResponse { + val (request, response, result) = Fuel.get(url + paramString) + .header(headers) + .response() + return buildPlayResponse(response, request) + } + + override fun post(url: String, headers: Map, body: ByteArray): PlayResponse { + val (request, response, result) = Fuel.post(url) + .header(headers) + .appendHeader(Headers.CONTENT_TYPE, "application/x-protobuf") + .body(body, Charset.defaultCharset()) + .response() + return buildPlayResponse(response, request) + } + + override fun post( + url: String, + headers: Map, + params: Map + ): PlayResponse { + val parameters = params + .map { it.key to it.value } + .toList() + val (request, response, result) = Fuel.post(url, parameters) + .header(headers) + .response() + return buildPlayResponse(response, request) + } + + override fun postAuth(url: String, body: ByteArray): PlayResponse { + val (request, response, result) = Fuel.post(url) + .appendHeader( + "User-Agent", + "${BuildConfig.APPLICATION_ID}-${BuildConfig.VERSION_NAME}-${BuildConfig.VERSION_CODE}" + ) + .body(body) + .response() + return buildPlayResponse(response, request) + } + + @JvmStatic + private fun buildPlayResponse(response: Response, request: Request): PlayResponse { + return PlayResponse().apply { + isSuccessful = response.isSuccessful + code = response.statusCode + + GooglePlayApi + + if (response.isSuccessful) { + responseBytes = response.body().toByteArray() + } + + if (response.isClientError || response.isServerError) { + errorBytes = response.responseMessage.toByteArray() + errorString = String(errorBytes) + } + }.also { + Log.i("FUEL [${request.method}:${response.statusCode}] ${response.url}") + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/data/network/HttpClient.kt b/app/src/main/java/com/aurora/store/data/network/HttpClient.kt new file mode 100644 index 000000000..390aa6108 --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/network/HttpClient.kt @@ -0,0 +1,34 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.data.network + +import android.os.Build +import com.aurora.gplayapi.network.IHttpClient + +object HttpClient { + + fun getPreferredClient(): IHttpClient { + return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + OkHttpClient + } else { + FuelClient + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/data/network/OkHttpClient.kt b/app/src/main/java/com/aurora/store/data/network/OkHttpClient.kt new file mode 100644 index 000000000..dd8173e7f --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/network/OkHttpClient.kt @@ -0,0 +1,169 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.data.network + +import com.aurora.gplayapi.data.models.PlayResponse +import com.aurora.gplayapi.network.IHttpClient +import com.aurora.store.BuildConfig +import com.aurora.store.util.Log +import okhttp3.* +import okhttp3.Headers.Companion.toHeaders +import okhttp3.HttpUrl.Companion.toHttpUrl +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.OkHttpClient +import okhttp3.RequestBody.Companion.toRequestBody +import java.io.IOException +import java.util.concurrent.TimeUnit + +object OkHttpClient : IHttpClient { + + private const val POST = "POST" + private const val GET = "GET" + + private val okHttpClient = OkHttpClient().newBuilder() + .connectTimeout(20, TimeUnit.SECONDS) + .retryOnConnectionFailure(true) + .followRedirects(true) + .followSslRedirects(true) + .build() + + + @Throws(IOException::class) + fun post(url: String, headers: Map, requestBody: RequestBody): PlayResponse { + val request = Request.Builder() + .url(url) + .headers(headers.toHeaders()) + .method(POST, requestBody) + .build() + return processRequest(request) + } + + @Throws(IOException::class) + override fun post( + url: String, + headers: Map, + params: Map + ): PlayResponse { + val request = Request.Builder() + .url(buildUrl(url, params)) + .headers(headers.toHeaders()) + .method(POST, "".toRequestBody(null)) + .build() + return processRequest(request) + } + + override fun postAuth(url: String, body: ByteArray): PlayResponse { + val requestBody = body.toRequestBody("application/json".toMediaType(), 0, body.size) + val request = Request.Builder() + .url(url) + .header( + "User-Agent", + "${BuildConfig.APPLICATION_ID}-${BuildConfig.VERSION_NAME}-${BuildConfig.VERSION_CODE}" + ) + .method(POST, requestBody) + .build() + return processRequest(request) + } + + @Throws(IOException::class) + override fun post(url: String, headers: Map, body: ByteArray): PlayResponse { + val requestBody = body.toRequestBody( + "application/x-protobuf".toMediaType(), + 0, + body.size + ) + return post(url, headers, requestBody) + } + + @Throws(IOException::class) + override fun get(url: String, headers: Map): PlayResponse { + return get(url, headers, mapOf()) + } + + @Throws(IOException::class) + override fun get( + url: String, + headers: Map, + params: Map + ): PlayResponse { + val request = Request.Builder() + .url(buildUrl(url, params)) + .headers(headers.toHeaders()) + .method(GET, null) + .build() + return processRequest(request) + } + + override fun getAuth(url: String): PlayResponse { + val request = Request.Builder() + .url(url) + .header( + "User-Agent", + "${BuildConfig.APPLICATION_ID}-${BuildConfig.VERSION_NAME}-${BuildConfig.VERSION_CODE}" + ) + .method(GET, null) + .build() + return processRequest(request) + } + + @Throws(IOException::class) + override fun get( + url: String, + headers: Map, + paramString: String + ): PlayResponse { + val request = Request.Builder() + .url(url + paramString) + .headers(headers.toHeaders()) + .method(GET, null) + .build() + return processRequest(request) + } + + private fun processRequest(request: Request): PlayResponse { + val call = okHttpClient.newCall(request) + return buildPlayResponse(call.execute()) + } + + private fun buildUrl(url: String, params: Map): HttpUrl { + val urlBuilder = url.toHttpUrl().newBuilder() + params.forEach { + urlBuilder.addQueryParameter(it.key, it.value) + } + return urlBuilder.build() + } + + private fun buildPlayResponse(response: Response): PlayResponse { + return PlayResponse().apply { + isSuccessful = response.isSuccessful + code = response.code + + if (response.body != null) { + responseBytes = response.body!!.bytes() + } + + if (!isSuccessful) { + errorString = response.message + } + }.also { + Log.i("OKHTTP [${response.code}] ${response.request.url}") + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/data/providers/AccountProvider.kt b/app/src/main/java/com/aurora/store/data/providers/AccountProvider.kt new file mode 100644 index 000000000..895f2d51d --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/providers/AccountProvider.kt @@ -0,0 +1,53 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.data.providers + +import android.content.Context +import com.aurora.Constants +import com.aurora.store.AccountType +import com.aurora.store.data.SingletonHolder +import com.aurora.store.util.Preferences + +class AccountProvider private constructor(var context: Context) { + + companion object : SingletonHolder(::AccountProvider) + + fun isSignedIn(): Boolean { + return Preferences.getBoolean(context, Constants.ACCOUNT_SIGNED_IN) + } + + fun getSignInTimeStamp(): Long { + return Preferences.getLong(context, Constants.ACCOUNT_SIGNED_TIMESTAMP) + } + + fun getAccountType(): AccountType { + val rawType = Preferences.getString(context, Constants.ACCOUNT_TYPE) + return when (rawType) { + "GOOGLE" -> AccountType.GOOGLE + else -> AccountType.ANONYMOUS + } + } + + fun logout() { + Preferences.putBoolean(context, Constants.ACCOUNT_SIGNED_IN, false) + Preferences.putString(context, Constants.ACCOUNT_EMAIL_PLAIN, "") + Preferences.putString(context, Constants.ACCOUNT_AAS_PLAIN, "") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/data/providers/ApkProvider.kt b/app/src/main/java/com/aurora/store/data/providers/ApkProvider.kt new file mode 100644 index 000000000..0b0b5aa29 --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/providers/ApkProvider.kt @@ -0,0 +1,26 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.data.providers + +import androidx.core.content.FileProvider + +class ApkProvider : FileProvider() { + +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/data/providers/AuthProvider.kt b/app/src/main/java/com/aurora/store/data/providers/AuthProvider.kt new file mode 100644 index 000000000..113465107 --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/providers/AuthProvider.kt @@ -0,0 +1,53 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.data.providers + +import android.content.Context +import com.aurora.gplayapi.data.models.AuthData +import com.aurora.store.data.SingletonHolder +import com.aurora.store.util.Log +import com.aurora.store.util.Preferences +import com.aurora.store.util.Preferences.PREFERENCE_AUTH_DATA +import com.google.gson.Gson +import com.google.gson.GsonBuilder +import java.lang.reflect.Modifier + +class AuthProvider private constructor(var context: Context) { + + companion object : SingletonHolder(::AuthProvider) + + private var gson: Gson = GsonBuilder() + .excludeFieldsWithModifiers(Modifier.TRANSIENT) + .create() + + fun getAuthData(): AuthData { + return getSavedAuthData() + } + + private fun getSavedAuthData(): AuthData { + Log.i("Loading saved AuthData") + + val rawAuth: String = Preferences.getString(context, PREFERENCE_AUTH_DATA) + return if (rawAuth.isNotEmpty()) + gson.fromJson(rawAuth, AuthData::class.java) + else + AuthData("", "") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/data/providers/BlacklistProvider.kt b/app/src/main/java/com/aurora/store/data/providers/BlacklistProvider.kt new file mode 100644 index 000000000..a2d5364e1 --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/providers/BlacklistProvider.kt @@ -0,0 +1,78 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.data.providers + +import android.content.Context +import com.aurora.store.data.SingletonHolder +import com.aurora.store.util.Preferences +import com.google.gson.Gson +import com.google.gson.GsonBuilder +import com.google.gson.reflect.TypeToken +import java.lang.reflect.Modifier + +class BlacklistProvider private constructor(var context: Context) { + + companion object : SingletonHolder(::BlacklistProvider) { + const val PREFERENCE_BLACKLIST = "PREFERENCE_BLACKLIST" + } + + private var gson: Gson = GsonBuilder() + .excludeFieldsWithModifiers(Modifier.TRANSIENT) + .create() + + fun getBlackList(): MutableSet { + val rawBlacklist = Preferences.getString(context, PREFERENCE_BLACKLIST) + return try { + if (rawBlacklist.isEmpty()) + mutableSetOf() + else + gson.fromJson(rawBlacklist, object : TypeToken?>() {}.type) + } catch (e: Exception) { + mutableSetOf() + } + } + + fun isBlacklisted(packageName: String): Boolean { + return getBlackList().contains(packageName) + } + + fun blacklist(packageName: String) { + val oldBlackList: MutableSet = getBlackList() + oldBlackList.add(packageName) + save(oldBlackList) + } + + fun whitelist(packageName: String) { + val oldBlackList: MutableSet = getBlackList() + oldBlackList.remove(packageName) + save(oldBlackList) + } + + fun blacklist(packageNames: Set) { + val oldBlackList: MutableSet = getBlackList() + oldBlackList.addAll(packageNames) + save(oldBlackList) + } + + @Synchronized + fun save(blacklist: Set) { + Preferences.putString(context, PREFERENCE_BLACKLIST, gson.toJson(blacklist)) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/data/providers/EglExtensionProvider.kt b/app/src/main/java/com/aurora/store/data/providers/EglExtensionProvider.kt new file mode 100644 index 000000000..bdeea7cac --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/providers/EglExtensionProvider.kt @@ -0,0 +1,131 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.data.providers + +import android.opengl.GLES10 +import android.text.TextUtils +import java.util.* +import javax.microedition.khronos.egl.EGL10 +import javax.microedition.khronos.egl.EGLConfig +import javax.microedition.khronos.egl.EGLContext +import javax.microedition.khronos.egl.EGLDisplay + +object EglExtensionProvider { + @JvmStatic + val eglExtensions: List + get() { + val glExtensions: MutableSet = HashSet() + val egl10 = EGLContext.getEGL() as EGL10 + val display = egl10.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY) + egl10.eglInitialize(display, IntArray(2)) + val cf = IntArray(1) + if (egl10.eglGetConfigs(display, null, 0, cf)) { + val configs = arrayOfNulls(cf[0]) + if (egl10.eglGetConfigs(display, configs, cf[0], cf)) { + val a1 = intArrayOf( + EGL10.EGL_WIDTH, + EGL10.EGL_PBUFFER_BIT, + EGL10.EGL_HEIGHT, + EGL10.EGL_PBUFFER_BIT, + EGL10.EGL_NONE + ) + val a2 = intArrayOf(12440, EGL10.EGL_PIXMAP_BIT, EGL10.EGL_NONE) + val a3 = IntArray(1) + for (i in 0 until cf[0]) { + egl10.eglGetConfigAttrib(display, configs[i], EGL10.EGL_CONFIG_CAVEAT, a3) + if (a3[0] != EGL10.EGL_SLOW_CONFIG) { + egl10.eglGetConfigAttrib( + display, + configs[i], + EGL10.EGL_SURFACE_TYPE, + a3 + ) + if (1 and a3[0] != 0) { + egl10.eglGetConfigAttrib( + display, + configs[i], + EGL10.EGL_RENDERABLE_TYPE, + a3 + ) + if (1 and a3[0] != 0) { + addExtensionsForConfig( + egl10, + display, + configs[i], + a1, + null, + glExtensions + ) + } + if (4 and a3[0] != 0) { + addExtensionsForConfig( + egl10, + display, + configs[i], + a1, + a2, + glExtensions + ) + } + } + } + } + } + } + egl10.eglTerminate(display) + val sorted: List = ArrayList(glExtensions) + Collections.sort(sorted) + return sorted + } + + private fun addExtensionsForConfig( + egl10: EGL10, + eglDisplay: EGLDisplay, + eglConfig: EGLConfig?, + ai: IntArray, + ai1: IntArray?, + set: MutableSet + ) { + val eglContext = egl10.eglCreateContext(eglDisplay, eglConfig, EGL10.EGL_NO_CONTEXT, ai1) + if (eglContext === EGL10.EGL_NO_CONTEXT) { + return + } + val eglSurface = egl10.eglCreatePbufferSurface(eglDisplay, eglConfig, ai) + if (eglSurface === EGL10.EGL_NO_SURFACE) { + egl10.eglDestroyContext(eglDisplay, eglContext) + } else { + egl10.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext) + val s = GLES10.glGetString(7939) + if (!TextUtils.isEmpty(s)) { + val `as` = s.split(" ".toRegex()).toTypedArray() + val i = `as`.size + set.addAll(listOf(*`as`).subList(0, i)) + } + egl10.eglMakeCurrent( + eglDisplay, + EGL10.EGL_NO_SURFACE, + EGL10.EGL_NO_SURFACE, + EGL10.EGL_NO_CONTEXT + ) + egl10.eglDestroySurface(eglDisplay, eglSurface) + egl10.eglDestroyContext(eglDisplay, eglContext) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/data/providers/ExodusDataProvider.kt b/app/src/main/java/com/aurora/store/data/providers/ExodusDataProvider.kt new file mode 100644 index 000000000..a18f80d9e --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/providers/ExodusDataProvider.kt @@ -0,0 +1,60 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.data.providers + +import android.content.Context +import com.aurora.store.data.SingletonHolder +import org.json.JSONArray +import org.json.JSONObject +import java.nio.charset.StandardCharsets + +class ExodusDataProvider private constructor(val context: Context) { + + companion object : SingletonHolder(::ExodusDataProvider) + + private val exodusTrackers: JSONObject + + init { + exodusTrackers = loadLocalTrackers() + } + + fun getLocalTrackers(): JSONObject { + return exodusTrackers + } + + fun getFilteredTrackers(trackerIds: List): List { + return trackerIds.map { + exodusTrackers.getJSONObject( + it.toString() + ) + }.toList() + } + + private fun loadLocalTrackers(): JSONObject { + val inputStream = context.assets.open("exodus_trackers.json") + val bytes = ByteArray(inputStream.available()) + inputStream.read(bytes) + inputStream.close() + + val json = String(bytes, StandardCharsets.UTF_8) + val jsonArray = JSONArray(json) + return jsonArray.getJSONObject(0) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/data/providers/NativeDeviceInfoProvider.kt b/app/src/main/java/com/aurora/store/data/providers/NativeDeviceInfoProvider.kt new file mode 100644 index 000000000..c59fc0f85 --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/providers/NativeDeviceInfoProvider.kt @@ -0,0 +1,167 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ +package com.aurora.store.data.providers + +import android.app.ActivityManager +import android.content.Context +import android.content.ContextWrapper +import android.content.res.Configuration +import android.os.Build +import android.text.TextUtils +import java.util.* + +class NativeDeviceInfoProvider(context: Context) : ContextWrapper(context) { + + fun getNativeDeviceProperties(): Properties { + return Properties().apply { + //Build Props + setProperty("UserReadableName", Build.DEVICE) + setProperty("Build.HARDWARE", Build.HARDWARE) + setProperty( + "Build.RADIO", + if (Build.getRadioVersion() != null) + Build.getRadioVersion() + else + "unknown" + ) + setProperty("Build.FINGERPRINT", Build.FINGERPRINT) + setProperty("Build.BRAND", Build.BRAND) + setProperty("Build.DEVICE", Build.DEVICE) + setProperty("Build.VERSION.SDK_INT", "${Build.VERSION.SDK_INT}") + setProperty("Build.VERSION.RELEASE", Build.VERSION.RELEASE) + setProperty("Build.MODEL", Build.MODEL) + setProperty("Build.MANUFACTURER", Build.MANUFACTURER) + setProperty("Build.PRODUCT", Build.PRODUCT) + setProperty("Build.ID", Build.ID) + setProperty("Build.BOOTLOADER", Build.BOOTLOADER) + + val config = applicationContext.resources.configuration + setProperty("TouchScreen", "${config.touchscreen}") + setProperty("Keyboard", "${config.keyboard}") + setProperty("Navigation", "${config.navigation}") + setProperty("ScreenLayout", "${config.screenLayout and 15}") + setProperty("HasHardKeyboard", "${config.keyboard == Configuration.KEYBOARD_QWERTY}") + setProperty( + "HasFiveWayNavigation", + "${config.navigation == Configuration.NAVIGATIONHIDDEN_YES}" + ) + + //Display Metrics + val metrics = applicationContext.resources.displayMetrics + setProperty("Screen.Density", "${metrics.densityDpi}") + setProperty("Screen.Width", "${metrics.widthPixels}") + setProperty("Screen.Height", "${metrics.heightPixels}") + + + //Supported Platforms + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + setProperty("Platforms", Build.SUPPORTED_ABIS.joinToString(separator = ",")) + } else { + val platform = mutableListOf() + if (!TextUtils.isEmpty(Build.CPU_ABI)) { + platform.add(Build.CPU_ABI) + } + if (!TextUtils.isEmpty(Build.CPU_ABI2)) { + platform.add(Build.CPU_ABI2) + } + + setProperty("Platforms", platform.joinToString(separator = ",")) + } + //Supported Features + setProperty("Features", getFeatures().joinToString(separator = ",")) + //Shared Locales + setProperty("Locales", getLocales().joinToString(separator = ",")) + //Shared Libraries + setProperty("SharedLibraries", getSharedLibraries().joinToString(separator = ",")) + //GL Extensions + val activityManager = + applicationContext.getSystemService(ACTIVITY_SERVICE) as ActivityManager + setProperty( + "GL.Version", + activityManager.deviceConfigurationInfo.reqGlEsVersion.toString() + ) + setProperty( + "GL.Extensions", + EglExtensionProvider.eglExtensions.joinToString(separator = ",") + ) + + //Google Related Props + val gsfVersionProvider = NativeGsfVersionProvider(applicationContext) + setProperty("Client", "android-google") + setProperty("GSF.version", "${gsfVersionProvider.getGsfVersionCode(true)}") + setProperty("Vending.version", "${gsfVersionProvider.getVendingVersionCode(true)}") + setProperty("Vending.versionString", gsfVersionProvider.getVendingVersionString(true)) + + //MISC + setProperty("Roaming", "mobile-notroaming") + setProperty("TimeZone", "UTC-10") + + //Telephony (USA 3650 AT&T) + setProperty("CellOperator", "310") + setProperty("SimOperator", "38") + } + } + + private fun getFeatures(): List { + val featureStringList: MutableList = ArrayList() + try { + val availableFeatures = applicationContext.packageManager.systemAvailableFeatures + for (feature in availableFeatures) { + if (feature.name.isNotEmpty()) { + featureStringList.add(feature.name) + } + } + } catch (e: Exception) { + + } + return featureStringList + } + + private fun getLocales(): List { + val localeList: MutableList = ArrayList() + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + localeList.addAll(listOf(*applicationContext.assets.locales)) + } else { + for (locale in Locale.getAvailableLocales()) { + localeList.add(locale.toString()) + } + } + val locales: MutableList = ArrayList() + for (locale in localeList) { + if (TextUtils.isEmpty(locale)) { + continue + } + locales.add(locale.replace("-", "_")) + } + return locales + } + + private fun getSharedLibraries(): List { + val systemSharedLibraryNames = applicationContext.packageManager.systemSharedLibraryNames + val libraries: MutableList = ArrayList() + try { + if (systemSharedLibraryNames != null) { + libraries.addAll(listOf(*systemSharedLibraryNames)) + } + } catch (e: Exception) { + + } + return libraries + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/data/providers/NativeGsfVersionProvider.kt b/app/src/main/java/com/aurora/store/data/providers/NativeGsfVersionProvider.kt new file mode 100644 index 000000000..d62e43b0d --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/providers/NativeGsfVersionProvider.kt @@ -0,0 +1,90 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.data.providers + +import android.content.Context +import android.content.pm.PackageManager + +class NativeGsfVersionProvider(context: Context) { + private var gsfVersionCode = 0 + private var vendingVersionCode = 0 + private var vendingVersionString = "" + + init { + try { + gsfVersionCode = + context.packageManager.getPackageInfo(GOOGLE_SERVICES_PACKAGE_ID, 0).versionCode + } catch (e: PackageManager.NameNotFoundException) { + // com.google.android.gms not found + } + try { + val packageInfo = context.packageManager.getPackageInfo(GOOGLE_VENDING_PACKAGE_ID, 0) + vendingVersionCode = packageInfo.versionCode + vendingVersionString = packageInfo.versionName + } catch (e: PackageManager.NameNotFoundException) { + // com.android.vending not found + } + } + + init { + try { + gsfVersionCode = + context.packageManager.getPackageInfo(GOOGLE_SERVICES_PACKAGE_ID, 0).versionCode + } catch (e: PackageManager.NameNotFoundException) { + // com.google.android.gms not found + } + try { + val packageInfo = context.packageManager.getPackageInfo(GOOGLE_VENDING_PACKAGE_ID, 0) + vendingVersionCode = packageInfo.versionCode + vendingVersionString = packageInfo.versionName + } catch (e: PackageManager.NameNotFoundException) { + // com.android.vending not found + } + } + + fun getGsfVersionCode(defaultIfNotFound: Boolean): Int { + return if (defaultIfNotFound && gsfVersionCode < GOOGLE_SERVICES_VERSION_CODE) + GOOGLE_SERVICES_VERSION_CODE + else + gsfVersionCode + } + + fun getVendingVersionCode(defaultIfNotFound: Boolean): Int { + return if (defaultIfNotFound && vendingVersionCode < GOOGLE_VENDING_VERSION_CODE) + GOOGLE_VENDING_VERSION_CODE + else + vendingVersionCode + } + + fun getVendingVersionString(defaultIfNotFound: Boolean): String { + return if (defaultIfNotFound && vendingVersionCode < GOOGLE_VENDING_VERSION_CODE) + GOOGLE_VENDING_VERSION_STRING + else + vendingVersionString + } + + companion object { + private const val GOOGLE_SERVICES_PACKAGE_ID = "com.google.android.gms" + private const val GOOGLE_VENDING_PACKAGE_ID = "com.android.vending" + private const val GOOGLE_SERVICES_VERSION_CODE = 203019037 + private const val GOOGLE_VENDING_VERSION_CODE = 82151710 + private const val GOOGLE_VENDING_VERSION_STRING = "21.5.17-21 [0] [PR] 326734551" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/data/providers/NetworkProvider.kt b/app/src/main/java/com/aurora/store/data/providers/NetworkProvider.kt new file mode 100644 index 000000000..4d7150cf1 --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/providers/NetworkProvider.kt @@ -0,0 +1,101 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.data.providers + +import android.content.Context +import com.aurora.store.data.SingletonHolder +import com.aurora.store.util.Log +import com.novoda.merlin.Merlin + +class NetworkProvider(var context: Context) { + + companion object : SingletonHolder(::NetworkProvider) { + + private var networkListeners: MutableList = mutableListOf() + + fun addListener(networkListener: NetworkListener) { + Log.i("Network-Provider added to ${networkListener.javaClass.simpleName}") + networkListeners.add(networkListener) + } + + fun removeListener(networkListener: NetworkListener) { + Log.i("Network-Provider removed from ${networkListener.javaClass.simpleName}") + networkListeners.remove(networkListener) + } + } + + private var merlin: Merlin = Merlin.Builder() + .withAllCallbacks() + .build(context) + + private var isDisconnected = true + + fun bind() { + merlin.bind() + + merlin.registerConnectable { + if (isDisconnected) { + isDisconnected = false + onReConnected() + } else { + onConnected() + } + } + + merlin.registerDisconnectable { + isDisconnected = true + onDisconnected() + } + } + + fun unbind() { + networkListeners.clear() + merlin.unbind() + Log.i("Network-Provider destroyed") + } + + private fun onConnected() { + Log.i("Network-Provider connected") + isDisconnected = false + networkListeners.forEach { + it.onConnected() + } + } + + private fun onReConnected() { + Log.i("Network-Provider reconnected") + networkListeners.forEach { + it.onReconnected() + } + } + + private fun onDisconnected() { + Log.e("Network-Provider disconnected") + networkListeners.forEach { + it.onDisconnected() + } + } + + interface NetworkListener { + fun onConnected() + fun onDisconnected() + fun onReconnected() + } +} diff --git a/app/src/main/java/com/aurora/store/data/providers/SpoofDeviceProvider.kt b/app/src/main/java/com/aurora/store/data/providers/SpoofDeviceProvider.kt new file mode 100644 index 000000000..527c34fb4 --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/providers/SpoofDeviceProvider.kt @@ -0,0 +1,176 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.data.providers + +import android.content.Context +import com.aurora.store.BuildConfig +import com.aurora.store.data.SingletonHolder +import com.aurora.store.util.Log +import com.aurora.store.util.PathUtil +import java.io.BufferedInputStream +import java.io.File +import java.io.FileInputStream +import java.io.IOException +import java.util.* +import java.util.jar.JarEntry +import java.util.jar.JarFile + +class SpoofDeviceProvider private constructor(var context: Context) { + + companion object : SingletonHolder(::SpoofDeviceProvider) { + private const val SUFFIX = ".properties" + + fun filenameValid(filename: String): Boolean { + return filename.endsWith(SUFFIX) + } + } + + val availableDevice: List + get() { + val propertiesList: MutableList = ArrayList() + propertiesList.add(0, NativeDeviceInfoProvider(context).getNativeDeviceProperties()) + propertiesList.addAll(spoofDevicesFromApk) + propertiesList.addAll(spoofDevicesFromUser) + return propertiesList + } + + private val spoofDevicesFromApk: List + get() { + val jarFile = apkAsJar + val propertiesList: MutableList = ArrayList() + if (null == jarFile) { + return propertiesList + } + val entries = jarFile.entries() + while (entries.hasMoreElements()) { + val entry = entries.nextElement() + if (!filenameValid(entry.name)) { + continue + } + propertiesList.add(getProperties(jarFile, entry)) + } + return propertiesList + } + + private val spoofDevicesFromUser: List + get() { + val deviceNames: MutableList = ArrayList() + val defaultDir = File(PathUtil.getExternalPath()) + val files = defaultDir.listFiles() + if (defaultDir.exists() && files != null) { + for (file in files) { + if (!file.isFile || !filenameValid(file.name)) { + continue + } + deviceNames.add(getProperties(file)) + } + } + return deviceNames + } + + private fun getProperties(jarFile: JarFile, entry: JarEntry): Properties { + val properties = Properties() + try { + properties.load(jarFile.getInputStream(entry)) + properties.setProperty("CONFIG_NAME", entry.name) + } catch (e: IOException) { + Log.e("Could not read %s", entry.name) + } + return properties + } + + private fun getProperties(file: File): Properties { + val properties = Properties() + try { + properties.load(BufferedInputStream(FileInputStream(file))) + properties.setProperty("CONFIG_NAME", file.name) + } catch (e: IOException) { + Log.e("Could not read %s", file.name) + } + return properties + } + + private val devicesFromApk: Map + get() { + val deviceNames: MutableMap = HashMap() + val jarFile = apkAsJar ?: return deviceNames + + val entries = jarFile.entries() + while (entries.hasMoreElements()) { + val entry = entries.nextElement() + if (!filenameValid(entry.name)) { + continue + } + + deviceNames[entry.name] = + getProperties(jarFile, entry).getProperty("UserReadableName") + } + return deviceNames + } + + private val apkAsJar: JarFile? + get() { + val file = apkFile + try { + if (file != null && file.exists()) { + return JarFile(file) + } + } catch (e: IOException) { + Log.e("Could not open Aurora Store apk as a jar file") + } + return null + } + + private val apkFile: File? + get() { + try { + val sourceDir: String = context.packageManager.getApplicationInfo( + BuildConfig.APPLICATION_ID, + 0 + ).sourceDir + + if (sourceDir.isNotEmpty()) { + return File(sourceDir) + } + } catch (ignored: Exception) { + + } + return null + } + + private val devicesFromDownloadDirectory: Map + get() { + val deviceNames: MutableMap = HashMap() + val defaultDir = File(PathUtil.getExternalPath()) + if (!defaultDir.exists() || null == defaultDir.listFiles()) { + return deviceNames + } + for (file in defaultDir.listFiles()) { + if (!file.isFile || !filenameValid(file.name)) { + continue + } + val name = getProperties(file).getProperty("UserReadableName") + if (name != null) { + deviceNames[file.name] = name + } + } + return deviceNames + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/data/providers/SpoofProvider.kt b/app/src/main/java/com/aurora/store/data/providers/SpoofProvider.kt new file mode 100644 index 000000000..084687423 --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/providers/SpoofProvider.kt @@ -0,0 +1,87 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.data.providers + +import android.content.Context +import com.aurora.store.data.SingletonHolder +import com.aurora.store.util.Preferences +import com.google.gson.Gson +import com.google.gson.GsonBuilder +import java.lang.reflect.Modifier +import java.util.* + +class SpoofProvider private constructor(var context: Context) { + + companion object : SingletonHolder(::SpoofProvider) { + const val LOCALE_SPOOF_ENABLED = "LOCALE_SPOOF_ENABLED" + const val LOCALE_SPOOF_LANG = "LOCALE_SPOOF_LANG" + const val LOCALE_SPOOF_COUNTRY = "LOCALE_SPOOF_COUNTRY" + + const val DEVICE_SPOOF_ENABLED = "DEVICE_SPOOF_ENABLED" + const val DEVICE_SPOOF_PROPERTIES = "DEVICE_SPOOF_PROPERTIES" + } + + fun isLocaleSpoofEnabled(): Boolean { + return Preferences.getBoolean(context, LOCALE_SPOOF_ENABLED) + } + + fun isDeviceSpoofEnabled(): Boolean { + return Preferences.getBoolean(context, DEVICE_SPOOF_ENABLED) + } + + fun getSpoofLocale(): Locale { + return if (isLocaleSpoofEnabled()) { + Locale( + Preferences.getString(context, LOCALE_SPOOF_LANG), + Preferences.getString(context, LOCALE_SPOOF_COUNTRY) + ) + } else { + Locale.getDefault() + } + } + + fun getSpoofDeviceProperties(): Properties { + return if (isDeviceSpoofEnabled()) { + val gson: Gson = + GsonBuilder().excludeFieldsWithModifiers(Modifier.STATIC, Modifier.TRANSIENT) + .create() + return gson.fromJson( + Preferences.getString(context, DEVICE_SPOOF_PROPERTIES), + Properties::class.java + ) + } else { + Properties() + } + } + + fun setSpoofLocale(locale: Locale) { + Preferences.putBoolean(context, LOCALE_SPOOF_ENABLED, true) + Preferences.putString(context, LOCALE_SPOOF_LANG, locale.language) + Preferences.putString(context, LOCALE_SPOOF_COUNTRY, locale.country) + } + + fun setSpoofDeviceProperties(properties: Properties) { + val gson: Gson = + GsonBuilder().excludeFieldsWithModifiers(Modifier.STATIC, Modifier.TRANSIENT) + .create() + Preferences.putBoolean(context, DEVICE_SPOOF_ENABLED, true) + Preferences.putString(context, DEVICE_SPOOF_PROPERTIES, gson.toJson(properties)) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/data/receiver/BootReceiver.kt b/app/src/main/java/com/aurora/store/data/receiver/BootReceiver.kt new file mode 100644 index 000000000..3e21e49c9 --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/receiver/BootReceiver.kt @@ -0,0 +1,27 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ +package com.aurora.store.data.receiver + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent + +class BootReceiver : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) {} +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/data/receiver/DownloadCancelReceiver.kt b/app/src/main/java/com/aurora/store/data/receiver/DownloadCancelReceiver.kt new file mode 100644 index 000000000..228c7f4c1 --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/receiver/DownloadCancelReceiver.kt @@ -0,0 +1,38 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ +package com.aurora.store.data.receiver + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import com.aurora.Constants.FETCH_GROUP_ID +import com.aurora.store.data.downloader.DownloadManager + +class DownloadCancelReceiver : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + val extras = intent.extras + if (extras != null) { + val groupId = extras.getInt(FETCH_GROUP_ID, -1) + DownloadManager + .with(context) + .getFetchInstance() + .cancelGroup(groupId) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/data/receiver/DownloadPauseReceiver.kt b/app/src/main/java/com/aurora/store/data/receiver/DownloadPauseReceiver.kt new file mode 100644 index 000000000..fc4ee03e1 --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/receiver/DownloadPauseReceiver.kt @@ -0,0 +1,38 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ +package com.aurora.store.data.receiver + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import com.aurora.Constants.FETCH_GROUP_ID +import com.aurora.store.data.downloader.DownloadManager + +class DownloadPauseReceiver : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + val extras = intent.extras + if (extras != null) { + val groupId: Int = extras.getInt(FETCH_GROUP_ID, -1) + DownloadManager + .with(context) + .getFetchInstance() + .pauseGroup(groupId) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/data/receiver/DownloadResumeReceiver.kt b/app/src/main/java/com/aurora/store/data/receiver/DownloadResumeReceiver.kt new file mode 100644 index 000000000..5ea777e37 --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/receiver/DownloadResumeReceiver.kt @@ -0,0 +1,38 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ +package com.aurora.store.data.receiver + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import com.aurora.Constants.FETCH_GROUP_ID +import com.aurora.store.data.downloader.DownloadManager + +class DownloadResumeReceiver : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + val extras = intent.extras + if (extras != null) { + val groupId: Int = extras.getInt(FETCH_GROUP_ID, -1) + DownloadManager + .with(context) + .getFetchInstance() + .resumeGroup(groupId) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/data/receiver/InstallReceiver.kt b/app/src/main/java/com/aurora/store/data/receiver/InstallReceiver.kt new file mode 100644 index 000000000..2ebba3d56 --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/receiver/InstallReceiver.kt @@ -0,0 +1,36 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ +package com.aurora.store.data.receiver + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent + +class InstallReceiver : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + val extras = intent.extras + if (extras != null) { + /*val packageName = extras.getString(Constants.INTENT_PACKAGE_NAME, "") + val versionString = extras.getString(Constants.DOWNLOAD_VERSION_CODE) + if (!packageName.isEmpty() && versionString != null) { + AuroraApplication.getInstaller().installSplit(packageName, versionString.toInt()) + }*/ + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/data/receiver/PackageManagerReceiver.kt b/app/src/main/java/com/aurora/store/data/receiver/PackageManagerReceiver.kt new file mode 100644 index 000000000..8e2703657 --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/receiver/PackageManagerReceiver.kt @@ -0,0 +1,82 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.data.receiver + +import android.app.NotificationManager +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import com.aurora.store.data.event.BusEvent.InstallEvent +import com.aurora.store.data.event.BusEvent.UninstallEvent +import com.aurora.store.data.installer.AppInstaller +import com.aurora.store.util.PathUtil +import com.aurora.store.util.Preferences +import org.greenrobot.eventbus.EventBus +import java.io.File + +open class PackageManagerReceiver : BroadcastReceiver() { + + override fun onReceive(context: Context, intent: Intent) { + if (intent.action != null && intent.data != null) { + val packageName = intent.data!!.encodedSchemeSpecificPart + + when (intent.action) { + Intent.ACTION_PACKAGE_ADDED -> { + EventBus.getDefault() + .post(InstallEvent(packageName, "")) + + //Clear installation queue + AppInstaller.with(context) + .getPreferredInstaller() + .removeFromInstallQueue(packageName) + } + Intent.ACTION_PACKAGE_REMOVED -> EventBus.getDefault() + .post(UninstallEvent(packageName, "")) + } + + clearNotification(context, packageName) + + val isAutoDeleteAPKEnabled = Preferences.getBoolean( + context, + Preferences.PREFERENCE_AUTO_DELETE + ) + + if (isAutoDeleteAPKEnabled) + clearDownloads(context, packageName) + } + } + + private fun clearNotification(context: Context, packageName: String) { + val notificationManager = context.applicationContext + .getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + notificationManager.cancel(packageName, packageName.hashCode()) + } + + private fun clearDownloads(context: Context, packageName: String) { + try { + val rootDirPath = PathUtil.getPackageDirectory(context, packageName) + val rootDir = File(rootDirPath) + if (rootDir.exists()) + rootDir.deleteRecursively() + } catch (e: Exception) { + + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/data/receiver/UpdatesReceiver.kt b/app/src/main/java/com/aurora/store/data/receiver/UpdatesReceiver.kt new file mode 100644 index 000000000..6c2948e07 --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/receiver/UpdatesReceiver.kt @@ -0,0 +1,30 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.data.receiver + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent + +class UpdatesReceiver : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/data/service/NotificationService.kt b/app/src/main/java/com/aurora/store/data/service/NotificationService.kt new file mode 100644 index 000000000..2dd2b7946 --- /dev/null +++ b/app/src/main/java/com/aurora/store/data/service/NotificationService.kt @@ -0,0 +1,378 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.data.service + +import android.app.* +import android.content.Context +import android.content.Intent +import android.graphics.Color +import android.os.Build +import android.os.IBinder +import android.util.ArrayMap +import androidx.core.app.NotificationCompat +import androidx.core.content.ContextCompat +import com.aurora.Constants +import com.aurora.gplayapi.data.models.App +import com.aurora.store.R +import com.aurora.store.data.downloader.DownloadManager +import com.aurora.store.data.installer.AppInstaller +import com.aurora.store.data.receiver.DownloadCancelReceiver +import com.aurora.store.data.receiver.DownloadPauseReceiver +import com.aurora.store.data.receiver.DownloadResumeReceiver +import com.aurora.store.data.receiver.InstallReceiver +import com.aurora.store.util.CommonUtil +import com.aurora.store.util.Log +import com.aurora.store.util.extensions.isLAndAbove +import com.aurora.store.view.ui.details.AppDetailsActivity +import com.aurora.store.view.ui.downloads.DownloadActivity +import com.google.gson.Gson +import com.google.gson.GsonBuilder +import com.tonyodev.fetch2.* +import org.apache.commons.lang3.StringUtils +import java.lang.reflect.Modifier +import java.util.* + +class NotificationService : Service() { + + companion object { + fun startService(context: Context) { + try { + context.startService(Intent(context, NotificationService::class.java)) + } catch (e: Exception) { + Log.e("Failed to start notification service : %s", e.message) + } + } + } + + private lateinit var fetch: Fetch + private lateinit var fetchListener: AbstractFetchGroupListener + private lateinit var notificationManager: NotificationManager + + private val appMap = ArrayMap() + private val gson: Gson = GsonBuilder() + .excludeFieldsWithModifiers(Modifier.STATIC, Modifier.TRANSIENT) + .create() + + override fun onBind(intent: Intent): IBinder? { + return null + } + + override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { + return START_NOT_STICKY + } + + override fun onCreate() { + super.onCreate() + + Log.i("Notification Service Started") + + notificationManager = getSystemService(NOTIFICATION_SERVICE) as NotificationManager + + //Create Notification Channels : General & Alert + createNotificationChannel() + + fetch = DownloadManager.with(this).fetch + + fetchListener = object : AbstractFetchGroupListener() { + override fun onCancelled(groupId: Int, download: Download, fetchGroup: FetchGroup) { + showNotification(groupId, download, fetchGroup) + } + + override fun onCompleted(groupId: Int, download: Download, fetchGroup: FetchGroup) { + showNotification(groupId, download, fetchGroup) + if (fetchGroup.groupDownloadProgress == 100) { + install(download.tag!!, fetchGroup.downloads) + } + } + + override fun onError( + groupId: Int, + download: Download, + error: Error, + throwable: Throwable?, + fetchGroup: FetchGroup + ) { + showNotification(groupId, download, fetchGroup) + } + + override fun onProgress( + groupId: Int, + download: Download, + etaInMilliSeconds: Long, + downloadedBytesPerSecond: Long, + fetchGroup: FetchGroup + ) { + showNotification(groupId, download, fetchGroup) + } + + override fun onQueued( + groupId: Int, + download: Download, + waitingNetwork: Boolean, + fetchGroup: FetchGroup + ) { + showNotification(groupId, download, fetchGroup) + } + + override fun onPaused(groupId: Int, download: Download, fetchGroup: FetchGroup) { + showNotification(groupId, download, fetchGroup) + } + } + + fetch.addListener(fetchListener) + } + + private fun createNotificationChannel() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val channels = ArrayList() + channels.add( + NotificationChannel( + Constants.NOTIFICATION_CHANNEL_ALERT, + getString(R.string.notification_channel_alert), + NotificationManager.IMPORTANCE_HIGH + ) + ) + channels.add( + NotificationChannel( + Constants.NOTIFICATION_CHANNEL_GENERAL, + getString(R.string.notification_channel_general), + NotificationManager.IMPORTANCE_MIN + ) + ) + notificationManager.createNotificationChannels(channels) + } + } + + private fun showNotification(groupId: Int, download: Download, fetchGroup: FetchGroup) { + val status = download.status + + //Ignore notifications for completion of sub-parts of a bundled apk + if (status == Status.COMPLETED && fetchGroup.groupDownloadProgress < 100) + return + + //synchronized(appMap) { + var app: App? = appMap[groupId] + + if (app == null) { + app = gson.fromJson( + download.extras.getString(Constants.STRING_EXTRA, "{}"), + App::class.java + ) + appMap[groupId] = app + } + + if (app == null) + return + + val builder = NotificationCompat.Builder(this, Constants.NOTIFICATION_CHANNEL_GENERAL) + builder.setContentTitle(app.displayName) + builder.setSmallIcon(R.drawable.ic_notification_outlined) + builder.color = ContextCompat.getColor(this, R.color.colorAccent) + builder.setWhen(download.created) + builder.setContentIntent(getContentIntentForDownloads()) + + when (status) { + Status.PAUSED -> { + builder.setSmallIcon(R.drawable.ic_download_pause) + builder.setContentText(getString(R.string.download_paused)) + } + Status.CANCELLED -> { + builder.setSmallIcon(R.drawable.ic_download_cancel) + builder.setContentText(getString(R.string.download_canceled)) + builder.color = Color.RED + } + Status.FAILED -> { + builder.setSmallIcon(R.drawable.ic_download_fail) + builder.setContentText(getString(R.string.download_failed)) + builder.color = Color.RED + } + Status.COMPLETED -> if (fetchGroup.groupDownloadProgress == 100) { + builder.setSmallIcon(android.R.drawable.stat_sys_download_done) + builder.setContentText(getString(R.string.download_completed)) + } + else -> { + builder.setSmallIcon(android.R.drawable.stat_sys_download) + builder.setContentText(getString(R.string.download_metadata)) + } + } + val progress = fetchGroup.groupDownloadProgress + val progressBigText = NotificationCompat.BigTextStyle() + when (status) { + + Status.QUEUED -> { + builder.setProgress(100, 0, true) + progressBigText.bigText(getString(R.string.download_queued)) + builder.setStyle(progressBigText) + } + + Status.DOWNLOADING -> { + val contentString = getString(R.string.download_progress) + val partString = StringUtils.joinWith( + "/", + fetchGroup.completedDownloads.size + 1, + fetchGroup.downloads.size + ) + val speedString: String = + CommonUtil.humanReadableByteSpeed(download.downloadedBytesPerSecond, true) + progressBigText.bigText( + StringUtils.joinWith( + " \u2022 ", + contentString, + partString, + speedString + ) + ) + builder.setStyle(progressBigText) + builder.addAction( + NotificationCompat.Action.Builder( + R.drawable.ic_download_pause, + getString(R.string.action_pause), + getPauseIntent(groupId) + ).build() + ) + builder.addAction( + NotificationCompat.Action.Builder( + R.drawable.ic_download_cancel, + getString(R.string.action_cancel), + getCancelIntent(groupId) + ).build() + ) + if (progress < 0) builder.setProgress( + 100, + 0, + true + ) else builder.setProgress(100, progress, false) + } + + Status.PAUSED -> { + val pauseString = getString(R.string.download_paused) + val filesString = StringUtils.joinWith( + "/", + fetchGroup.completedDownloads.size, + fetchGroup.downloads.size + ) + progressBigText.bigText( + StringUtils.joinWith( + " \u2022 ", + pauseString, + filesString + ) + ) + builder.setStyle(progressBigText) + builder.addAction( + NotificationCompat.Action.Builder( + R.drawable.ic_download_pause, + getString(R.string.action_resume), + getResumeIntent(groupId) + ).build() + ) + } + + Status.COMPLETED -> if (fetchGroup.groupDownloadProgress == 100) { + builder.setAutoCancel(true) + builder.setContentIntent(getContentIntentForDetails(app)) + builder.setStyle(progressBigText) + } + else -> { + + } + } + + if (isLAndAbove()) { + when (status) { + Status.DOWNLOADING -> builder.setCategory(Notification.CATEGORY_PROGRESS) + Status.FAILED, Status.CANCELLED -> builder.setCategory(Notification.CATEGORY_ERROR) + else -> builder.setCategory(Notification.CATEGORY_STATUS) + } + } + + notificationManager.notify( + app.packageName, + app.id, + builder.build() + ) + //} + } + + private fun getPauseIntent(groupId: Int): PendingIntent { + val intent = Intent(this, DownloadPauseReceiver::class.java) + intent.putExtra(Constants.FETCH_GROUP_ID, groupId) + return PendingIntent.getBroadcast(this, groupId, intent, PendingIntent.FLAG_UPDATE_CURRENT) + } + + private fun getResumeIntent(groupId: Int): PendingIntent { + val intent = Intent(this, DownloadResumeReceiver::class.java) + intent.putExtra(Constants.FETCH_GROUP_ID, groupId) + return PendingIntent.getBroadcast(this, groupId, intent, PendingIntent.FLAG_UPDATE_CURRENT) + } + + private fun getCancelIntent(groupId: Int): PendingIntent { + val intent = Intent(this, DownloadCancelReceiver::class.java) + intent.putExtra(Constants.FETCH_GROUP_ID, groupId) + return PendingIntent.getBroadcast(this, groupId, intent, PendingIntent.FLAG_UPDATE_CURRENT) + } + + private fun getContentIntentForDetails(app: App?): PendingIntent { + val intent = Intent(this, AppDetailsActivity::class.java) + intent.putExtra(Constants.STRING_EXTRA, gson.toJson(app)) + return PendingIntent.getActivity( + this, + packageName.hashCode(), + intent, + PendingIntent.FLAG_UPDATE_CURRENT + ) + } + + private fun getContentIntentForDownloads(): PendingIntent { + val intent = Intent(this, DownloadActivity::class.java) + return PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT) + } + + private fun getInstallIntent(packageName: String, versionCode: String): PendingIntent { + val intent = Intent(this, InstallReceiver::class.java) + intent.putExtra(Constants.STRING_EXTRA, packageName) + return PendingIntent.getBroadcast( + this, + packageName.hashCode(), + intent, + PendingIntent.FLAG_UPDATE_CURRENT + ) + } + + @Synchronized + private fun install(packageName: String, files: List) { + AppInstaller.with(this) + .getPreferredInstaller() + .install( + packageName, + files + .filter { it.file.endsWith(".apk") } + .map { + it.file + }.toList() + ) + } + + override fun onDestroy() { + Log.i("Notification Service Stopped") + fetch.removeListener(fetchListener) + super.onDestroy() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/util/AC2DMTask.kt b/app/src/main/java/com/aurora/store/util/AC2DMTask.kt new file mode 100644 index 000000000..334431cbb --- /dev/null +++ b/app/src/main/java/com/aurora/store/util/AC2DMTask.kt @@ -0,0 +1,66 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.util + +import com.github.kittinunf.fuel.Fuel +import java.util.* + +class AC2DMTask { + @Throws(Exception::class) + fun getAC2DMResponse(email: String?, oAuthToken: String?): Map { + if (email == null || oAuthToken == null) + return mapOf() + + val params: MutableMap = hashMapOf() + params["lang"] = Locale.getDefault().toString().replace("_", "-") + params["google_play_services_version"] = PLAY_SERVICES_VERSION_CODE + params["sdk_version"] = BUILD_VERSION_SDK + params["device_country"] = Locale.getDefault().country.toLowerCase(Locale.US) + params["Email"] = email + params["service"] = "ac2dm" + params["get_accountid"] = 1 + params["ACCESS_TOKEN"] = 1 + params["callerPkg"] = "com.google.android.gms" + params["add_account"] = 1 + params["Token"] = oAuthToken + params["callerSig"] = "38918a453d07199354f8b19af05ec6562ced5788" + + val body = params.map { "${it.key}=${it.value}" }.joinToString(separator = "&") + + val response = Fuel.post(TOKEN_AUTH_URL) + .body(body) + .header("app" to "com.google.android.gms") + .header("User-Agent" to "") + .header("Content-Type" to "application/x-www-form-urlencoded") + .response() + + return response.third.fold(success = { + Util.parseResponse(String(it)) + }, failure = { + mapOf() + }) + } + + companion object { + private const val TOKEN_AUTH_URL = "https://android.clients.google.com/auth" + private const val BUILD_VERSION_SDK = 28 + private const val PLAY_SERVICES_VERSION_CODE = 19629032 + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/util/ApkCopier.kt b/app/src/main/java/com/aurora/store/util/ApkCopier.kt new file mode 100644 index 000000000..c1f690735 --- /dev/null +++ b/app/src/main/java/com/aurora/store/util/ApkCopier.kt @@ -0,0 +1,122 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.util + +import android.content.Context +import android.content.pm.PackageInfo +import android.content.pm.PackageManager +import android.net.Uri +import android.os.Build +import androidx.annotation.RequiresApi +import androidx.core.content.FileProvider +import com.aurora.store.BuildConfig +import com.aurora.store.util.extensions.isLAndAbove +import org.apache.commons.io.IOUtils +import java.io.File +import java.io.FileOutputStream +import java.util.* +import java.util.zip.ZipEntry +import java.util.zip.ZipOutputStream + +class ApkCopier(private val context: Context, private val packageName: String) { + + fun copy() { + val destination = File(PathUtil.getBaseCopyDirectory()) + + destination.let { + if (it.exists()) { + Log.i("Base copy directory is available") + } else { + it.mkdirs() + Log.e("Base copy directory is created : ${it.path}") + } + } + + val packageInfo: PackageInfo = context.packageManager.getPackageInfo( + packageName, + PackageManager.GET_META_DATA + ) + + val baseApk = getBaseApk(packageInfo) + val fileList: MutableList = mutableListOf() + + /*Add base APK*/ + fileList.add(baseApk) + + if (isLAndAbove()) { + val splitSourceDirs = packageInfo.applicationInfo.splitSourceDirs + if (splitSourceDirs != null && splitSourceDirs.isNotEmpty()) { + /*Add Split APKs*/ + fileList.addAll(getSplitAPKs(packageInfo)) + } + bundleAllAPKs(fileList) + } else { + bundleAllAPKs(fileList) + } + } + + private fun getBaseApk(packageInfo: PackageInfo?): File? { + return if (packageInfo?.applicationInfo != null) { + File(packageInfo.applicationInfo.sourceDir) + } else { + null + } + } + + @RequiresApi(Build.VERSION_CODES.LOLLIPOP) + private fun getSplitAPKs(packageInfo: PackageInfo): MutableList { + val fileList: MutableList = ArrayList() + val splitSourceDirs = packageInfo.applicationInfo.splitSourceDirs + if (splitSourceDirs != null) { + for (fileName in splitSourceDirs) fileList.add(File(fileName)) + } + return fileList + } + + private fun bundleAllAPKs(fileList: List) { + try { + val fileOutputStream = + FileOutputStream(PathUtil.getBaseCopyDirectory() + "$packageName.zip") + val zipOutputStream = ZipOutputStream(fileOutputStream) + + for (file in fileList) { + file?.let { + val zipEntry = ZipEntry(file.name) + zipOutputStream.putNextEntry(zipEntry) + IOUtils.copy(it.inputStream(), zipOutputStream) + zipOutputStream.closeEntry() + } + } + + zipOutputStream.close() + } catch (e: Exception) { + e.printStackTrace() + Log.e("ApkCopier : %s", e.message) + } + } + + fun getUri(file: File): Uri { + return FileProvider.getUriForFile( + context, + "${BuildConfig.APPLICATION_ID}.fileProvider", + file + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/util/CertUtil.kt b/app/src/main/java/com/aurora/store/util/CertUtil.kt new file mode 100644 index 000000000..e2d18353e --- /dev/null +++ b/app/src/main/java/com/aurora/store/util/CertUtil.kt @@ -0,0 +1,100 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.util + +import android.content.Context +import android.content.pm.PackageManager +import com.aurora.store.util.extensions.isPAndAbove +import java.io.ByteArrayInputStream +import java.io.InputStream +import java.security.cert.CertificateFactory +import java.security.cert.X509Certificate +import java.util.* + +object CertUtil { + private const val FDROID = "FDROID" + private const val GUARDIAN = "GUARDIANPROJECT.INFO" + + private fun getX509Certificates( + context: Context, + packageName: String + ): List { + val certificates: MutableList = mutableListOf() + val packageManager = context.applicationContext.packageManager + + try { + + val packageInfo = if (isPAndAbove()) + packageManager.getPackageInfo( + packageName, + PackageManager.GET_SIGNING_CERTIFICATES + ) + else + packageManager.getPackageInfo( + packageName, + PackageManager.GET_SIGNATURES + ) + + val certificateFactory = CertificateFactory.getInstance("X509") + + if (isPAndAbove()) { + packageInfo.signingInfo.apkContentsSigners.forEach { + val bytes = it.toByteArray() + val inputStream: InputStream = ByteArrayInputStream(bytes) + certificates.add( + certificateFactory!!.generateCertificate(inputStream) as X509Certificate + ) + } + } else { + for (i in 0..packageInfo.signatures.size) { + val bytes = packageInfo.signatures[i].toByteArray() + val inStream: InputStream = ByteArrayInputStream(bytes) + certificates.add( + certificateFactory!!.generateCertificate(inStream) as X509Certificate + ) + } + } + } catch (e: Exception) { + Log.e(e.message) + } + + return certificates + } + + fun isFDroidApp(context: Context, packageName: String): Boolean { + val certificates = getX509Certificates(context, packageName) + + return if (certificates.isEmpty()) + false + else { + val cert = certificates[0] + if (cert != null) { + if (cert.subjectDN != null) { + val DN = cert.subjectDN.name.toUpperCase(Locale.getDefault()) + DN.contains(FDROID) || DN.contains(GUARDIAN) + } else { + false + } + } else { + false + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/util/CommonUtil.kt b/app/src/main/java/com/aurora/store/util/CommonUtil.kt new file mode 100644 index 000000000..467a2f0f5 --- /dev/null +++ b/app/src/main/java/com/aurora/store/util/CommonUtil.kt @@ -0,0 +1,189 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.util + +import android.content.Context +import android.os.Build +import android.os.Bundle +import androidx.core.app.ActivityOptionsCompat +import com.aurora.store.R +import java.text.DecimalFormat +import java.util.* +import kotlin.math.ln +import kotlin.math.pow + +object CommonUtil { + + private val siPrefixes: Map = hashMapOf( + Pair(1, ""), + Pair(3, " KB"), + Pair(6, " MB"), + Pair(9, " GB") + ) + private val diPrefixes: Map = hashMapOf( + Pair(1, ""), + Pair(3, " K"), + Pair(6, " Million"), + Pair(9, " Billion") + ) + + fun addSiPrefix(value: Long): String { + if (value <= 0L) + return "NA" + var tempValue = value + var order = 0 + while (tempValue >= 1000.0) { + tempValue /= 1000.toLong() + order += 3 + } + return tempValue.toString() + siPrefixes[order] + } + + fun addDiPrefix(value: Long): String { + if (value <= 0L) + return "NA" + var tempValue = value + var order = 0 + while (tempValue >= 1000.0) { + tempValue /= 1000.0.toLong() + order += 3 + } + return tempValue.toString() + diPrefixes[order] + } + + fun getETAString(context: Context, etaInMilliSeconds: Long): String { + if (etaInMilliSeconds < 0) { + return context.getString(R.string.download_eta_calculating) + } + var seconds = (etaInMilliSeconds / 1000).toInt() + val hours = (seconds / 3600).toLong() + seconds -= (hours * 3600).toInt() + val minutes = (seconds / 60).toLong() + seconds -= (minutes * 60).toInt() + return when { + hours > 0 -> { + context.getString(R.string.download_eta_hrs, hours, minutes, seconds) + } + minutes > 0 -> { + context.getString(R.string.download_eta_min, minutes, seconds) + } + else -> { + context.getString(R.string.download_eta_sec, seconds) + } + } + } + + fun humanReadableByteSpeed(bytes: Long, si: Boolean): String { + val unit = if (si) 1000 else 1024 + if (bytes < unit) return "$bytes B" + val exp = (ln(bytes.toDouble()) / ln(unit.toDouble())).toInt() + val pre = (if (si) "kMGTPE" else "KMGTPE")[exp - 1].toString() + if (si) "" else "i" + return String.format( + Locale.getDefault(), "%.1f %sB/s", + bytes / unit.toDouble().pow(exp.toDouble()), + pre + ) + } + + fun humanReadableByteValue(bytes: Long, si: Boolean): String { + val unit = if (si) 1000 else 1024 + if (bytes < unit) return "$bytes B" + val exp = (ln(bytes.toDouble()) / ln(unit.toDouble())).toInt() + val pre = (if (si) "kMGTPE" else "KMGTPE")[exp - 1].toString() + if (si) "" else "i" + return String.format( + Locale.getDefault(), "%.1f %sB", + bytes / unit.toDouble().pow(exp.toDouble()), + pre + ) + } + + fun getDownloadSpeedString(context: Context, downloadedBytesPerSecond: Long): String { + if (downloadedBytesPerSecond < 0) { + return context.getString(R.string.download_speed_estimating) + } + val kb = downloadedBytesPerSecond.toDouble() / 1000.toDouble() + val mb = kb / 1000.toDouble() + val decimalFormat = DecimalFormat(".##") + return when { + mb >= 1 -> { + context.getString(R.string.download_speed_mb, decimalFormat.format(mb)) + } + kb >= 1 -> { + context.getString(R.string.download_speed_kb, decimalFormat.format(kb)) + } + else -> { + context.getString(R.string.download_speed_bytes, downloadedBytesPerSecond) + } + } + } + + fun getEmptyActivityBundle(context: Context): Bundle? { + return ActivityOptionsCompat.makeCustomAnimation( + context, + android.R.anim.fade_in, + android.R.anim.fade_out + ).toBundle() + } + + fun cleanupInstallationSessions(context: Context) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + val packageInstaller = context.packageManager.packageInstaller + for (sessionInfo in packageInstaller.mySessions) { + try { + val sessionId = sessionInfo.sessionId + packageInstaller.abandonSession(sessionInfo.sessionId) + Log.i("Abandoned session id -> %d", sessionId) + } catch (e: Exception) { + + } + } + } + } + + fun getThemeStyleById(themeId: Int): Int { + return when (themeId) { + 0 -> R.style.AppTheme + 1 -> R.style.AppTheme_Light + 2 -> R.style.AppTheme_Dark + 3 -> R.style.AppTheme_Black + 4 -> R.style.AppTheme_DarkX + 5 -> R.style.AppTheme_Darkord + else -> R.style.AppTheme + } + } + + fun getAccentStyleById(accentId: Int): Int { + return when (accentId) { + 1 -> R.style.Accent01 + 2 -> R.style.Accent02 + 3 -> R.style.Accent03 + 4 -> R.style.Accent04 + 5 -> R.style.Accent05 + 6 -> R.style.Accent06 + 7 -> R.style.Accent07 + 8 -> R.style.Accent08 + 9 -> R.style.Accent09 + 10 -> R.style.Accent10 + 11 -> R.style.Accent11 + 12 -> R.style.Accent12 + else -> R.style.Accent01 + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/util/Log.kt b/app/src/main/java/com/aurora/store/util/Log.kt new file mode 100644 index 000000000..c6959642d --- /dev/null +++ b/app/src/main/java/com/aurora/store/util/Log.kt @@ -0,0 +1,73 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.util + +import android.content.Context +import android.util.Log +import java.io.File +import java.io.FileWriter +import java.io.IOException + +object Log { + + const val TAG = "¯\\_(ツ)_/¯ " + + fun e(message: String?, vararg args: Any?) { + e(String.format(message!!, *args)) + } + + fun e(message: String?) { + Log.e(TAG, message!!) + } + + fun i(message: String?, vararg args: Any?) { + i(String.format(message!!, *args)) + } + + fun i(message: String?) { + Log.i(TAG, message!!) + } + + fun d(message: String?, vararg args: Any?) { + d(String.format(message!!, *args)) + } + + fun d(message: String?) { + Log.d(TAG, message!!) + } + + fun w(message: String?, vararg args: Any?) { + w(String.format(message!!, *args)) + } + + fun w(message: String?) { + Log.w(TAG, message!!) + } + + fun writeToFile(context: Context, obj: Any) { + try { + val out = FileWriter(File(context.filesDir, "AuroraLogs.txt")) + out.write(obj.toString()) + out.close() + } catch (e: IOException) { + e(e.message) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/util/NavigationUtil.kt b/app/src/main/java/com/aurora/store/util/NavigationUtil.kt new file mode 100644 index 000000000..ebb516723 --- /dev/null +++ b/app/src/main/java/com/aurora/store/util/NavigationUtil.kt @@ -0,0 +1,99 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.util + +import android.app.ActivityOptions +import android.content.Context +import android.content.Intent +import android.os.Build +import androidx.appcompat.app.AppCompatActivity +import com.aurora.Constants +import com.aurora.gplayapi.data.models.App +import com.aurora.store.data.model.Report +import com.aurora.store.view.ui.details.AppDetailsActivity +import com.aurora.store.view.ui.details.DetailsExodusActivity +import com.aurora.store.view.ui.details.DevAppsActivity +import com.aurora.store.view.ui.search.SearchResultsActivity +import com.google.gson.Gson +import com.google.gson.GsonBuilder +import java.lang.reflect.Modifier + +object NavigationUtil { + val gson: Gson = GsonBuilder().excludeFieldsWithModifiers(Modifier.TRANSIENT).create() + + fun openDetailsActivity(context: Context, app: App) { + val intent = Intent( + context, + AppDetailsActivity::class.java + ).apply { + putExtra(Constants.STRING_EXTRA, gson.toJson(app)) + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + val options = ActivityOptions.makeSceneTransitionAnimation(context as AppCompatActivity) + context.startActivity(intent, options.toBundle()) + } else { + context.startActivity(intent) + } + } + + fun openDevAppsActivity(context: Context, app: App) { + val intent = Intent( + context, + DevAppsActivity::class.java + ).apply { + putExtra(Constants.STRING_APP, gson.toJson(app)) + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + val options = ActivityOptions.makeSceneTransitionAnimation(context as AppCompatActivity) + context.startActivity(intent, options.toBundle()) + } else { + context.startActivity(intent) + } + } + + fun openExodusActivity(context: Context, app: App, report: Report) { + val intent = Intent( + context, + DetailsExodusActivity::class.java + ).apply { + putExtra(Constants.STRING_APP, gson.toJson(app)) + putExtra(Constants.STRING_EXTRA, gson.toJson(report)) + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + val options = ActivityOptions.makeSceneTransitionAnimation(context as AppCompatActivity) + context.startActivity(intent, options.toBundle()) + } else { + context.startActivity(intent) + } + } + + fun openSearchActivity(context: Context) { + val intent = Intent( + context, + SearchResultsActivity::class.java + ) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + val options = ActivityOptions.makeSceneTransitionAnimation(context as AppCompatActivity) + context.startActivity(intent, options.toBundle()) + } else { + context.startActivity(intent) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/util/PackageUtil.kt b/app/src/main/java/com/aurora/store/util/PackageUtil.kt new file mode 100644 index 000000000..a6f80058d --- /dev/null +++ b/app/src/main/java/com/aurora/store/util/PackageUtil.kt @@ -0,0 +1,155 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.util + +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.content.pm.PackageInfo +import android.content.pm.PackageManager +import android.content.res.Configuration +import android.os.Build + + +object PackageUtil { + + fun isInstalled(context: Context, packageName: String): Boolean { + return try { + context.packageManager.getPackageInfo(packageName, PackageManager.GET_META_DATA) + true + } catch (e: PackageManager.NameNotFoundException) { + false + } + } + + fun isUpdatable(context: Context, packageName: String, versionCode: Long): Boolean { + return try { + val packageInfo = getPackageInfo(context, packageName) + if (packageInfo != null) { + return versionCode > packageInfo.versionCode + } + true + } catch (e: PackageManager.NameNotFoundException) { + false + } + } + + fun isTv(context: Context): Boolean { + val uiMode = context.resources.configuration.uiMode + return uiMode and Configuration.UI_MODE_TYPE_MASK == Configuration.UI_MODE_TYPE_TELEVISION + } + + fun getLaunchIntent(context: Context, packageName: String?): Intent? { + val isTv = Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && isTv(context) + val intent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + if (isTv) { + context.packageManager.getLeanbackLaunchIntentForPackage(packageName!!) + } else { + context.packageManager.getLaunchIntentForPackage(packageName!!) + } + } else { + context.packageManager.getLaunchIntentForPackage(packageName!!) + } + + return if (intent == null) { + null + } else { + intent.addCategory(if (isTv) Intent.CATEGORY_LEANBACK_LAUNCHER else Intent.CATEGORY_LAUNCHER) + intent + } + } + + @Throws(Exception::class) + fun getPackageInfo(context: Context, packageName: String?): PackageInfo? { + return context.packageManager.getPackageInfo(packageName!!, 0) + } + + fun getAllPackages(context: Context): List { + val packageInfoSet: MutableList = mutableListOf() + val packageManager: PackageManager = context.packageManager + val flags: Int = getAllFlags() + val packageInfoList: List = packageManager.getInstalledPackages(flags) + for (packageInfo in packageInfoList) { + if (packageInfo.packageName != null && packageInfo.applicationInfo != null) { + packageInfoSet.add(packageInfo) + } + } + return packageInfoSet + } + + fun getPackageInfoMap(context: Context): MutableMap { + val packageInfoSet: MutableMap = mutableMapOf() + val packageManager: PackageManager = context.packageManager + val flags: Int = PackageManager.GET_META_DATA + var packageInfoList: List = packageManager.getInstalledPackages(flags) + + val isFdroidFilterEnabled = Preferences.getBoolean( + context, + Preferences.PREFERENCE_FILTER_FDROID + ) + + packageInfoList = packageInfoList + .filter { + it.packageName != null && it.applicationInfo != null && it.applicationInfo.enabled + } + + if (isFdroidFilterEnabled) { + packageInfoList + .filter { + val packageInstaller = packageManager.getInstallerPackageName(it.packageName) + packageInstaller != "org.fdroid.fdroid.privileged" + }.filter { + !CertUtil.isFDroidApp(context, it.packageName) + } + } + + packageInfoList.forEach { + packageInfoSet[it.packageName] = it + } + + return packageInfoSet + } + + + fun getFilter(): IntentFilter { + val filter = IntentFilter() + filter.addDataScheme("package") + filter.addAction(Intent.ACTION_PACKAGE_INSTALL) + filter.addAction(Intent.ACTION_PACKAGE_ADDED) + filter.addAction(Intent.ACTION_PACKAGE_REMOVED) + return filter + } + + private fun getAllFlags(): Int { + var flags = (PackageManager.GET_META_DATA + or PackageManager.GET_ACTIVITIES + or PackageManager.GET_SERVICES + or PackageManager.GET_PROVIDERS + or PackageManager.GET_RECEIVERS) + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) { + flags = flags or PackageManager.GET_DISABLED_COMPONENTS + flags = flags or PackageManager.GET_UNINSTALLED_PACKAGES + } else { + flags = flags or PackageManager.MATCH_DISABLED_COMPONENTS + flags = flags or PackageManager.MATCH_UNINSTALLED_PACKAGES + } + return flags + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/util/PathUtil.kt b/app/src/main/java/com/aurora/store/util/PathUtil.kt new file mode 100644 index 000000000..2d442982a --- /dev/null +++ b/app/src/main/java/com/aurora/store/util/PathUtil.kt @@ -0,0 +1,78 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.util + +import android.content.Context +import android.os.Environment +import com.aurora.gplayapi.data.models.App +import com.aurora.gplayapi.data.models.File +import com.aurora.store.util.extensions.isLAndAbove + +fun Context.getInternalBaseDirectory(): String { + return filesDir.path +} + +object PathUtil { + + private fun getDownloadDirectory(context: Context): String { + return if (isLAndAbove()) + context.getInternalBaseDirectory() + "/Downloads" + else + getExternalPath() + } + + fun getPackageDirectory(context: Context, packageName: String): String { + return getDownloadDirectory(context) + "/$packageName" + } + + private fun getVersionDirectory( + context: Context, + packageName: String, + versionCode: Int + ): String { + return getPackageDirectory(context, packageName) + "/$versionCode" + } + + fun getApkDownloadFile(context: Context, app: App, file: File): String { + return getVersionDirectory(context, app.packageName, app.versionCode) + "/${file.name}" + } + + fun getApkDownloadFile(context: Context, packageName: String, versionCode: Int): String { + return getVersionDirectory(context, packageName, versionCode) + } + + fun getExternalPath(): String { + return Environment.getExternalStorageDirectory().toString() + "/Aurora/" + } + + fun getBaseCopyDirectory(): String { + return "${getExternalPath()}/files/export/" + } + + private fun getObbDownloadPath(context: Context, app: App): String { + return Environment.getExternalStorageDirectory() + .toString() + "/Android/obb/" + app.packageName + } + + fun getObbDownloadFile(context: Context, app: App, file: File): String { + val obbDir = getObbDownloadPath(context, app) + return "$obbDir/${file.name}" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/util/Preferences.kt b/app/src/main/java/com/aurora/store/util/Preferences.kt new file mode 100644 index 000000000..f84cb7e9d --- /dev/null +++ b/app/src/main/java/com/aurora/store/util/Preferences.kt @@ -0,0 +1,114 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.util + +import android.content.Context +import android.content.SharedPreferences +import androidx.fragment.app.Fragment +import androidx.preference.PreferenceManager + +object Preferences { + + const val PREFERENCE_AUTH_DATA = "PREFERENCE_AUTH_DATA" + const val PREFERENCE_INSTALLER_ID = "PREFERENCE_INSTALLER_ID" + const val PREFERENCE_THEME_TYPE = "PREFERENCE_THEME_TYPE" + const val PREFERENCE_THEME_ACCENT = "PREFERENCE_THEME_ACCENT" + const val PREFERENCE_INTRO = "PREFERENCE_INTRO" + + const val PREFERENCE_FILTER_GOOGLE = "PREFERENCE_FILTER_GOOGLE" + const val PREFERENCE_FILTER_FDROID = "PREFERENCE_FILTER_FDROID" + + const val PREFERENCE_AUTO_INSTALL = "PREFERENCE_AUTO_INSTALL" + const val PREFERENCE_AUTO_DELETE = "PREFERENCE_AUTO_DELETE" + + const val INSTALLATION_ABANDON_SESSION = "INSTALLATION_ABANDON_SESSION" + + const val PREFERENCE_DOWNLOAD_ACTIVE = "PREFERENCE_DOWNLOAD_ACTIVE" + const val PREFERENCE_DOWNLOAD_WIFI = "PREFERENCE_DOWNLOAD_WIFI" + + + private fun getPrefs(context: Context): SharedPreferences { + return PreferenceManager.getDefaultSharedPreferences(context) + } + + fun putString(context: Context, key: String, value: String) { + getPrefs(context).edit().putString(key, value).apply() + } + + fun putInteger(context: Context, key: String, value: Int) { + getPrefs(context).edit().putInt(key, value).apply() + } + + fun putFloat(context: Context, key: String, value: Float) { + getPrefs(context).edit().putFloat(key, value).apply() + } + + fun putLong(context: Context, key: String, value: Long) { + getPrefs(context).edit().putLong(key, value).apply() + } + + fun putBoolean(context: Context, key: String, value: Boolean) { + getPrefs(context).edit().putBoolean(key, value).apply() + } + + fun getString(context: Context, key: String): String { + return getPrefs(context).getString(key, "").toString() + } + + fun getInteger(context: Context, key: String): Int { + return getPrefs(context).getInt(key, 0) + } + + fun getFloat(context: Context, key: String): Float { + return getPrefs(context).getFloat(key, 0.0f) + } + + fun getLong(context: Context, key: String): Long { + return getPrefs(context).getLong(key, 0L) + } + + fun getBoolean(context: Context, key: String): Boolean { + return getPrefs(context).getBoolean(key, false) + } +} + +fun Context.save(key: String, value: Int) { + Preferences.putInteger(this, key, value) +} + +fun Fragment.save(key: String, value: Int) { + Preferences.putInteger(requireContext(), key, value) +} + +fun Context.save(key: String, value: Boolean) { + Preferences.putBoolean(this, key, value) +} + +fun Fragment.save(key: String, value: Boolean) { + Preferences.putBoolean(requireContext(), key, value) +} + +fun Context.save(key: String, value: String) { + Preferences.putString(this, key, value) +} + +fun Fragment.save(key: String, value: String) { + Preferences.putString(requireContext(), key, value) +} diff --git a/app/src/main/java/com/aurora/store/util/Util.java b/app/src/main/java/com/aurora/store/util/Util.java new file mode 100644 index 000000000..e08648db5 --- /dev/null +++ b/app/src/main/java/com/aurora/store/util/Util.java @@ -0,0 +1,53 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.util; + +import java.util.HashMap; +import java.util.Map; +import java.util.StringTokenizer; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class Util { + + public static Map parseResponse(String response) { + Map keyValueMap = new HashMap(); + StringTokenizer st = new StringTokenizer(response, "\n\r"); + while (st.hasMoreTokens()) { + String[] keyValue = st.nextToken().split("=", 2); + if (keyValue.length >= 2) { + keyValueMap.put(keyValue[0], keyValue[1]); + } + } + return keyValueMap; + } + + public static Map parseCookieString(String cookies) { + Map cookieList = new HashMap<>(); + Pattern cookiePattern = Pattern.compile("([^=]+)=([^;]*);?\\s?"); + Matcher matcher = cookiePattern.matcher(cookies); + while (matcher.find()) { + String cookieKey = matcher.group(1); + String cookieValue = matcher.group(2); + cookieList.put(cookieKey, cookieValue); + } + return cookieList; + } +} diff --git a/app/src/main/java/com/aurora/store/util/ViewUtil.kt b/app/src/main/java/com/aurora/store/util/ViewUtil.kt new file mode 100644 index 000000000..2bf47cb31 --- /dev/null +++ b/app/src/main/java/com/aurora/store/util/ViewUtil.kt @@ -0,0 +1,71 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.util + +import android.app.Activity +import android.content.Context +import android.graphics.Color +import android.os.Build +import android.os.Bundle +import android.util.TypedValue +import android.view.View +import android.view.WindowManager +import androidx.annotation.RequiresApi +import androidx.core.app.ActivityOptionsCompat + + +object ViewUtil { + + fun getEmptyActivityBundle(context: Context?): Bundle? { + return ActivityOptionsCompat.makeCustomAnimation( + context!!, + android.R.anim.fade_in, + android.R.anim.fade_out + ).toBundle() + } + + @RequiresApi(Build.VERSION_CODES.LOLLIPOP) + fun configureActivityLayout(activity: Activity, isLight: Boolean) { + val window = activity.window + val params = window.attributes + params.flags = params.flags and WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS + window.attributes = params + window.statusBarColor = Color.TRANSPARENT + setFullScreenLightStatusBar(activity, isLight) + } + + private fun setFullScreenLightStatusBar(activity: Activity, isLight: Boolean) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + var flags = activity.window.decorView.systemUiVisibility + if (isLight) + flags = flags or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR + flags = flags or View.SYSTEM_UI_FLAG_LAYOUT_STABLE + flags = flags or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + activity.window.decorView.systemUiVisibility = flags + } + } + + fun getStyledAttribute(context: Context, styleID: Int): Int { + val arr = context.obtainStyledAttributes(TypedValue().data, intArrayOf(styleID)) + val styledColor = arr.getColor(0, Color.WHITE) + arr.recycle() + return styledColor + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/util/extensions/Collection.kt b/app/src/main/java/com/aurora/store/util/extensions/Collection.kt new file mode 100644 index 000000000..6acdca890 --- /dev/null +++ b/app/src/main/java/com/aurora/store/util/extensions/Collection.kt @@ -0,0 +1,30 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.util.extensions + +fun MutableList.flushAndAdd(list: List) { + clear() + addAll(list) +} + +fun MutableSet.flushAndAdd(list: Set) { + clear() + addAll(list) +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/util/extensions/Commons.kt b/app/src/main/java/com/aurora/store/util/extensions/Commons.kt new file mode 100644 index 000000000..f0ee7dbe0 --- /dev/null +++ b/app/src/main/java/com/aurora/store/util/extensions/Commons.kt @@ -0,0 +1,46 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.util.extensions + +import android.os.Build +import android.text.format.DateFormat +import java.util.* + +fun Long.toDate(): String { + val calendar = Calendar.getInstance(Locale.getDefault()) + calendar.timeInMillis = this + return DateFormat.format("dd/MM/yy", calendar).toString() +} + +fun isLAndAbove(): Boolean { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP +} + +fun isNAndAbove(): Boolean { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N +} + +fun isPAndAbove(): Boolean { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.P +} + +fun isQAndAbove(): Boolean { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/util/extensions/Context.kt b/app/src/main/java/com/aurora/store/util/extensions/Context.kt new file mode 100644 index 000000000..7c509c7f8 --- /dev/null +++ b/app/src/main/java/com/aurora/store/util/extensions/Context.kt @@ -0,0 +1,114 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.util.extensions + +import android.app.AlarmManager +import android.app.PendingIntent +import android.content.ClipData +import android.content.ClipboardManager +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.os.Build +import androidx.appcompat.app.AppCompatActivity +import androidx.core.app.ShareCompat +import com.aurora.Constants +import com.aurora.gplayapi.data.models.App +import com.aurora.store.MainActivity +import com.aurora.store.R +import com.aurora.store.util.Log +import com.aurora.store.util.ViewUtil +import kotlin.system.exitProcess + + +fun Context.browse(url: String) { + try { + startActivity( + Intent( + Intent.ACTION_VIEW, + Uri.parse(url) + ) + ) + } catch (e: Exception) { + Log.e(e.message) + } +} + +fun Context.share(app: App) { + try { + ShareCompat.IntentBuilder.from(this as AppCompatActivity) + .setType("text/plain") + .setChooserTitle(getString(R.string.action_share)) + .setSubject(app.displayName) + .setText(Constants.SHARE_URL + app.packageName) + .startChooser() + } catch (e: Exception) { + + } +} + +fun Context.openInfo(packageName: String) { + try { + val intent = Intent( + "android.settings.APPLICATION_DETAILS_SETTINGS", + Uri.parse("package:$packageName") + ) + startActivity(intent) + } catch (e: Exception) { + + } +} + +fun Context.open(className: Class, newTask: Boolean = false) { + val intent = Intent(this, className) + if (newTask) + intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK + startActivity( + intent, + ViewUtil.getEmptyActivityBundle(this) + ) +} + +fun AppCompatActivity.close() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + finishAfterTransition() + } else { + finish() + } +} + +fun Context.restartApp() { + val pendingIntent = PendingIntent.getActivity( + this, + 1337, + Intent(this, MainActivity::class.java), + PendingIntent.FLAG_CANCEL_CURRENT + ) + + val alarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager + alarmManager.set(AlarmManager.RTC, System.currentTimeMillis() + 100, pendingIntent) + exitProcess(0) +} + +fun Context.copyToClipBoard(data: String?) { + val clipboard = getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + val clip = ClipData.newPlainText("Download Url", data) + clipboard.setPrimaryClip(clip) +} diff --git a/app/src/main/java/com/aurora/store/util/extensions/Glide.kt b/app/src/main/java/com/aurora/store/util/extensions/Glide.kt new file mode 100644 index 000000000..048e8c1d1 --- /dev/null +++ b/app/src/main/java/com/aurora/store/util/extensions/Glide.kt @@ -0,0 +1,165 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.util.extensions + +import android.graphics.Bitmap +import android.graphics.drawable.Drawable +import android.net.Uri +import android.widget.ImageView +import androidx.annotation.DrawableRes +import androidx.annotation.RawRes +import com.bumptech.glide.Glide +import com.bumptech.glide.TransitionOptions +import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade +import com.bumptech.glide.request.RequestOptions +import com.bumptech.glide.request.target.ViewTarget +import com.bumptech.glide.request.transition.DrawableCrossFadeFactory +import java.io.File + +fun ImageView.load( + bitmap: Bitmap?, + transitionOptions: TransitionOptions<*, Drawable>? = null, + requestOptions: RequestOptions? = null +): ViewTarget = loadAny(bitmap, transitionOptions, requestOptions) + +@JvmSynthetic +fun ImageView.load( + byteArray: ByteArray?, + transitionOptions: TransitionOptions<*, Drawable>? = null, + requestOptions: RequestOptions? = null +): ViewTarget = loadAny(byteArray, transitionOptions, requestOptions) + +@JvmSynthetic +fun ImageView.load( + drawable: Drawable?, + transitionOptions: TransitionOptions<*, Drawable>? = null, + requestOptions: RequestOptions? = null +): ViewTarget = loadAny(drawable, transitionOptions, requestOptions) + +@JvmSynthetic +fun ImageView.load( + @RawRes @DrawableRes resourceId: Int?, + transitionOptions: TransitionOptions<*, Drawable>? = null, + requestOptions: RequestOptions? = null +): ViewTarget = loadAny(resourceId, transitionOptions, requestOptions) + +@JvmSynthetic +fun ImageView.load( + uri: Uri?, + transitionOptions: TransitionOptions<*, Drawable>? = null, + requestOptions: RequestOptions? = null +): ViewTarget = loadAny(uri, transitionOptions, requestOptions) + +@JvmSynthetic +fun ImageView.load( + string: String?, + transitionOptions: TransitionOptions<*, Drawable>? = null, + requestOptions: RequestOptions? = null +): ViewTarget = loadAny(string, transitionOptions, requestOptions) + +@JvmSynthetic +fun ImageView.load( + file: File?, + transitionOptions: TransitionOptions<*, Drawable>? = null, + requestOptions: RequestOptions? = null +): ViewTarget = loadAny(file, transitionOptions, requestOptions) + +@JvmSynthetic +inline fun ImageView.load( + bitmap: Bitmap?, + transitionOptions: TransitionOptions<*, Drawable>? = null, + requestOptions: RequestOptions.() -> Unit +): ViewTarget = loadAny(bitmap, transitionOptions, requestOptions) + +@JvmSynthetic +inline fun ImageView.load( + byteArray: ByteArray?, + transitionOptions: TransitionOptions<*, Drawable>? = null, + requestOptions: RequestOptions.() -> Unit +): ViewTarget = loadAny(byteArray, transitionOptions, requestOptions) + +@JvmSynthetic +inline fun ImageView.load( + drawable: Drawable?, + transitionOptions: TransitionOptions<*, Drawable>? = null, + requestOptions: RequestOptions.() -> Unit +): ViewTarget = loadAny(drawable, transitionOptions, requestOptions) + +@JvmSynthetic +inline fun ImageView.load( + @RawRes @DrawableRes resourceId: Int?, + transitionOptions: TransitionOptions<*, Drawable>? = null, + requestOptions: RequestOptions.() -> Unit +): ViewTarget = loadAny(resourceId, transitionOptions, requestOptions) + +@JvmSynthetic +inline fun ImageView.load( + uri: Uri?, + transitionOptions: TransitionOptions<*, Drawable>? = null, + requestOptions: RequestOptions.() -> Unit +): ViewTarget = loadAny(uri, transitionOptions, requestOptions) + +@JvmSynthetic +inline fun ImageView.load( + string: String?, + transitionOptions: TransitionOptions<*, Drawable>? = null, + requestOptions: RequestOptions.() -> Unit +): ViewTarget = loadAny(string, transitionOptions, requestOptions) + +@JvmSynthetic +inline fun ImageView.load( + file: File?, + transitionOptions: TransitionOptions<*, Drawable>? = null, + requestOptions: RequestOptions.() -> Unit +): ViewTarget = loadAny(file, transitionOptions, requestOptions) + +@JvmSynthetic +fun ImageView.loadAny( + data: Any?, + transitionOptions: TransitionOptions<*, Drawable>? = null, + requestOptions: RequestOptions? = null +): ViewTarget { + return Glide.with(this) + .load(data) + .apply { + transitionOptions?.let { transition(it) } + requestOptions?.let { apply(it) } + } + .into(this) +} + +@JvmSynthetic +inline fun ImageView.loadAny( + data: Any?, + transitionOptions: TransitionOptions<*, Drawable>? = null, + requestOptions: RequestOptions.() -> Unit +): ViewTarget { + val factory = DrawableCrossFadeFactory.Builder().setCrossFadeEnabled(true).build() + return Glide.with(this) + .load(data) + .transition(withCrossFade(factory)) + .apply(RequestOptions().apply(requestOptions)) + .into(this) +} + +@JvmSynthetic +fun ImageView.clear() { + //Glide.with(this).clear(this) +} diff --git a/app/src/main/java/com/aurora/store/util/extensions/Number.kt b/app/src/main/java/com/aurora/store/util/extensions/Number.kt new file mode 100644 index 000000000..d58817d19 --- /dev/null +++ b/app/src/main/java/com/aurora/store/util/extensions/Number.kt @@ -0,0 +1,28 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.util.extensions + +import android.content.res.Resources + +val Number.dp: Number + get() = (this.toFloat() / Resources.getSystem().displayMetrics.density).toInt() + +val Number.px: Number + get() = (this.toFloat() * Resources.getSystem().displayMetrics.density).toInt() \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/util/extensions/ThemeEngine.kt b/app/src/main/java/com/aurora/store/util/extensions/ThemeEngine.kt new file mode 100644 index 000000000..b4c6f13a6 --- /dev/null +++ b/app/src/main/java/com/aurora/store/util/extensions/ThemeEngine.kt @@ -0,0 +1,148 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.util.extensions + +import android.content.Intent +import android.content.res.Configuration +import android.graphics.Color +import android.os.Build +import android.view.View +import android.view.WindowInsetsController +import androidx.annotation.RequiresApi +import androidx.appcompat.app.AppCompatActivity +import androidx.appcompat.app.AppCompatDelegate +import androidx.core.graphics.ColorUtils +import androidx.fragment.app.Fragment +import com.aurora.Constants +import com.aurora.store.R +import com.aurora.store.util.CommonUtil +import com.aurora.store.util.ViewUtil + + +fun Fragment.applyTheme( + themeId: Int, + accentId: Int = 1, + shouldApplyTransition: Boolean = true, + position: Int = 2 +) { + val themeStyle = CommonUtil.getThemeStyleById(themeId) + + if (themeStyle == 0) { + AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM) + (requireActivity() as AppCompatActivity).applyDayNightMask() + } else { + AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES) + } + + /*Apply Theme*/ + requireContext().theme.applyStyle(themeStyle, true) + + /*Apply transition only on AppCompatActivity*/ + if (shouldApplyTransition) + (requireActivity() as AppCompatActivity).transitionRecreate(position) + else + (requireActivity() as AppCompatActivity).recreate() + + if (themeId == 1) { + (requireActivity() as AppCompatActivity).setLightConfiguration() + } +} + +fun AppCompatActivity.transitionRecreate(position: Int = 2) { + val intent = Intent(this, javaClass) + intent.putExtra(Constants.INT_EXTRA, position) + //intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) + startActivity(intent) + overridePendingTransition(R.anim.fade_in, R.anim.fade_out) +} + +fun AppCompatActivity.applyTheme(themeId: Int, accentId: Int = 1) { + val themeStyle = CommonUtil.getThemeStyleById(themeId) + val accentStyle = CommonUtil.getAccentStyleById(accentId) + + if (themeStyle == 0) { + AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM) + applyDayNightMask() + } else { + AppCompatDelegate.setDefaultNightMode(AppCompatDelegate.MODE_NIGHT_YES) + } + + /*Apply Theme*/ + setTheme(themeStyle) + + /*Apply Accent*/ + theme.applyStyle(accentStyle, true) + + /*Apply Light Configuration*/ + if (themeId == 1) { + setLightConfiguration() + } +} + +fun AppCompatActivity.applyDayNightMask() { + val currentNightMode = resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK + if (currentNightMode == Configuration.UI_MODE_NIGHT_NO) { + setLightConfiguration() + } +} + +fun AppCompatActivity.setLightConfiguration() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) { + setLightConfigurationO() + } else { + setLightConfigurationO() + } +} + +@RequiresApi(Build.VERSION_CODES.R) +private fun AppCompatActivity.setLightConfigurationR() { + window?.insetsController?.setSystemBarsAppearance( + WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS + or WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS, + WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS + ) +} + +private fun AppCompatActivity.setLightConfigurationO() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + setLightStatusBar() + setLightNavigationBar() + } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + window.statusBarColor = ColorUtils.setAlphaComponent(Color.BLACK, 120) + } +} + +private fun AppCompatActivity.setLightStatusBar() { + var flags = window.decorView.systemUiVisibility + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + flags = flags or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR + } + window.decorView.systemUiVisibility = flags +} + +private fun AppCompatActivity.setLightNavigationBar() { + var flags = window.decorView.systemUiVisibility + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + window.navigationBarColor = + ViewUtil.getStyledAttribute(this, android.R.attr.colorBackground) + flags = flags or View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR + } + window.decorView.systemUiVisibility = flags +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/util/extensions/Threading.kt b/app/src/main/java/com/aurora/store/util/extensions/Threading.kt new file mode 100644 index 000000000..8a6ef856d --- /dev/null +++ b/app/src/main/java/com/aurora/store/util/extensions/Threading.kt @@ -0,0 +1,41 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.util.extensions + +import android.os.Handler +import android.os.Looper + +fun runAsync(action: () -> Unit) = Thread(Runnable(action)).start() + +fun runOnUiThread(action: () -> Unit) { + if (isMainLooperAlive()) { + action() + } else { + Handler(Looper.getMainLooper()).post(Runnable(action)) + } +} + +fun runDelayed(delayMillis: Long, action: () -> Unit) = + Handler().postDelayed(Runnable(action), delayMillis) + +fun runDelayedOnUiThread(delayMillis: Long, action: () -> Unit) = + Handler(Looper.getMainLooper()).postDelayed(Runnable(action), delayMillis) + +private fun isMainLooperAlive() = Looper.myLooper() == Looper.getMainLooper() \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/util/extensions/Toast.kt b/app/src/main/java/com/aurora/store/util/extensions/Toast.kt new file mode 100644 index 000000000..b83289a6e --- /dev/null +++ b/app/src/main/java/com/aurora/store/util/extensions/Toast.kt @@ -0,0 +1,36 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.util.extensions + +import android.content.Context +import android.widget.Toast +import androidx.annotation.StringRes + +fun Context.toast(text: CharSequence): Toast = + Toast.makeText(this, text, Toast.LENGTH_SHORT).apply { show() } + +fun Context.longToast(text: CharSequence): Toast = + Toast.makeText(this, text, Toast.LENGTH_LONG).apply { show() } + +fun Context.toast(@StringRes resId: Int): Toast = + Toast.makeText(this, resId, Toast.LENGTH_SHORT).apply { show() } + +fun Context.longToast(@StringRes resId: Int): Toast = + Toast.makeText(this, resId, Toast.LENGTH_LONG).apply { show() } diff --git a/app/src/main/java/com/aurora/store/util/extensions/View.kt b/app/src/main/java/com/aurora/store/util/extensions/View.kt new file mode 100644 index 000000000..06284e99f --- /dev/null +++ b/app/src/main/java/com/aurora/store/util/extensions/View.kt @@ -0,0 +1,81 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.util.extensions + +import android.content.Context +import android.view.View +import android.view.inputmethod.InputMethodManager + +fun View.isVisible() = visibility == View.VISIBLE + +fun View.isGone() = visibility == View.GONE + +fun View.isInvisible() = visibility == View.INVISIBLE + +fun View.show() { + visibility = View.VISIBLE +} + +fun View.hide() { + visibility = View.GONE +} + +fun View.showKeyboard() { + val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + this.requestFocus() + imm.showSoftInput(this, 0) +} + +fun View.hideKeyboard(): Boolean { + try { + val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + return imm.hideSoftInputFromWindow(windowToken, 0) + } catch (ignored: RuntimeException) { + } + return false +} + +fun View.setOnSingleClickListener(tolerance: Long = 500, onClick: (v: View) -> Unit) { + var lastClicked = 0L + val currentTimeMillis = System.currentTimeMillis() + setOnClickListener { + if (currentTimeMillis - lastClicked > tolerance) { + onClick(it) + lastClicked = currentTimeMillis + } + } +} + +fun View.rotate(resetToZero: Boolean = true, duration: Long = 400) { + if (resetToZero) + rotation = 0f + animate().rotation(360f).setDuration(duration).start() +} + +fun View.flip(resetToZero: Boolean = true, duration: Long = 400) { + if (resetToZero) + rotation = 0f + animate().rotation(180f).setDuration(duration).start() +} + +fun View.getString(resourceId: Int): String { + return context.getString(resourceId) +} + diff --git a/app/src/main/java/com/aurora/store/view/custom/ActionButton.kt b/app/src/main/java/com/aurora/store/view/custom/ActionButton.kt new file mode 100644 index 000000000..53bbe5ed6 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/custom/ActionButton.kt @@ -0,0 +1,86 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.custom + +import android.content.Context +import android.os.Build +import android.util.AttributeSet +import android.view.View +import android.widget.RelativeLayout +import androidx.annotation.RequiresApi +import com.aurora.store.R +import com.aurora.store.databinding.ViewActionButtonBinding + +class ActionButton : RelativeLayout { + + private lateinit var B: ViewActionButtonBinding + + constructor(context: Context) : super(context) { + init(context, null) + } + + constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) { + init(context, attrs) + } + + constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super( + context, + attrs, + defStyleAttr + ) { + init(context, attrs) + } + + @RequiresApi(Build.VERSION_CODES.LOLLIPOP) + constructor( + context: Context, + attrs: AttributeSet?, + defStyleAttr: Int, + defStyleRes: Int + ) : super(context, attrs, defStyleAttr, defStyleRes) { + init(context, attrs) + } + + private fun init(context: Context, attrs: AttributeSet?) { + val view = inflate(context, R.layout.view_action_button, this) + B = ViewActionButtonBinding.bind(view) + + val typedArray = context.obtainStyledAttributes(attrs, R.styleable.ActionButton) + val btnTxt = typedArray.getString(R.styleable.ActionButton_btnActionText) + B.btn.text = btnTxt + typedArray.recycle() + } + + fun setText(text: String) { + B.btn.text = text + } + + fun updateProgress(isVisible: Boolean) { + if (isVisible) + B.progress.visibility = View.VISIBLE + else + + B.progress.visibility = View.INVISIBLE + } + + fun addOnClickListener(onClickListener: OnClickListener) { + B.btn.setOnClickListener(onClickListener) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/custom/CubicBezierInterpolator.java b/app/src/main/java/com/aurora/store/view/custom/CubicBezierInterpolator.java new file mode 100644 index 000000000..ad993a9df --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/custom/CubicBezierInterpolator.java @@ -0,0 +1,94 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.custom; + +import android.graphics.PointF; +import android.view.animation.Interpolator; + +public class CubicBezierInterpolator implements Interpolator { + + public static final CubicBezierInterpolator DEFAULT = new CubicBezierInterpolator(0.25, 0.1, 0.25, 1); + public static final CubicBezierInterpolator EASE_OUT = new CubicBezierInterpolator(0, 0, .58, 1); + public static final CubicBezierInterpolator EASE_OUT_QUINT = new CubicBezierInterpolator(.23, 1, .32, 1); + public static final CubicBezierInterpolator EASE_IN = new CubicBezierInterpolator(.42, 0, 1, 1); + public static final CubicBezierInterpolator EASE_BOTH = new CubicBezierInterpolator(.42, 0, .58, 1); + public static final CubicBezierInterpolator EASE_IN_OUT_QUAD = new CubicBezierInterpolator(0.455, 0.03, 0.515, 0.955); + + protected PointF start; + protected PointF end; + protected PointF a = new PointF(); + protected PointF b = new PointF(); + protected PointF c = new PointF(); + + public CubicBezierInterpolator(PointF start, PointF end) throws IllegalArgumentException { + if (start.x < 0 || start.x > 1) { + throw new IllegalArgumentException("startX value must be in the range [0, 1]"); + } + if (end.x < 0 || end.x > 1) { + throw new IllegalArgumentException("endX value must be in the range [0, 1]"); + } + this.start = start; + this.end = end; + } + + public CubicBezierInterpolator(float startX, float startY, float endX, float endY) { + this(new PointF(startX, startY), new PointF(endX, endY)); + } + + public CubicBezierInterpolator(double startX, double startY, double endX, double endY) { + this((float) startX, (float) startY, (float) endX, (float) endY); + } + + @Override + public float getInterpolation(float time) { + return getBezierCoordinateY(getXForTime(time)); + } + + protected float getBezierCoordinateY(float time) { + c.y = 3 * start.y; + b.y = 3 * (end.y - start.y) - c.y; + a.y = 1 - c.y - b.y; + return time * (c.y + time * (b.y + time * a.y)); + } + + protected float getXForTime(float time) { + float x = time; + float z; + for (int i = 1; i < 14; i++) { + z = getBezierCoordinateX(x) - time; + if (Math.abs(z) < 1e-3) { + break; + } + x -= z / getXDerivate(x); + } + return x; + } + + private float getXDerivate(float t) { + return c.x + t * (2 * b.x + 3 * a.x * t); + } + + private float getBezierCoordinateX(float time) { + c.x = 3 * start.x; + b.x = 3 * (end.x - start.x) - c.x; + a.x = 1 - c.x - b.x; + return time * (c.x + time * (b.x + time * a.x)); + } +} diff --git a/app/src/main/java/com/aurora/store/view/custom/RatingView.kt b/app/src/main/java/com/aurora/store/view/custom/RatingView.kt new file mode 100644 index 000000000..33ccf0c67 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/custom/RatingView.kt @@ -0,0 +1,71 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.custom + +import android.content.Context +import android.os.Build +import android.util.AttributeSet +import android.widget.RelativeLayout +import androidx.annotation.RequiresApi +import com.aurora.store.R +import com.aurora.store.databinding.ViewRatingBinding + +class RatingView : RelativeLayout { + + private lateinit var B: ViewRatingBinding + + var number = 0 + var max = 0 + var rating = 0 + + constructor(context: Context, number: Int, max: Int, rating: Int) : super(context) { + this.number = number + this.max = max + this.rating = rating + init(context) + } + + constructor(context: Context?) : super(context) {} + constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) {} + constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super( + context, + attrs, + defStyleAttr + ) { + } + + @RequiresApi(Build.VERSION_CODES.LOLLIPOP) + constructor( + context: Context?, + attrs: AttributeSet?, + defStyleAttr: Int, + defStyleRes: Int + ) : super(context, attrs, defStyleAttr, defStyleRes) { + } + + private fun init(context: Context) { + val view = inflate(context, R.layout.view_rating, this) + B = ViewRatingBinding.bind(view) + + B.avgNum.text = number.toString() + B.avgRating.max = max + B.avgRating.progress = rating + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/custom/StateButton.kt b/app/src/main/java/com/aurora/store/view/custom/StateButton.kt new file mode 100644 index 000000000..91b05954f --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/custom/StateButton.kt @@ -0,0 +1,89 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.custom + +import android.content.Context +import android.os.Build +import android.util.AttributeSet +import android.view.View +import android.widget.RelativeLayout +import androidx.annotation.RequiresApi +import androidx.core.content.ContextCompat +import com.aurora.store.R +import com.aurora.store.databinding.ViewStateButtonBinding + +class StateButton : RelativeLayout { + + private lateinit var B: ViewStateButtonBinding + + constructor(context: Context) : super(context) { + init(context, null) + } + + constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) { + init(context, attrs) + } + + constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super( + context, + attrs, + defStyleAttr + ) { + init(context, attrs) + } + + @RequiresApi(Build.VERSION_CODES.LOLLIPOP) + constructor( + context: Context, + attrs: AttributeSet?, + defStyleAttr: Int, + defStyleRes: Int + ) : super(context, attrs, defStyleAttr, defStyleRes) { + init(context, attrs) + } + + private fun init(context: Context, attrs: AttributeSet?) { + val view = inflate(context, R.layout.view_state_button, this) + B = ViewStateButtonBinding.bind(view) + + val typedArray = context.obtainStyledAttributes(attrs, R.styleable.StateButton) + val btnTxt = typedArray.getString(R.styleable.StateButton_btnStateText) + val btnIcon = typedArray.getResourceId( + R.styleable.StateButton_btnStateIcon, + R.drawable.ic_arrow_right + ) + + B.btn.text = btnTxt + B.btn.icon = ContextCompat.getDrawable(context, btnIcon) + typedArray.recycle() + } + + fun updateProgress(isVisible: Boolean) { + if (isVisible) + B.progress.visibility = View.VISIBLE + else + + B.progress.visibility = View.INVISIBLE + } + + fun addOnClickListener(onClickListener: OnClickListener) { + B.btn.setOnClickListener(onClickListener) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/custom/layouts/ActionHeaderLayout.kt b/app/src/main/java/com/aurora/store/view/custom/layouts/ActionHeaderLayout.kt new file mode 100644 index 000000000..ca54b2ca6 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/custom/layouts/ActionHeaderLayout.kt @@ -0,0 +1,69 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.custom.layouts + +import android.content.Context +import android.util.AttributeSet +import android.view.View +import android.widget.RelativeLayout +import com.aurora.store.R +import com.aurora.store.databinding.ViewActionHeaderBinding + +class ActionHeaderLayout : RelativeLayout { + + private lateinit var B: ViewActionHeaderBinding + + constructor(context: Context) : super(context) { + init(context, null) + } + + constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) { + init(context, attrs) + } + + constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super( + context, + attrs, + defStyleAttr + ) { + init(context, attrs) + } + + private fun init(context: Context, attrs: AttributeSet?) { + val view = inflate(context, R.layout.view_action_header, this) + B = ViewActionHeaderBinding.bind(view) + + val typedArray = context.obtainStyledAttributes(attrs, R.styleable.ActionHeaderLayout) + val textPrimary = typedArray.getString(R.styleable.ActionHeaderLayout_headerTitle) + + textPrimary?.let { + B.txtTitle.text = it + } + } + + fun setHeader(header: String?) { + B.txtTitle.text = header + } + + fun addClickListener(onclickListener: OnClickListener?) { + B.imgAction.visibility = View.VISIBLE + B.imgAction.setOnClickListener(onclickListener) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/custom/layouts/ViewDevInfo.kt b/app/src/main/java/com/aurora/store/view/custom/layouts/ViewDevInfo.kt new file mode 100644 index 000000000..abccd9ac4 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/custom/layouts/ViewDevInfo.kt @@ -0,0 +1,83 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.custom.layouts + +import android.content.Context +import android.os.Build +import android.util.AttributeSet +import android.widget.RelativeLayout +import androidx.annotation.RequiresApi +import com.aurora.store.R +import com.aurora.store.databinding.ViewDevInfoBinding + +class ViewDevInfo : RelativeLayout { + + private lateinit var B: ViewDevInfoBinding + + constructor(context: Context) : super(context) { + init(context, null) + } + + constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) { + init(context, attrs) + } + + constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super( + context, + attrs, + defStyleAttr + ) { + init(context, attrs) + } + + @RequiresApi(Build.VERSION_CODES.LOLLIPOP) + constructor( + context: Context, + attrs: AttributeSet?, + defStyleAttr: Int, + defStyleRes: Int + ) : super(context, attrs, defStyleAttr, defStyleRes) { + init(context, attrs) + } + + private fun init(context: Context, attrs: AttributeSet?) { + val view = inflate(context, R.layout.view_dev_info, this) + B = ViewDevInfoBinding.bind(view) + + val typedArray = context.obtainStyledAttributes(attrs, R.styleable.DevInfoLayout) + val icon = typedArray.getResourceId( + R.styleable.DevInfoLayout_imgIcon, + R.drawable.ic_map_marker + ) + + val textPrimary = typedArray.getString(R.styleable.DevInfoLayout_txtTitle) + val textSecondary = typedArray.getString(R.styleable.DevInfoLayout_txtSubtitle) + + B.img.setImageResource(icon) + B.txtTitle.text = textPrimary + B.txtSubtitle.text = textSecondary + typedArray.recycle() + } + + fun setTxtSubtitle(text: String?) { + B.txtSubtitle.text = text + invalidate() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/custom/preference/AuroraListPreference.kt b/app/src/main/java/com/aurora/store/view/custom/preference/AuroraListPreference.kt new file mode 100644 index 000000000..75eb16752 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/custom/preference/AuroraListPreference.kt @@ -0,0 +1,60 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.custom.preference + +import android.content.Context +import android.util.AttributeSet +import androidx.preference.ListPreference + +class AuroraListPreference : ListPreference { + + constructor(context: Context) : super(context) { + + } + + constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) { + + } + + constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super( + context, + attrs, + defStyleAttr + ) { + + } + + constructor( + context: Context, + attrs: AttributeSet?, + defStyleAttr: Int, + defStyleRes: Int + ) : super(context, attrs, defStyleAttr, defStyleRes) { + + } + + override fun getPersistedString(defaultReturnValue: String?): String? { + return getPersistedInt(-1).toString() + } + + override fun persistString(value: String?): Boolean { + return persistInt(Integer.valueOf(value)) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/custom/recycler/EndlessRecyclerOnScrollListener.kt b/app/src/main/java/com/aurora/store/view/custom/recycler/EndlessRecyclerOnScrollListener.kt new file mode 100644 index 000000000..a4ed6520a --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/custom/recycler/EndlessRecyclerOnScrollListener.kt @@ -0,0 +1,176 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.custom.recycler + +import android.view.View +import androidx.recyclerview.widget.OrientationHelper +import androidx.recyclerview.widget.RecyclerView + +abstract class EndlessRecyclerOnScrollListener : RecyclerView.OnScrollListener { + + private var enabled = true + private var previousTotal = 0 + private var isLoading = true + private var visibleThreshold = RecyclerView.NO_POSITION + + var firstVisibleItem: Int = 0 + private set + var visibleItemCount: Int = 0 + private set + var totalItemCount: Int = 0 + private set + + private var isOrientationHelperVertical: Boolean = false + private var orientationHelper: OrientationHelper? = null + + var currentPage = 0 + private set + + lateinit var layoutManager: RecyclerView.LayoutManager + private set + + constructor() + + constructor(layoutManager: RecyclerView.LayoutManager) { + this.layoutManager = layoutManager + } + + constructor(visibleThreshold: Int) { + this.visibleThreshold = visibleThreshold + } + + constructor(layoutManager: RecyclerView.LayoutManager, visibleThreshold: Int) { + this.layoutManager = layoutManager + this.visibleThreshold = visibleThreshold + } + + private fun findFirstVisibleItemPosition(recyclerView: RecyclerView): Int { + val child = findOneVisibleChild(0, layoutManager.childCount, false, true) + return if (child == null) RecyclerView.NO_POSITION else recyclerView.getChildAdapterPosition( + child + ) + } + + private fun findLastVisibleItemPosition(recyclerView: RecyclerView): Int { + val child = findOneVisibleChild(recyclerView.childCount - 1, -1, false, true) + return if (child == null) RecyclerView.NO_POSITION else recyclerView.getChildAdapterPosition( + child + ) + } + + private fun findOneVisibleChild( + fromIndex: Int, + toIndex: Int, + completelyVisible: Boolean, + acceptPartiallyVisible: Boolean + ): View? { + if (layoutManager.canScrollVertically() != isOrientationHelperVertical || orientationHelper == null) { + isOrientationHelperVertical = layoutManager.canScrollVertically() + orientationHelper = if (isOrientationHelperVertical) + OrientationHelper.createVerticalHelper(layoutManager) + else + OrientationHelper.createHorizontalHelper(layoutManager) + } + + val mOrientationHelper = this.orientationHelper ?: return null + + val start = mOrientationHelper.startAfterPadding + val end = mOrientationHelper.endAfterPadding + val next = if (toIndex > fromIndex) 1 else -1 + var partiallyVisible: View? = null + var i = fromIndex + while (i != toIndex) { + val child = layoutManager.getChildAt(i) + if (child != null) { + val childStart = mOrientationHelper.getDecoratedStart(child) + val childEnd = mOrientationHelper.getDecoratedEnd(child) + if (childStart < end && childEnd > start) { + if (completelyVisible) { + if (childStart >= start && childEnd <= end) { + return child + } else if (acceptPartiallyVisible && partiallyVisible == null) { + partiallyVisible = child + } + } else { + return child + } + } + } + i += next + } + return partiallyVisible + } + + override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { + super.onScrolled(recyclerView, dx, dy) + + if (enabled) { + if (!::layoutManager.isInitialized) { + layoutManager = recyclerView.layoutManager + ?: throw RuntimeException("A LayoutManager is required") + } + + + if (visibleThreshold == RecyclerView.NO_POSITION) { + visibleThreshold = + findLastVisibleItemPosition(recyclerView) - findFirstVisibleItemPosition( + recyclerView + ) + } + + visibleItemCount = recyclerView.childCount + totalItemCount = layoutManager.itemCount + firstVisibleItem = findFirstVisibleItemPosition(recyclerView) + + if (isLoading) { + if (totalItemCount > previousTotal) { + isLoading = false + previousTotal = totalItemCount + } + } + + if (!isLoading && totalItemCount - visibleItemCount <= firstVisibleItem + visibleThreshold) { + currentPage++ + onLoadMore(currentPage) + isLoading = true + } + } + } + + fun enable(): EndlessRecyclerOnScrollListener { + enabled = true + return this + } + + fun disable(): EndlessRecyclerOnScrollListener { + enabled = false + return this + } + + @JvmOverloads + fun resetPageCount(page: Int = 0) { + previousTotal = 0 + isLoading = true + currentPage = page + onLoadMore(currentPage) + } + + abstract fun onLoadMore(currentPage: Int) +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/controller/CategoryCarouselController.kt b/app/src/main/java/com/aurora/store/view/epoxy/controller/CategoryCarouselController.kt new file mode 100644 index 000000000..87f8c4bb5 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/controller/CategoryCarouselController.kt @@ -0,0 +1,29 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.epoxy.controller + +import com.aurora.gplayapi.data.models.StreamCluster + +class CategoryCarouselController(callbacks: Callbacks) : GenericCarouselController(callbacks) { + + override fun applyFilter(streamBundle: StreamCluster): Boolean { + return streamBundle.clusterAppList.isNotEmpty() //Filter empty clusters + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/controller/EarlyAccessCarouselController.kt b/app/src/main/java/com/aurora/store/view/epoxy/controller/EarlyAccessCarouselController.kt new file mode 100644 index 000000000..cf93bccf8 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/controller/EarlyAccessCarouselController.kt @@ -0,0 +1,30 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.epoxy.controller + +import com.aurora.gplayapi.data.models.StreamCluster + +class EarlyAccessCarouselController(callbacks: Callbacks) : GenericCarouselController(callbacks) { + + override fun applyFilter(streamBundle: StreamCluster): Boolean { + return streamBundle.clusterTitle.isNotBlank() //Filter noisy cluster + && streamBundle.clusterAppList.isNotEmpty() //Filter empty clusters + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/controller/EditorChoiceController.kt b/app/src/main/java/com/aurora/store/view/epoxy/controller/EditorChoiceController.kt new file mode 100644 index 000000000..cf6252840 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/controller/EditorChoiceController.kt @@ -0,0 +1,50 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.epoxy.controller + +import com.airbnb.epoxy.TypedEpoxyController +import com.aurora.gplayapi.data.models.editor.EditorChoiceBundle +import com.aurora.gplayapi.data.models.editor.EditorChoiceCluster +import com.aurora.store.view.epoxy.groups.EditorChoiceModelGroup +import com.aurora.store.view.epoxy.views.EditorHeadViewModel_ + +class EditorChoiceController(private val callbacks: Callbacks) : + TypedEpoxyController>() { + + interface Callbacks { + fun onClick(editorChoiceCluster: EditorChoiceCluster) + } + + override fun buildModels(editorChoiceBundles: List) { + editorChoiceBundles.forEach { editorChoiceBundle -> + val idPrefix = editorChoiceBundle.id + + add( + EditorHeadViewModel_() + .id("header_${idPrefix}") + .title(editorChoiceBundle.bundleTitle) + ) + + editorChoiceBundle.bundleChoiceClusters.forEach { + add(EditorChoiceModelGroup(it, callbacks)) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/controller/FlexLayoutManager.kt b/app/src/main/java/com/aurora/store/view/epoxy/controller/FlexLayoutManager.kt new file mode 100644 index 000000000..5669bdd83 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/controller/FlexLayoutManager.kt @@ -0,0 +1,47 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.gara.store.view.epoxy.controller + +import android.content.Context +import android.util.AttributeSet +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.google.android.flexbox.FlexboxLayoutManager + +class FlexLayoutManager : FlexboxLayoutManager { + constructor(context: Context?) : super(context) + constructor(context: Context?, flexDirection: Int) : super(context, flexDirection) + constructor(context: Context?, flexDirection: Int, flexWrap: Int) : super( + context, + flexDirection, + flexWrap + ) + + constructor( + context: Context?, + attrs: AttributeSet?, + defStyleAttr: Int, + defStyleRes: Int + ) : super(context, attrs, defStyleAttr, defStyleRes) + + override fun generateLayoutParams(lp: ViewGroup.LayoutParams): RecyclerView.LayoutParams { + return LayoutParams(lp) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/controller/GenericCarouselController.kt b/app/src/main/java/com/aurora/store/view/epoxy/controller/GenericCarouselController.kt new file mode 100644 index 000000000..5a25b5e58 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/controller/GenericCarouselController.kt @@ -0,0 +1,102 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.epoxy.controller + +import com.airbnb.epoxy.TypedEpoxyController +import com.aurora.gplayapi.data.models.App +import com.aurora.gplayapi.data.models.StreamBundle +import com.aurora.gplayapi.data.models.StreamCluster +import com.aurora.store.R +import com.aurora.store.view.epoxy.groups.CarouselModelGroup +import com.aurora.store.view.epoxy.groups.CarouselShimmerGroup +import com.aurora.store.view.epoxy.views.AppListViewModel_ +import com.aurora.store.view.epoxy.views.AppProgressViewModel_ +import com.aurora.store.view.epoxy.views.app.NoAppViewModel_ + +open class GenericCarouselController(private val callbacks: Callbacks) : + + TypedEpoxyController() { + + interface Callbacks { + fun onHeaderClicked(streamCluster: StreamCluster) + fun onClusterScrolled(streamCluster: StreamCluster) + fun onAppClick(app: App) + fun onAppLongClick(app: App) + } + + open fun applyFilter(streamBundle: StreamCluster): Boolean { + return streamBundle.clusterTitle.isNotBlank() //Filter noisy cluster + && streamBundle.clusterAppList.isNotEmpty() //Filter empty clusters + && streamBundle.clusterAppList.count() > 1 //Filter clusters with single apps (mostly promotions) + } + + override fun buildModels(streamBundle: StreamBundle?) { + setFilterDuplicates(true) + if (streamBundle == null) { + for (i in 1..2) { + add( + CarouselShimmerGroup() + .id(i) + ) + } + } else { + if (streamBundle.streamClusters.isEmpty()) { + add( + NoAppViewModel_() + .id("no_app") + .icon(R.drawable.ic_apps) + .message("No apps available") + ) + } else { + if (streamBundle.streamClusters.size == 1) { + streamBundle + .streamClusters + .values + .filter { applyFilter(it) } + .forEach { streamCluster -> + streamCluster.clusterAppList.forEach { + add( + AppListViewModel_() + .id(it.id) + .app(it) + .click { _ -> callbacks.onAppClick(it) } + ) + } + } + + } else { + streamBundle + .streamClusters + .values + .filter { applyFilter(it) } + .forEach { streamCluster -> + add(CarouselModelGroup(streamCluster, callbacks)) + } + + } + if (streamBundle.hasNext()) + add( + AppProgressViewModel_() + .id("progress") + ) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/groups/CarouselHorizontal.kt b/app/src/main/java/com/aurora/store/view/epoxy/groups/CarouselHorizontal.kt new file mode 100644 index 000000000..bd77894a2 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/groups/CarouselHorizontal.kt @@ -0,0 +1,36 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.epoxy.groups + +import android.content.Context +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.airbnb.epoxy.Carousel +import com.airbnb.epoxy.ModelView + +@ModelView(saveViewState = true, autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT) +class CarouselHorizontal(context: Context?) : Carousel(context) { + + override fun createLayoutManager(): LayoutManager { + return LinearLayoutManager(context, RecyclerView.HORIZONTAL, false) + } + + override fun getSnapHelperFactory(): Nothing? = null +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/groups/CarouselModelGroup.kt b/app/src/main/java/com/aurora/store/view/epoxy/groups/CarouselModelGroup.kt new file mode 100644 index 000000000..697048f2a --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/groups/CarouselModelGroup.kt @@ -0,0 +1,102 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.epoxy.groups + +import com.airbnb.epoxy.EpoxyModel +import com.airbnb.epoxy.EpoxyModelGroup +import com.aurora.gplayapi.data.models.StreamCluster +import com.aurora.store.R +import com.aurora.store.util.Log +import com.aurora.store.view.epoxy.controller.GenericCarouselController +import com.aurora.store.view.epoxy.views.AppProgressViewModel_ +import com.aurora.store.view.epoxy.views.AppViewModel_ +import com.aurora.store.view.epoxy.views.HeaderViewModel_ + +class CarouselModelGroup( + streamCluster: StreamCluster, + callbacks: GenericCarouselController.Callbacks +) : + EpoxyModelGroup( + R.layout.model_carousel_group, buildModels( + streamCluster, + callbacks + ) + ) { + companion object { + private fun buildModels( + streamCluster: StreamCluster, + callbacks: GenericCarouselController.Callbacks + ): List> { + val models = ArrayList>() + val clusterViewModels = mutableListOf>() + + val idPrefix = streamCluster.id + + models.add( + HeaderViewModel_() + .id("${idPrefix}_header") + .title(streamCluster.clusterTitle) + .browseUrl(streamCluster.clusterBrowseUrl) + .click { _ -> + callbacks.onHeaderClicked(streamCluster) + } + ) + + for (app in streamCluster.clusterAppList) { + clusterViewModels.add( + AppViewModel_() + .id(app.id) + .app(app) + .click { _ -> + callbacks.onAppClick(app) + } + .longClick { _ -> + callbacks.onAppLongClick(app) + false + } + .onBind { _, _, position -> + val itemCount = clusterViewModels.count() + if (itemCount >= 2) { + if (position == clusterViewModels.count() - 2) { + callbacks.onClusterScrolled(streamCluster) + Log.i("Cluster %s Scrolled", streamCluster.clusterTitle) + } + } + } + ) + } + + if (streamCluster.hasNext()) { + clusterViewModels.add( + AppProgressViewModel_() + .id("${idPrefix}_progress") + ) + } + + models.add( + CarouselHorizontalModel_() + .id("${idPrefix}_cluster") + .models(clusterViewModels) + ) + + return models + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/groups/CarouselShimmerGroup.kt b/app/src/main/java/com/aurora/store/view/epoxy/groups/CarouselShimmerGroup.kt new file mode 100644 index 000000000..01e12bf03 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/groups/CarouselShimmerGroup.kt @@ -0,0 +1,60 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.epoxy.groups + +import com.airbnb.epoxy.EpoxyModel +import com.airbnb.epoxy.EpoxyModelGroup +import com.aurora.store.R +import com.aurora.store.view.epoxy.views.shimmer.AppViewShimmerModel_ +import com.aurora.store.view.epoxy.views.shimmer.HeaderViewShimmerModel_ +import java.util.* +import kotlin.collections.ArrayList + +class CarouselShimmerGroup() : + EpoxyModelGroup( + R.layout.model_carousel_group, buildModels() + ) { + companion object { + private fun buildModels(): List> { + val models = ArrayList>() + val clusterViewModels = mutableListOf>() + val idPrefix = UUID.randomUUID() + + for (i in 1..8) { + clusterViewModels.add( + AppViewShimmerModel_() + .id(i) + ) + } + + models.add( + HeaderViewShimmerModel_() + .id("shimmer_header") + ) + + models.add( + CarouselHorizontalModel_() + .id("cluster_${idPrefix}") + .models(clusterViewModels) + ) + return models + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/groups/EditorChoiceModelGroup.kt b/app/src/main/java/com/aurora/store/view/epoxy/groups/EditorChoiceModelGroup.kt new file mode 100644 index 000000000..2db7ba125 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/groups/EditorChoiceModelGroup.kt @@ -0,0 +1,76 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.epoxy.groups + +import com.airbnb.epoxy.EpoxyModel +import com.airbnb.epoxy.EpoxyModelGroup +import com.aurora.gplayapi.data.models.editor.EditorChoiceCluster +import com.aurora.store.R +import com.aurora.store.view.epoxy.controller.EditorChoiceController +import com.aurora.store.view.epoxy.views.EditorImageViewModel_ +import com.aurora.store.view.epoxy.views.HeaderViewModel_ + +class EditorChoiceModelGroup( + editorChoiceCluster: EditorChoiceCluster, + callbacks: EditorChoiceController.Callbacks +) : + EpoxyModelGroup( + R.layout.model_editorchoice_group, buildModels( + editorChoiceCluster, + callbacks + ) + ) { + companion object { + private fun buildModels( + editorChoiceCluster: EditorChoiceCluster, + callbacks: EditorChoiceController.Callbacks + ): List> { + + val models = ArrayList>() + val clusterViewModels = mutableListOf>() + + val idPrefix = editorChoiceCluster.id + + models.add( + HeaderViewModel_() + .id("header_${idPrefix}") + .title(editorChoiceCluster.clusterTitle) + .browseUrl(editorChoiceCluster.clusterBrowseUrl) + .click { _ -> callbacks.onClick(editorChoiceCluster) } + ) + + editorChoiceCluster.clusterArtwork.forEach { + clusterViewModels.add( + EditorImageViewModel_() + .id("artwork_${idPrefix}") + .artwork(it) + ) + } + + models.add( + CarouselHorizontalModel_() + .id("cluster_${idPrefix}") + .models(clusterViewModels) + ) + + return models + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/views/AccentView.kt b/app/src/main/java/com/aurora/store/view/epoxy/views/AccentView.kt new file mode 100644 index 000000000..6202c8919 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/views/AccentView.kt @@ -0,0 +1,82 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.epoxy.views + +import android.content.Context +import android.content.res.ColorStateList +import android.graphics.Color +import android.util.AttributeSet +import android.widget.RelativeLayout +import androidx.core.view.isVisible +import com.airbnb.epoxy.CallbackProp +import com.airbnb.epoxy.ModelProp +import com.airbnb.epoxy.ModelView +import com.aurora.store.R +import com.aurora.store.data.model.Accent +import com.aurora.store.databinding.ViewAccentBinding + +@ModelView( + autoLayout = ModelView.Size.WRAP_WIDTH_WRAP_HEIGHT, + baseModelClass = BaseView::class +) +class AccentView : RelativeLayout { + + private lateinit var B: ViewAccentBinding + + constructor(context: Context?) : super(context) { + init(context, null) + } + + constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) { + init(context, attrs) + } + + constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super( + context, + attrs, + defStyleAttr + ) { + init(context, attrs) + } + + private fun init(context: Context?, attrs: AttributeSet?) { + val view = inflate(context, R.layout.view_accent, this) + B = ViewAccentBinding.bind(view) + } + + @ModelProp + fun accent(accent: Accent) { + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { + B.img.backgroundTintList = ColorStateList.valueOf(Color.parseColor(accent.accent)) + } else { + B.img.setBackgroundColor(Color.parseColor(accent.accent)) + } + } + + @ModelProp + fun markChecked(isChecked: Boolean) { + B.tick.isVisible = isChecked + } + + @CallbackProp + fun click(onClickListener: OnClickListener?) { + B.root.setOnClickListener(onClickListener) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/views/AppProgressView.kt b/app/src/main/java/com/aurora/store/view/epoxy/views/AppProgressView.kt new file mode 100644 index 000000000..dcbf64d0e --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/views/AppProgressView.kt @@ -0,0 +1,54 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.epoxy.views + +import android.content.Context +import android.util.AttributeSet +import android.widget.RelativeLayout +import com.airbnb.epoxy.ModelView +import com.aurora.store.R +import com.aurora.store.databinding.ViewAppProgressBinding + +@ModelView(autoLayout = ModelView.Size.WRAP_WIDTH_WRAP_HEIGHT) +class AppProgressView : RelativeLayout { + + private lateinit var B: ViewAppProgressBinding + + constructor(context: Context?) : super(context) { + init(context, null) + } + + constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) { + init(context, attrs) + } + + constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super( + context, + attrs, + defStyleAttr + ) { + init(context, attrs) + } + + private fun init(context: Context?, attrs: AttributeSet?) { + val view = inflate(context, R.layout.view_app_progress, this) + B = ViewAppProgressBinding.bind(view) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/views/BaseView.kt b/app/src/main/java/com/aurora/store/view/epoxy/views/BaseView.kt new file mode 100644 index 000000000..9f16de7b8 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/views/BaseView.kt @@ -0,0 +1,49 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.epoxy.views + +import android.view.View +import android.view.animation.AnimationUtils +import com.airbnb.epoxy.EpoxyModel + +abstract class BaseView : EpoxyModel() { + + override fun bind(view: T) { + super.bind(view) + when (view) { + is AppListView -> { + view.startAnimation( + AnimationUtils.loadAnimation( + view.context, + android.R.anim.fade_in + ) + ) + } + } + } + + override fun unbind(view: T) { + when (view) { + is AppListView -> { + view.clearAnimation() + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/views/BlackView.kt b/app/src/main/java/com/aurora/store/view/epoxy/views/BlackView.kt new file mode 100644 index 000000000..2cac41662 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/views/BlackView.kt @@ -0,0 +1,102 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.epoxy.views + +import android.content.Context +import android.util.AttributeSet +import android.widget.CompoundButton +import android.widget.RelativeLayout +import com.airbnb.epoxy.CallbackProp +import com.airbnb.epoxy.ModelProp +import com.airbnb.epoxy.ModelView +import com.airbnb.epoxy.OnViewRecycled +import com.aurora.store.R +import com.aurora.store.data.model.Black +import com.aurora.store.databinding.ViewBlackBinding +import com.aurora.store.util.extensions.clear +import com.aurora.store.util.extensions.load +import com.bumptech.glide.load.resource.bitmap.RoundedCorners + +@ModelView( + autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT, + baseModelClass = BaseView::class +) +class BlackView : RelativeLayout { + + private lateinit var B: ViewBlackBinding + + constructor(context: Context?) : super(context) { + init(context, null) + } + + constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) { + init(context, attrs) + } + + constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super( + context, + attrs, + defStyleAttr + ) { + init(context, attrs) + } + + private fun init(context: Context?, attrs: AttributeSet?) { + val view = inflate(context, R.layout.view_black, this) + B = ViewBlackBinding.bind(view) + } + + @ModelProp + fun black(black: Black) { + B.imgIcon.load(black.drawable) { + placeholder(R.drawable.bg_placeholder) + transform(RoundedCorners(25)) + } + + B.txtLine1.text = black.displayName + B.txtLine2.text = black.packageName + B.txtLine3.text = ("${black.versionName}.${black.versionCode}") + } + + @ModelProp + fun markChecked(isChecked: Boolean) { + B.checkbox.isChecked = isChecked + } + + @CallbackProp + fun checked(onCheckedChangeListener: CompoundButton.OnCheckedChangeListener?) { + B.checkbox.setOnCheckedChangeListener(onCheckedChangeListener) + } + + @CallbackProp + fun click(onClickListener: OnClickListener?) { + B.root.setOnClickListener(onClickListener) + } + + @CallbackProp + fun longClick(onClickListener: OnLongClickListener?) { + B.root.setOnLongClickListener(onClickListener) + } + + @OnViewRecycled + fun clear() { + B.imgIcon.clear() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/views/CategoryView.kt b/app/src/main/java/com/aurora/store/view/epoxy/views/CategoryView.kt new file mode 100644 index 000000000..7f220207b --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/views/CategoryView.kt @@ -0,0 +1,81 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.epoxy.views + +import android.content.Context +import android.util.AttributeSet +import android.widget.RelativeLayout +import com.airbnb.epoxy.CallbackProp +import com.airbnb.epoxy.ModelProp +import com.airbnb.epoxy.ModelView +import com.airbnb.epoxy.OnViewRecycled +import com.aurora.gplayapi.data.models.Category +import com.aurora.store.R +import com.aurora.store.databinding.ViewCategoryBinding +import com.aurora.store.util.extensions.clear +import com.aurora.store.util.extensions.load +import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions + +@ModelView( + autoLayout = ModelView.Size.WRAP_WIDTH_WRAP_HEIGHT, + baseModelClass = BaseView::class +) +class CategoryView : RelativeLayout { + + private lateinit var B: ViewCategoryBinding + + constructor(context: Context?) : super(context) { + init(context, null) + } + + constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) { + init(context, attrs) + } + + constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super( + context, + attrs, + defStyleAttr + ) { + init(context, attrs) + } + + private fun init(context: Context?, attrs: AttributeSet?) { + val view = inflate(context, R.layout.view_category, this) + B = ViewCategoryBinding.bind(view) + } + + @ModelProp + fun category(category: Category) { + B.txtName.text = category.title + B.imgBackground.load(category.imageUrl, DrawableTransitionOptions.withCrossFade()) { + } + } + + @CallbackProp + fun click(onClickListener: OnClickListener?) { + B.root.setOnClickListener(onClickListener) + } + + @OnViewRecycled + fun clear() { + B.imgBackground.clear() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/views/DownloadView.kt b/app/src/main/java/com/aurora/store/view/epoxy/views/DownloadView.kt new file mode 100644 index 000000000..8ed0476d9 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/views/DownloadView.kt @@ -0,0 +1,146 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.epoxy.views + +import android.content.Context +import android.util.AttributeSet +import android.widget.RelativeLayout +import com.airbnb.epoxy.CallbackProp +import com.airbnb.epoxy.ModelProp +import com.airbnb.epoxy.ModelView +import com.airbnb.epoxy.OnViewRecycled +import com.aurora.Constants +import com.aurora.gplayapi.data.models.App +import com.aurora.store.R +import com.aurora.store.data.model.DownloadFile +import com.aurora.store.databinding.ViewDownloadBinding +import com.aurora.store.util.CommonUtil.getDownloadSpeedString +import com.aurora.store.util.CommonUtil.getETAString +import com.aurora.store.util.CommonUtil.humanReadableByteValue +import com.aurora.store.util.extensions.clear +import com.aurora.store.util.extensions.load +import com.bumptech.glide.load.resource.bitmap.RoundedCorners +import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions +import com.google.gson.Gson +import com.google.gson.GsonBuilder +import com.tonyodev.fetch2.Status +import java.lang.reflect.Modifier +import java.util.* + +@ModelView( + autoLayout = ModelView.Size.WRAP_WIDTH_WRAP_HEIGHT, + baseModelClass = BaseView::class +) +class DownloadView : RelativeLayout { + + private lateinit var B: ViewDownloadBinding + + private val gson: Gson = GsonBuilder() + .excludeFieldsWithModifiers(Modifier.STATIC, Modifier.TRANSIENT) + .create() + + constructor(context: Context?) : super(context) { + init(context, null) + } + + constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) { + init(context, attrs) + } + + constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super( + context, + attrs, + defStyleAttr + ) { + init(context, attrs) + } + + private fun init(context: Context?, attrs: AttributeSet?) { + val view = inflate(context, R.layout.view_download, this) + B = ViewDownloadBinding.bind(view) + } + + @ModelProp + fun download(downloadFile: DownloadFile) { + val download = downloadFile.download + val extras = download.extras.getString(Constants.STRING_EXTRA, "{}") + val app = gson.fromJson(extras, App::class.java) + + app?.let { + B.imgDownload.load(app.iconArtwork.url, DrawableTransitionOptions.withCrossFade()) { + placeholder(R.drawable.bg_placeholder) + transform(RoundedCorners(32)) + } + B.txtTitle.text = app.displayName + } + + B.txtStatus.text = download.status.name + .toLowerCase(Locale.getDefault()) + .capitalize(Locale.getDefault()) + + B.txtSize.text = StringBuilder() + .append(humanReadableByteValue(download.downloaded, true)) + .append("/") + .append(humanReadableByteValue(download.total, true)) + + val file = download.file + B.txtFile.text = file.substring(file.lastIndexOf("/") + 1) + + var progress = download.progress + if (progress == -1) { + progress = 0 + } + + B.progressDownload.progress = progress + B.txtProgress.text = ("$progress%") + + B.txtEta.text = getETAString(context, download.etaInMilliSeconds) + B.txtSpeed.text = getDownloadSpeedString( + context, + download.downloadedBytesPerSecond + ) + + when (download.status) { + Status.DOWNLOADING, Status.QUEUED, Status.ADDED -> { + B.txtSpeed.visibility = VISIBLE + B.txtEta.visibility = VISIBLE + } + else -> { + B.txtSpeed.visibility = INVISIBLE + B.txtEta.visibility = INVISIBLE + } + } + } + + @CallbackProp + fun click(onClickListener: OnClickListener?) { + B.root.setOnClickListener(onClickListener) + } + + @CallbackProp + fun longClick(onClickListener: OnLongClickListener?) { + B.root.setOnLongClickListener(onClickListener) + } + + @OnViewRecycled + fun clear() { + B.imgDownload.clear() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/views/EditorHeadView.kt b/app/src/main/java/com/aurora/store/view/epoxy/views/EditorHeadView.kt new file mode 100644 index 000000000..a1d30ce61 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/views/EditorHeadView.kt @@ -0,0 +1,63 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.epoxy.views + +import android.content.Context +import android.util.AttributeSet +import android.widget.RelativeLayout +import com.airbnb.epoxy.ModelProp +import com.airbnb.epoxy.ModelView +import com.aurora.store.R +import com.aurora.store.databinding.ViewEditorHeadBinding + +@ModelView( + autoLayout = ModelView.Size.WRAP_WIDTH_WRAP_HEIGHT, + baseModelClass = BaseView::class +) +class EditorHeadView : RelativeLayout { + + private lateinit var B: ViewEditorHeadBinding + + constructor(context: Context?) : super(context) { + init(context, null) + } + + constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) { + init(context, attrs) + } + + constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super( + context, + attrs, + defStyleAttr + ) { + init(context, attrs) + } + + private fun init(context: Context?, attrs: AttributeSet?) { + val view = inflate(context, R.layout.view_editor_head, this) + B = ViewEditorHeadBinding.bind(view) + } + + @ModelProp + fun title(title: String) { + B.title.text = title + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/views/EditorImageView.kt b/app/src/main/java/com/aurora/store/view/epoxy/views/EditorImageView.kt new file mode 100644 index 000000000..a18738179 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/views/EditorImageView.kt @@ -0,0 +1,90 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.epoxy.views + +import android.content.Context +import android.util.AttributeSet +import android.widget.RelativeLayout +import com.airbnb.epoxy.ModelProp +import com.airbnb.epoxy.ModelView +import com.airbnb.epoxy.OnViewRecycled +import com.aurora.gplayapi.data.models.Artwork +import com.aurora.store.R +import com.aurora.store.databinding.ViewEditorImageBinding +import com.aurora.store.util.extensions.clear +import com.aurora.store.util.extensions.load +import com.bumptech.glide.load.resource.bitmap.RoundedCorners +import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions + +@ModelView( + autoLayout = ModelView.Size.WRAP_WIDTH_WRAP_HEIGHT, + baseModelClass = BaseView::class +) +class EditorImageView : RelativeLayout { + + private lateinit var B: ViewEditorImageBinding + + constructor(context: Context?) : super(context) { + init(context, null) + } + + constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) { + init(context, attrs) + } + + constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super( + context, + attrs, + defStyleAttr + ) { + init(context, attrs) + } + + private fun init(context: Context?, attrs: AttributeSet?) { + val view = inflate(context, R.layout.view_editor_image, this) + B = ViewEditorImageBinding.bind(view) + } + + @ModelProp + fun artwork(artwork: Artwork) { + when (artwork.type) { + 14 -> { + B.img.layoutParams.width = + resources.getDimension(R.dimen.icon_size_large).toInt() * 2 + B.img.requestLayout() + B.img.load(artwork.url, DrawableTransitionOptions.withCrossFade()) { + transform(RoundedCorners(16)) + } + } + else -> { + B.img.layoutParams.width = resources.getDimension(R.dimen.icon_size_large).toInt() + B.img.requestLayout() + B.img.load(artwork.url, DrawableTransitionOptions.withCrossFade()) { + transform(RoundedCorners(16)) + } + } + } + } + + @OnViewRecycled + fun clear() { + B.img.clear() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/views/HeaderView.kt b/app/src/main/java/com/aurora/store/view/epoxy/views/HeaderView.kt new file mode 100644 index 000000000..51a46e85a --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/views/HeaderView.kt @@ -0,0 +1,84 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.epoxy.views + +import android.content.Context +import android.util.AttributeSet +import android.widget.RelativeLayout +import androidx.annotation.Nullable +import com.airbnb.epoxy.CallbackProp +import com.airbnb.epoxy.ModelProp +import com.airbnb.epoxy.ModelView +import com.airbnb.epoxy.OnViewRecycled +import com.aurora.store.R +import com.aurora.store.databinding.ViewHeaderBinding + + +@ModelView( + autoLayout = ModelView.Size.WRAP_WIDTH_WRAP_HEIGHT, + baseModelClass = BaseView::class +) +class HeaderView : RelativeLayout { + + private lateinit var B: ViewHeaderBinding + + constructor(context: Context?) : super(context) { + init(context, null) + } + + constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) { + init(context, attrs) + } + + constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super( + context, + attrs, + defStyleAttr + ) { + init(context, attrs) + } + + private fun init(context: Context?, attrs: AttributeSet?) { + val view = inflate(context, R.layout.view_header, this) + B = ViewHeaderBinding.bind(view) + } + + @ModelProp + fun title(title: String) { + B.txtTitle.text = title + } + + @JvmOverloads + @ModelProp + fun browseUrl(@Nullable browseUrl: String = String()) { + if (browseUrl.isEmpty()) + B.imgAction.visibility = INVISIBLE + } + + @CallbackProp + fun click(onClickListener: OnClickListener?) { + B.root.setOnClickListener(onClickListener) + } + + @OnViewRecycled + fun clear() { + B.imgAction.visibility = VISIBLE + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/views/MinimalHeaderView.kt b/app/src/main/java/com/aurora/store/view/epoxy/views/MinimalHeaderView.kt new file mode 100644 index 000000000..cc359b46c --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/views/MinimalHeaderView.kt @@ -0,0 +1,76 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.epoxy.views + +import android.content.Context +import android.util.AttributeSet +import android.widget.RelativeLayout +import com.airbnb.epoxy.CallbackProp +import com.airbnb.epoxy.ModelProp +import com.airbnb.epoxy.ModelView +import com.airbnb.epoxy.OnViewRecycled +import com.aurora.store.R +import com.aurora.store.databinding.ViewActionHeaderBinding + + +@ModelView( + autoLayout = ModelView.Size.WRAP_WIDTH_WRAP_HEIGHT, + baseModelClass = BaseView::class +) +class MinimalHeaderView : RelativeLayout { + + private lateinit var B: ViewActionHeaderBinding + + constructor(context: Context?) : super(context) { + init(context, null) + } + + constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) { + init(context, attrs) + } + + constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super( + context, + attrs, + defStyleAttr + ) { + init(context, attrs) + } + + private fun init(context: Context?, attrs: AttributeSet?) { + val view = inflate(context, R.layout.view_action_header, this) + B = ViewActionHeaderBinding.bind(view) + } + + @ModelProp + fun title(title: String) { + B.txtTitle.text = title + } + + @CallbackProp + fun click(onClickListener: OnClickListener?) { + B.imgAction.setOnClickListener(onClickListener) + } + + @OnViewRecycled + fun clear() { + B.imgAction.visibility = VISIBLE + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/views/SearchSuggestionView.kt b/app/src/main/java/com/aurora/store/view/epoxy/views/SearchSuggestionView.kt new file mode 100644 index 000000000..723eb3a0e --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/views/SearchSuggestionView.kt @@ -0,0 +1,98 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.epoxy.views + +import android.content.Context +import android.util.AttributeSet +import android.widget.RelativeLayout +import androidx.core.content.ContextCompat +import com.airbnb.epoxy.CallbackProp +import com.airbnb.epoxy.ModelProp +import com.airbnb.epoxy.ModelView +import com.airbnb.epoxy.OnViewRecycled +import com.aurora.gplayapi.SearchSuggestEntry +import com.aurora.store.R +import com.aurora.store.databinding.ViewSearchSuggestionBinding +import com.aurora.store.util.extensions.clear +import com.aurora.store.util.extensions.load +import com.bumptech.glide.load.resource.bitmap.RoundedCorners + +@ModelView( + autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT, + baseModelClass = BaseView::class +) +class SearchSuggestionView : RelativeLayout { + + private lateinit var B: ViewSearchSuggestionBinding + + constructor(context: Context?) : super(context) { + init(context, null) + } + + constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) { + init(context, attrs) + } + + constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super( + context, + attrs, + defStyleAttr + ) { + init(context, attrs) + } + + private fun init(context: Context?, attrs: AttributeSet?) { + val view = inflate(context, R.layout.view_search_suggestion, this) + B = ViewSearchSuggestionBinding.bind(view) + } + + @ModelProp + fun entry(searchSuggestEntry: SearchSuggestEntry) { + if (searchSuggestEntry.hasImageContainer()) { + B.img.load(searchSuggestEntry.imageContainer.imageUrl) { + transform(RoundedCorners(8)) + } + } else { + B.img.setImageDrawable( + ContextCompat.getDrawable( + context, + R.drawable.ic_search_suggestion + ) + ) + } + + B.txtTitle.text = searchSuggestEntry.title + } + + @CallbackProp + fun click(onClickListener: OnClickListener?) { + B.root.setOnClickListener(onClickListener) + } + + @CallbackProp + fun action(onClickListener: OnClickListener?) { + B.action.setOnClickListener(onClickListener) + } + + @OnViewRecycled + fun clear() { + B.img.clear() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/views/ThemeView.kt b/app/src/main/java/com/aurora/store/view/epoxy/views/ThemeView.kt new file mode 100644 index 000000000..e1aa2f4ee --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/views/ThemeView.kt @@ -0,0 +1,77 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.epoxy.views + +import android.content.Context +import android.util.AttributeSet +import android.widget.CompoundButton +import android.widget.RelativeLayout +import com.airbnb.epoxy.CallbackProp +import com.airbnb.epoxy.ModelProp +import com.airbnb.epoxy.ModelView +import com.aurora.store.R +import com.aurora.store.data.model.Theme +import com.aurora.store.databinding.ViewThemeBinding + +@ModelView( + autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT, + baseModelClass = BaseView::class +) +class ThemeView : RelativeLayout { + + private lateinit var B: ViewThemeBinding + + constructor(context: Context?) : super(context) { + init(context, null) + } + + constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) { + init(context, attrs) + } + + constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super( + context, + attrs, + defStyleAttr + ) { + init(context, attrs) + } + + private fun init(context: Context?, attrs: AttributeSet?) { + val view = inflate(context, R.layout.view_theme, this) + B = ViewThemeBinding.bind(view) + } + + @ModelProp + fun theme(theme: Theme) { + B.line1.text = theme.title + B.line2.text = theme.subtitle + } + + @ModelProp + fun markChecked(isChecked: Boolean) { + B.checkbox.isChecked = isChecked + } + + @CallbackProp + fun checked(onCheckedChangeListener: CompoundButton.OnCheckedChangeListener?) { + B.checkbox.setOnCheckedChangeListener(onCheckedChangeListener) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/views/UpdateHeaderView.kt b/app/src/main/java/com/aurora/store/view/epoxy/views/UpdateHeaderView.kt new file mode 100644 index 000000000..4beaaf6f2 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/views/UpdateHeaderView.kt @@ -0,0 +1,81 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.epoxy.views + +import android.content.Context +import android.util.AttributeSet +import android.widget.RelativeLayout +import com.airbnb.epoxy.CallbackProp +import com.airbnb.epoxy.ModelProp +import com.airbnb.epoxy.ModelView +import com.airbnb.epoxy.OnViewRecycled +import com.aurora.store.R +import com.aurora.store.databinding.ViewHeaderUpdateBinding + + +@ModelView( + autoLayout = ModelView.Size.WRAP_WIDTH_WRAP_HEIGHT, + baseModelClass = BaseView::class +) +class UpdateHeaderView : RelativeLayout { + + private lateinit var B: ViewHeaderUpdateBinding + + constructor(context: Context?) : super(context) { + init(context, null) + } + + constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) { + init(context, attrs) + } + + constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super( + context, + attrs, + defStyleAttr + ) { + init(context, attrs) + } + + private fun init(context: Context?, attrs: AttributeSet?) { + val view = inflate(context, R.layout.view_header_update, this) + B = ViewHeaderUpdateBinding.bind(view) + } + + @ModelProp + fun title(title: String) { + B.txtTitle.text = title + } + + @ModelProp + fun action(action: String) { + B.btnAction.text = action + } + + @CallbackProp + fun click(onClickListener: OnClickListener?) { + B.btnAction.setOnClickListener(onClickListener) + } + + @OnViewRecycled + fun clear() { + B.btnAction.isEnabled = true + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/views/app/AppListView.kt b/app/src/main/java/com/aurora/store/view/epoxy/views/app/AppListView.kt new file mode 100644 index 000000000..8f87fefc9 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/views/app/AppListView.kt @@ -0,0 +1,110 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.epoxy.views + +import android.content.Context +import android.util.AttributeSet +import android.widget.RelativeLayout +import com.airbnb.epoxy.CallbackProp +import com.airbnb.epoxy.ModelProp +import com.airbnb.epoxy.ModelView +import com.airbnb.epoxy.OnViewRecycled +import com.aurora.gplayapi.data.models.App +import com.aurora.store.R +import com.aurora.store.databinding.ViewAppListBinding +import com.aurora.store.util.CommonUtil +import com.aurora.store.util.extensions.clear +import com.aurora.store.util.extensions.getString +import com.aurora.store.util.extensions.load +import com.bumptech.glide.load.resource.bitmap.RoundedCorners + +@ModelView( + autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT, + baseModelClass = BaseView::class +) +class AppListView : RelativeLayout { + + private lateinit var B: ViewAppListBinding + + constructor(context: Context?) : super(context) { + init(context, null) + } + + constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) { + init(context, attrs) + } + + constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super( + context, + attrs, + defStyleAttr + ) { + init(context, attrs) + } + + private fun init(context: Context?, attrs: AttributeSet?) { + val view = inflate(context, R.layout.view_app_list, this) + B = ViewAppListBinding.bind(view) + } + + @ModelProp + fun app(app: App) { + B.imgIcon.load(app.iconArtwork.url) { + placeholder(R.drawable.bg_placeholder) + transform(RoundedCorners(25)) + } + + B.txtLine1.text = app.displayName + B.txtLine2.text = app.developerName + + val extras: MutableList = mutableListOf() + extras.add(CommonUtil.addSiPrefix(app.size)) + extras.add("${app.labeledRating}★") + extras.add( + if (app.isFree) + getString(R.string.details_free) + else + getString(R.string.details_paid) + ) + + if (app.containsAds) + extras.add(getString(R.string.details_contains_ads)) + + if (app.dependencies.dependentPackages.isNotEmpty()) + extras.add(getString(R.string.details_gsf_dependent)) + + B.txtLine3.text = extras.joinToString(separator = " • ") + } + + @CallbackProp + fun click(onClickListener: OnClickListener?) { + B.root.setOnClickListener(onClickListener) + } + + @CallbackProp + fun longClick(onClickListener: OnLongClickListener?) { + B.root.setOnLongClickListener(onClickListener) + } + + @OnViewRecycled + fun clear() { + B.imgIcon.clear() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/views/app/AppUpdateView.kt b/app/src/main/java/com/aurora/store/view/epoxy/views/app/AppUpdateView.kt new file mode 100644 index 000000000..17d411f54 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/views/app/AppUpdateView.kt @@ -0,0 +1,124 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.epoxy.views + +import android.content.Context +import android.util.AttributeSet +import android.widget.CompoundButton +import android.widget.RelativeLayout +import androidx.core.text.HtmlCompat +import com.airbnb.epoxy.CallbackProp +import com.airbnb.epoxy.ModelProp +import com.airbnb.epoxy.ModelView +import com.airbnb.epoxy.OnViewRecycled +import com.aurora.gplayapi.data.models.App +import com.aurora.store.R +import com.aurora.store.databinding.ViewAppUpdateBinding +import com.aurora.store.util.CommonUtil +import com.aurora.store.util.extensions.clear +import com.aurora.store.util.extensions.load +import com.bumptech.glide.load.resource.bitmap.RoundedCorners +import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions.withCrossFade + +@ModelView( + autoLayout = ModelView.Size.WRAP_WIDTH_WRAP_HEIGHT, + baseModelClass = BaseView::class +) +class AppUpdateView : RelativeLayout { + + private lateinit var B: ViewAppUpdateBinding + private var expanded: Boolean = false + + constructor(context: Context?) : super(context) { + init(context, null) + } + + constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) { + init(context, attrs) + } + + constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super( + context, + attrs, + defStyleAttr + ) { + init(context, attrs) + } + + private fun init(context: Context?, attrs: AttributeSet?) { + val view = inflate(context, R.layout.view_app_update, this) + B = ViewAppUpdateBinding.bind(view) + } + + @ModelProp + fun app(app: App) { + B.txtLine1.text = app.displayName + B.imgIcon.load(app.iconArtwork.url, withCrossFade()) { + placeholder(R.drawable.bg_placeholder) + transform(RoundedCorners(25)) + } + + B.txtLine2.text = app.developerName + B.txtLine3.text = CommonUtil.addSiPrefix(app.size) + B.txtChangelog.text = if (app.changes.isNotEmpty()) + HtmlCompat.fromHtml( + app.changes, + HtmlCompat.FROM_HTML_OPTION_USE_CSS_COLORS + ) + else + context.getString(R.string.details_changelog_unavailable) + + B.headerIndicator.setOnClickListener { + B.expansionLayout.let { + if (it.isExpanded) { + it.collapse(true) + } else { + it.expand(true) + } + } + } + } + + @ModelProp + fun markChecked(isChecked: Boolean) { + B.checkbox.isChecked = isChecked + } + + @CallbackProp + fun checked(onCheckedChangeListener: CompoundButton.OnCheckedChangeListener?) { + B.checkbox.setOnCheckedChangeListener(onCheckedChangeListener) + } + + @CallbackProp + fun click(onClickListener: OnClickListener?) { + B.layoutContent.setOnClickListener(onClickListener) + } + + @CallbackProp + fun longClick(onClickListener: OnLongClickListener?) { + B.layoutContent.setOnLongClickListener(onClickListener) + } + + @OnViewRecycled + fun clear() { + B.imgIcon.clear() + B.headerIndicator.removeCallbacks { } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/views/app/AppView.kt b/app/src/main/java/com/aurora/store/view/epoxy/views/app/AppView.kt new file mode 100644 index 000000000..45a49d1e2 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/views/app/AppView.kt @@ -0,0 +1,93 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.epoxy.views + +import android.content.Context +import android.util.AttributeSet +import android.widget.RelativeLayout +import com.airbnb.epoxy.CallbackProp +import com.airbnb.epoxy.ModelProp +import com.airbnb.epoxy.ModelView +import com.airbnb.epoxy.OnViewRecycled +import com.aurora.gplayapi.data.models.App +import com.aurora.store.R +import com.aurora.store.databinding.ViewAppBinding +import com.aurora.store.util.CommonUtil +import com.aurora.store.util.extensions.clear +import com.aurora.store.util.extensions.load +import com.bumptech.glide.load.resource.bitmap.RoundedCorners + + +@ModelView( + autoLayout = ModelView.Size.WRAP_WIDTH_WRAP_HEIGHT, + baseModelClass = BaseView::class +) +class AppView : RelativeLayout { + + private lateinit var B: ViewAppBinding + + constructor(context: Context?) : super(context) { + init(context, null) + } + + constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) { + init(context, attrs) + } + + constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super( + context, + attrs, + defStyleAttr + ) { + init(context, attrs) + } + + private fun init(context: Context?, attrs: AttributeSet?) { + val view = inflate(context, R.layout.view_app, this) + B = ViewAppBinding.bind(view) + } + + @ModelProp + fun app(app: App) { + B.txtName.text = app.displayName + B.imgIcon.load(app.iconArtwork.url) { + placeholder(R.drawable.bg_placeholder) + transform(RoundedCorners(32)) + } + + if (app.size > 0) + B.txtSize.text = CommonUtil.addSiPrefix(app.size) + } + + @CallbackProp + fun click(onClickListener: OnClickListener?) { + B.root.setOnClickListener(onClickListener) + } + + @CallbackProp + fun longClick(onClickListener: OnLongClickListener?) { + B.root.setOnLongClickListener(onClickListener) + } + + @OnViewRecycled + fun clear() { + B.imgIcon.clear() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/views/app/NoAppAltView.kt b/app/src/main/java/com/aurora/store/view/epoxy/views/app/NoAppAltView.kt new file mode 100644 index 000000000..1c7568e7e --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/views/app/NoAppAltView.kt @@ -0,0 +1,65 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.epoxy.views.app + +import android.content.Context +import android.util.AttributeSet +import android.widget.RelativeLayout +import com.airbnb.epoxy.ModelProp +import com.airbnb.epoxy.ModelView +import com.aurora.store.R +import com.aurora.store.databinding.ViewNoAppAltBinding +import com.aurora.store.view.epoxy.views.BaseView + + +@ModelView( + autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT, + baseModelClass = BaseView::class +) +class NoAppAltView : RelativeLayout { + + private lateinit var B: ViewNoAppAltBinding + + constructor(context: Context?) : super(context) { + init(context, null) + } + + constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) { + init(context, attrs) + } + + constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super( + context, + attrs, + defStyleAttr + ) { + init(context, attrs) + } + + private fun init(context: Context?, attrs: AttributeSet?) { + val view = inflate(context, R.layout.view_no_app_alt, this) + B = ViewNoAppAltBinding.bind(view) + } + + @ModelProp + fun message(message: String) { + B.txtMsg.text = message + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/views/app/NoAppView.kt b/app/src/main/java/com/aurora/store/view/epoxy/views/app/NoAppView.kt new file mode 100644 index 000000000..c03f262e6 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/views/app/NoAppView.kt @@ -0,0 +1,73 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.epoxy.views.app + +import android.content.Context +import android.util.AttributeSet +import android.widget.RelativeLayout +import androidx.core.content.ContextCompat +import com.airbnb.epoxy.ModelProp +import com.airbnb.epoxy.ModelView +import com.aurora.store.R +import com.aurora.store.databinding.ViewNoAppBinding +import com.aurora.store.view.epoxy.views.BaseView + + +@ModelView( + autoLayout = ModelView.Size.MATCH_WIDTH_MATCH_HEIGHT, + baseModelClass = BaseView::class +) +class NoAppView : RelativeLayout { + + private lateinit var B: ViewNoAppBinding + + constructor(context: Context?) : super(context) { + init(context, null) + } + + constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) { + init(context, attrs) + } + + constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super( + context, + attrs, + defStyleAttr + ) { + init(context, attrs) + } + + private fun init(context: Context?, attrs: AttributeSet?) { + val view = inflate(context, R.layout.view_no_app, this) + B = ViewNoAppBinding.bind(view) + } + + @ModelProp + fun message(message: String) { + B.txt.text = message + } + + @ModelProp + fun icon(icon: Int?) { + icon?.let { + B.img.setImageDrawable(ContextCompat.getDrawable(context, icon)) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/views/details/AppDependentView.kt b/app/src/main/java/com/aurora/store/view/epoxy/views/details/AppDependentView.kt new file mode 100644 index 000000000..49306da25 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/views/details/AppDependentView.kt @@ -0,0 +1,89 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.epoxy.views.details + +import android.content.Context +import android.util.AttributeSet +import android.widget.RelativeLayout +import com.airbnb.epoxy.CallbackProp +import com.airbnb.epoxy.ModelProp +import com.airbnb.epoxy.ModelView +import com.airbnb.epoxy.OnViewRecycled +import com.aurora.gplayapi.data.models.App +import com.aurora.store.R +import com.aurora.store.databinding.ViewAppDependentBinding +import com.aurora.store.util.extensions.clear +import com.aurora.store.util.extensions.load +import com.aurora.store.view.epoxy.views.BaseView +import com.bumptech.glide.load.resource.bitmap.RoundedCorners + +@ModelView( + autoLayout = ModelView.Size.WRAP_WIDTH_WRAP_HEIGHT, + baseModelClass = BaseView::class +) +class AppDependentView : RelativeLayout { + + private lateinit var B: ViewAppDependentBinding + + constructor(context: Context?) : super(context) { + init(context, null) + } + + constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) { + init(context, attrs) + } + + constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super( + context, + attrs, + defStyleAttr + ) { + init(context, attrs) + } + + private fun init(context: Context?, attrs: AttributeSet?) { + val view = inflate(context, R.layout.view_app_dependent, this) + B = ViewAppDependentBinding.bind(view) + } + + @ModelProp + fun app(app: App) { + B.txtName.text = app.displayName + B.imgIcon.load(app.iconArtwork.url) { + placeholder(R.drawable.bg_placeholder) + transform(RoundedCorners(32)) + } + } + + @CallbackProp + fun click(onClickListener: OnClickListener?) { + B.root.setOnClickListener(onClickListener) + } + + @CallbackProp + fun longClick(onClickListener: OnLongClickListener?) { + B.root.setOnLongClickListener(onClickListener) + } + + @OnViewRecycled + fun clear() { + B.imgIcon.clear() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/views/details/BadgeView.kt b/app/src/main/java/com/aurora/store/view/epoxy/views/details/BadgeView.kt new file mode 100644 index 000000000..f6cb92f4b --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/views/details/BadgeView.kt @@ -0,0 +1,91 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.epoxy.views + +import android.content.Context +import android.util.AttributeSet +import android.widget.RelativeLayout +import com.airbnb.epoxy.CallbackProp +import com.airbnb.epoxy.ModelProp +import com.airbnb.epoxy.ModelView +import com.airbnb.epoxy.OnViewRecycled +import com.aurora.gplayapi.data.models.details.Badge +import com.aurora.store.R +import com.aurora.store.databinding.ViewBadgeBinding +import com.aurora.store.util.extensions.load + + +@ModelView( + autoLayout = ModelView.Size.WRAP_WIDTH_WRAP_HEIGHT, + baseModelClass = BaseView::class +) +class BadgeView : RelativeLayout { + + private lateinit var B: ViewBadgeBinding + + constructor(context: Context?) : super(context) { + init(context, null) + } + + constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) { + init(context, attrs) + } + + constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super( + context, + attrs, + defStyleAttr + ) { + init(context, attrs) + } + + private fun init(context: Context?, attrs: AttributeSet?) { + val view = inflate(context, R.layout.view_badge, this) + B = ViewBadgeBinding.bind(view) + } + + @ModelProp + fun badge(badge: Badge) { + if (badge.textMajor.isEmpty()) { + if (badge.textMinor.isEmpty()) { + B.txt.text = badge.textDescription + } else { + B.txt.text = badge.textMinor + } + } else { + B.txt.text = badge.textMajor + } + + badge.artwork?.let { + B.img.load(it.url) { + + } + } + } + + @CallbackProp + fun click(onClickListener: OnClickListener?) { + + } + + @OnViewRecycled + fun clear() { + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/views/details/ExodusView.kt b/app/src/main/java/com/aurora/store/view/epoxy/views/details/ExodusView.kt new file mode 100644 index 000000000..d876675eb --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/views/details/ExodusView.kt @@ -0,0 +1,78 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.epoxy.views + +import android.content.Context +import android.util.AttributeSet +import android.widget.RelativeLayout +import com.airbnb.epoxy.CallbackProp +import com.airbnb.epoxy.ModelProp +import com.airbnb.epoxy.ModelView +import com.airbnb.epoxy.OnViewRecycled +import com.aurora.store.R +import com.aurora.store.data.model.ExodusTracker +import com.aurora.store.databinding.ViewExodusBinding + + +@ModelView( + autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT, + baseModelClass = BaseView::class +) +class ExodusView : RelativeLayout { + + private lateinit var B: ViewExodusBinding + + constructor(context: Context?) : super(context) { + init(context, null) + } + + constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) { + init(context, attrs) + } + + constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super( + context, + attrs, + defStyleAttr + ) { + init(context, attrs) + } + + private fun init(context: Context?, attrs: AttributeSet?) { + val view = inflate(context, R.layout.view_exodus, this) + B = ViewExodusBinding.bind(view) + } + + @ModelProp + fun tracker(report: ExodusTracker) { + B.txtTitle.text = report.name + B.txtSubtitle.text = report.signature + B.txtDescription.text = report.date + } + + @CallbackProp + fun click(onClickListener: OnClickListener?) { + B.root.setOnClickListener(onClickListener) + } + + @OnViewRecycled + fun clear() { + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/views/details/FileView.kt b/app/src/main/java/com/aurora/store/view/epoxy/views/details/FileView.kt new file mode 100644 index 000000000..2af3c2edf --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/views/details/FileView.kt @@ -0,0 +1,66 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.epoxy.views + +import android.content.Context +import android.util.AttributeSet +import android.widget.RelativeLayout +import com.airbnb.epoxy.ModelProp +import com.airbnb.epoxy.ModelView +import com.aurora.gplayapi.data.models.File +import com.aurora.store.R +import com.aurora.store.databinding.ViewFileBinding +import com.aurora.store.util.CommonUtil + +@ModelView( + autoLayout = ModelView.Size.WRAP_WIDTH_WRAP_HEIGHT, + baseModelClass = BaseView::class +) +class FileView : RelativeLayout { + + private lateinit var B: ViewFileBinding + + constructor(context: Context?) : super(context) { + init(context, null) + } + + constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) { + init(context, attrs) + } + + constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super( + context, + attrs, + defStyleAttr + ) { + init(context, attrs) + } + + private fun init(context: Context?, attrs: AttributeSet?) { + val view = inflate(context, R.layout.view_file, this) + B = ViewFileBinding.bind(view) + } + + @ModelProp + fun file(file: File) { + B.line1.text = file.name + B.line2.text = CommonUtil.addSiPrefix(file.size) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/views/details/LargeScreenshotView.kt b/app/src/main/java/com/aurora/store/view/epoxy/views/details/LargeScreenshotView.kt new file mode 100644 index 000000000..096404ea3 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/views/details/LargeScreenshotView.kt @@ -0,0 +1,118 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.epoxy.views + +import android.content.Context +import android.content.res.Resources +import android.graphics.drawable.Drawable +import android.util.AttributeSet +import android.widget.RelativeLayout +import com.airbnb.epoxy.ModelProp +import com.airbnb.epoxy.ModelView +import com.airbnb.epoxy.OnViewRecycled +import com.aurora.gplayapi.data.models.Artwork +import com.aurora.store.GlideApp +import com.aurora.store.R +import com.aurora.store.databinding.ViewScreenshotLargeBinding +import com.aurora.store.util.extensions.clear +import com.aurora.store.util.extensions.px +import com.aurora.store.util.extensions.runOnUiThread +import com.bumptech.glide.load.DataSource +import com.bumptech.glide.load.engine.DiskCacheStrategy +import com.bumptech.glide.load.engine.GlideException +import com.bumptech.glide.request.RequestListener +import com.bumptech.glide.request.target.Target + + +@ModelView( + autoLayout = ModelView.Size.MATCH_WIDTH_MATCH_HEIGHT, + baseModelClass = BaseView::class +) +class LargeScreenshotView : RelativeLayout { + + private lateinit var B: ViewScreenshotLargeBinding + + constructor(context: Context?) : super(context) { + init(context, null) + } + + constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) { + init(context, attrs) + } + + constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super( + context, + attrs, + defStyleAttr + ) { + init(context, attrs) + } + + private fun init(context: Context?, attrs: AttributeSet?) { + val view = inflate(context, R.layout.view_screenshot_large, this) + B = ViewScreenshotLargeBinding.bind(view) + } + + @ModelProp + fun artwork(artwork: Artwork) { + GlideApp.with(context) + .load(artwork.url) + .diskCacheStrategy(DiskCacheStrategy.AUTOMATIC) + .addListener(object : RequestListener { + override fun onLoadFailed( + e: GlideException?, + model: Any, + target: Target, + isFirstResource: Boolean + ): Boolean { + return false + } + + override fun onResourceReady( + drawable: Drawable, + model: Any, + target: Target, + dataSource: DataSource, + isFirstResource: Boolean + ): Boolean { + runOnUiThread { + if (artwork.height != 0 && artwork.width != 0) { + B.img.layoutParams.height = artwork.height.px.toInt() + B.img.layoutParams.width = artwork.width.px.toInt() + } else { + val displayMetrics = Resources.getSystem().displayMetrics + val height = displayMetrics.heightPixels + val width = displayMetrics.widthPixels + B.img.layoutParams.width = width + B.img.layoutParams.height = height + } + B.img.setImageDrawable(drawable) + B.img.requestLayout() + } + return false + } + }).submit() + } + + @OnViewRecycled + fun clear() { + B.img.clear() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/views/details/MoreBadgeView.kt b/app/src/main/java/com/aurora/store/view/epoxy/views/details/MoreBadgeView.kt new file mode 100644 index 000000000..aaae0a9d5 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/views/details/MoreBadgeView.kt @@ -0,0 +1,99 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.epoxy.views.details + +import android.content.Context +import android.util.AttributeSet +import android.widget.RelativeLayout +import androidx.core.text.HtmlCompat +import com.airbnb.epoxy.CallbackProp +import com.airbnb.epoxy.ModelProp +import com.airbnb.epoxy.ModelView +import com.airbnb.epoxy.OnViewRecycled +import com.aurora.gplayapi.data.models.details.Badge +import com.aurora.store.R +import com.aurora.store.databinding.ViewMoreBadgeBinding +import com.aurora.store.util.extensions.load +import com.aurora.store.view.epoxy.views.BaseView + + +@ModelView( + autoLayout = ModelView.Size.WRAP_WIDTH_WRAP_HEIGHT, + baseModelClass = BaseView::class +) +class MoreBadgeView : RelativeLayout { + + private lateinit var B: ViewMoreBadgeBinding + + constructor(context: Context?) : super(context) { + init(context, null) + } + + constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) { + init(context, attrs) + } + + constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super( + context, + attrs, + defStyleAttr + ) { + init(context, attrs) + } + + private fun init(context: Context?, attrs: AttributeSet?) { + val view = inflate(context, R.layout.view_more_badge, this) + B = ViewMoreBadgeBinding.bind(view) + } + + @ModelProp + fun badge(badge: Badge) { + B.line1.text = badge.textMajor + + badge.textMinorHtml?.let { + if (it.isNotEmpty()) { + B.line2.text = HtmlCompat.fromHtml(it, HtmlCompat.FROM_HTML_MODE_COMPACT) + } else { + B.line2.text = badge.textMinor + } + } + + badge.textDescription?.let { + if (it.isNotEmpty()) { + B.line2.text = it + } + } + + badge.artwork?.let { + B.img.load(it.url) { + placeholder(R.drawable.ic_arrow_right) + } + } + } + + @CallbackProp + fun click(onClickListener: OnClickListener?) { + + } + + @OnViewRecycled + fun clear() { + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/views/details/ReviewView.kt b/app/src/main/java/com/aurora/store/view/epoxy/views/details/ReviewView.kt new file mode 100644 index 000000000..5dd2cdeb0 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/views/details/ReviewView.kt @@ -0,0 +1,98 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.epoxy.views + +import android.content.Context +import android.util.AttributeSet +import android.widget.RelativeLayout +import com.airbnb.epoxy.CallbackProp +import com.airbnb.epoxy.ModelProp +import com.airbnb.epoxy.ModelView +import com.airbnb.epoxy.OnViewRecycled +import com.aurora.gplayapi.data.models.Review +import com.aurora.store.R +import com.aurora.store.databinding.ViewReviewBinding +import com.aurora.store.util.extensions.clear +import com.aurora.store.util.extensions.load +import com.aurora.store.util.extensions.toDate +import com.bumptech.glide.load.resource.bitmap.RoundedCorners +import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions + +@ModelView( + autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT, + baseModelClass = BaseView::class +) +class ReviewView : RelativeLayout { + + private lateinit var B: ViewReviewBinding + + constructor(context: Context?) : super(context) { + init(context, null) + } + + constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) { + init(context, attrs) + } + + constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super( + context, + attrs, + defStyleAttr + ) { + init(context, attrs) + } + + private fun init(context: Context?, attrs: AttributeSet?) { + val view = inflate(context, R.layout.view_review, this) + B = ViewReviewBinding.bind(view) + } + + @ModelProp + fun review(review: Review) { + B.txtAuthor.text = review.userName + B.txtTime.text = review.timeStamp.toDate() + B.txtComment.text = review.comment + + B.img.load(review.userPhotoUrl, DrawableTransitionOptions.withCrossFade()) { + placeholder(R.drawable.bg_placeholder) + transform(RoundedCorners(32)) + } + + B.rating.rating = review.rating.toFloat() + /*if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + B.line2.justificationMode = Layout.JUSTIFICATION_MODE_INTER_WORD + }*/ + } + + @CallbackProp + fun click(onClickListener: OnClickListener?) { + B.root.setOnClickListener(onClickListener) + } + + @CallbackProp + fun longClick(onClickListener: OnLongClickListener?) { + B.root.setOnLongClickListener(onClickListener) + } + + @OnViewRecycled + fun clear() { + B.img.clear() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/views/details/ScreenshotView.kt b/app/src/main/java/com/aurora/store/view/epoxy/views/details/ScreenshotView.kt new file mode 100644 index 000000000..9ea8fa281 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/views/details/ScreenshotView.kt @@ -0,0 +1,126 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.epoxy.views + +import android.content.Context +import android.util.AttributeSet +import android.widget.RelativeLayout +import com.airbnb.epoxy.CallbackProp +import com.airbnb.epoxy.ModelProp +import com.airbnb.epoxy.ModelView +import com.airbnb.epoxy.OnViewRecycled +import com.aurora.gplayapi.data.models.Artwork +import com.aurora.store.R +import com.aurora.store.databinding.ViewScreenshotBinding +import com.aurora.store.util.extensions.clear +import com.aurora.store.util.extensions.load +import com.aurora.store.util.extensions.px +import com.bumptech.glide.load.resource.bitmap.RoundedCorners +import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions + + +@ModelView( + autoLayout = ModelView.Size.WRAP_WIDTH_WRAP_HEIGHT, + baseModelClass = BaseView::class +) +class ScreenshotView : RelativeLayout { + + private lateinit var B: ViewScreenshotBinding + + private var position: Int = 0 + + interface ScreenshotCallback { + fun onClick(position: Int = 0) + } + + constructor(context: Context?) : super(context) { + init(context, null) + } + + constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) { + init(context, attrs) + } + + constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super( + context, + attrs, + defStyleAttr + ) { + init(context, attrs) + } + + private fun init(context: Context?, attrs: AttributeSet?) { + val view = inflate(context, R.layout.view_screenshot, this) + B = ViewScreenshotBinding.bind(view) + } + + @ModelProp + fun position(pos: Int) { + position = pos + } + + @ModelProp + fun artwork(artwork: Artwork) { + normalizeSize(artwork) + B.img.load(artwork.url, DrawableTransitionOptions.withCrossFade()) { + placeholder(R.drawable.bg_rounded) + transform(RoundedCorners(8.px.toInt())) + } + } + + private fun normalizeSize(artwork: Artwork) { + if (artwork.height != 0 && artwork.width != 0) { + + val viewHeight = artwork.height + val viewWidth = artwork.width + + var normalizedHeight: Int = viewHeight + var normalizedWidth: Int = viewWidth + + + if (viewHeight == viewWidth) { + normalizedHeight = 240 + normalizedWidth = 240 + } else if (viewHeight > viewWidth) { + normalizedHeight = 240 + normalizedWidth = 135 + } else if (viewHeight < viewWidth) { + normalizedHeight = 240 + normalizedWidth = 427 + } + + B.img.layoutParams.height = normalizedHeight.px.toInt() + B.img.layoutParams.width = normalizedWidth.px.toInt() + B.img.requestLayout() + } + } + + @CallbackProp + fun callback(screenshotCallback: ScreenshotCallback?) { + B.img.setOnClickListener { + screenshotCallback?.onClick(position) + } + } + + @OnViewRecycled + fun clear() { + B.img.clear() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/views/preference/DashView.kt b/app/src/main/java/com/aurora/store/view/epoxy/views/preference/DashView.kt new file mode 100644 index 000000000..f5421d99b --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/views/preference/DashView.kt @@ -0,0 +1,84 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.epoxy.views.preference + +import android.content.Context +import android.util.AttributeSet +import android.widget.RelativeLayout +import androidx.core.content.ContextCompat +import com.airbnb.epoxy.CallbackProp +import com.airbnb.epoxy.ModelProp +import com.airbnb.epoxy.ModelView +import com.aurora.store.R +import com.aurora.store.data.model.Dash +import com.aurora.store.databinding.ViewDashBinding +import com.aurora.store.view.epoxy.views.BaseView +import com.aurora.store.view.ui.onboarding.WelcomeFragment + +@ModelView( + autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT, + baseModelClass = BaseView::class +) +class DashView : RelativeLayout { + + private lateinit var B: ViewDashBinding + + constructor(context: Context?) : super(context) { + init(context, null) + } + + constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) { + init(context, attrs) + } + + constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super( + context, + attrs, + defStyleAttr + ) { + init(context, attrs) + } + + private fun init(context: Context?, attrs: AttributeSet?) { + val view = inflate(context, R.layout.view_dash, this) + B = ViewDashBinding.bind(view) + } + + @ModelProp + fun dash(dash: Dash) { + B.line1.text = dash.title + B.line2.text = dash.subtitle + + var icon = WelcomeFragment.icMap[dash.icon] + if (icon == null) + icon = R.drawable.ic_arrow_right + B.img.setImageDrawable( + ContextCompat.getDrawable( + context, + icon + ) + ) + } + + @CallbackProp + fun click(onClickListener: OnClickListener?) { + B.root.setOnClickListener(onClickListener) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/views/preference/DeviceView.kt b/app/src/main/java/com/aurora/store/view/epoxy/views/preference/DeviceView.kt new file mode 100644 index 000000000..cbbf9b6c7 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/views/preference/DeviceView.kt @@ -0,0 +1,84 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.epoxy.views.preference + +import android.content.Context +import android.util.AttributeSet +import android.widget.CompoundButton +import android.widget.RelativeLayout +import com.airbnb.epoxy.CallbackProp +import com.airbnb.epoxy.ModelProp +import com.airbnb.epoxy.ModelView +import com.aurora.store.R +import com.aurora.store.databinding.ViewDeviceBinding +import com.aurora.store.view.epoxy.views.BaseView +import org.apache.commons.lang3.StringUtils +import java.util.* + +@ModelView( + autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT, + baseModelClass = BaseView::class +) +class DeviceView : RelativeLayout { + + private lateinit var B: ViewDeviceBinding + + constructor(context: Context?) : super(context) { + init(context, null) + } + + constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) { + init(context, attrs) + } + + constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super( + context, + attrs, + defStyleAttr + ) { + init(context, attrs) + } + + private fun init(context: Context?, attrs: AttributeSet?) { + val view = inflate(context, R.layout.view_device, this) + B = ViewDeviceBinding.bind(view) + } + + @ModelProp + fun properties(properties: Properties) { + B.line1.text = properties.getProperty("UserReadableName") + B.line2.text = StringUtils.joinWith( + " \u2022 ", + properties.getProperty("Build.MANUFACTURER"), + "API " + properties.getProperty("Build.VERSION.SDK_INT") + ) + B.line3.text = properties.getProperty("Platforms") + } + + @ModelProp + fun markChecked(isChecked: Boolean) { + B.checkbox.isChecked = isChecked + } + + @CallbackProp + fun checked(onCheckedChangeListener: CompoundButton.OnCheckedChangeListener?) { + B.checkbox.setOnCheckedChangeListener(onCheckedChangeListener) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/views/preference/InstallerView.kt b/app/src/main/java/com/aurora/store/view/epoxy/views/preference/InstallerView.kt new file mode 100644 index 000000000..e117e8faf --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/views/preference/InstallerView.kt @@ -0,0 +1,79 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.epoxy.views.preference + +import android.content.Context +import android.util.AttributeSet +import android.widget.CompoundButton +import android.widget.RelativeLayout +import com.airbnb.epoxy.CallbackProp +import com.airbnb.epoxy.ModelProp +import com.airbnb.epoxy.ModelView +import com.aurora.store.R +import com.aurora.store.data.model.Installer +import com.aurora.store.databinding.ViewInstallerBinding +import com.aurora.store.view.epoxy.views.BaseView + +@ModelView( + autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT, + baseModelClass = BaseView::class +) +class InstallerView : RelativeLayout { + + private lateinit var B: ViewInstallerBinding + + constructor(context: Context?) : super(context) { + init(context, null) + } + + constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) { + init(context, attrs) + } + + constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super( + context, + attrs, + defStyleAttr + ) { + init(context, attrs) + } + + private fun init(context: Context?, attrs: AttributeSet?) { + val view = inflate(context, R.layout.view_installer, this) + B = ViewInstallerBinding.bind(view) + } + + @ModelProp + fun installer(installer: Installer) { + B.line1.text = installer.title + B.line2.text = installer.subtitle + B.line3.text = installer.description + } + + @ModelProp + fun markChecked(isChecked: Boolean) { + B.checkbox.isChecked = isChecked + } + + @CallbackProp + fun checked(onCheckedChangeListener: CompoundButton.OnCheckedChangeListener?) { + B.checkbox.setOnCheckedChangeListener(onCheckedChangeListener) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/views/preference/LinkView.kt b/app/src/main/java/com/aurora/store/view/epoxy/views/preference/LinkView.kt new file mode 100644 index 000000000..82e10949d --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/views/preference/LinkView.kt @@ -0,0 +1,81 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.epoxy.views.preference + +import android.content.Context +import android.util.AttributeSet +import android.widget.RelativeLayout +import com.airbnb.epoxy.CallbackProp +import com.airbnb.epoxy.ModelProp +import com.airbnb.epoxy.ModelView +import com.aurora.store.R +import com.aurora.store.data.model.Link +import com.aurora.store.databinding.ViewLinkBinding +import com.aurora.store.util.extensions.load +import com.aurora.store.util.extensions.show +import com.aurora.store.view.epoxy.views.BaseView + +@ModelView( + autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT, + baseModelClass = BaseView::class +) +class LinkView : RelativeLayout { + + private lateinit var B: ViewLinkBinding + + constructor(context: Context?) : super(context) { + init(context, null) + } + + constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) { + init(context, attrs) + } + + constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super( + context, + attrs, + defStyleAttr + ) { + init(context, attrs) + } + + private fun init(context: Context?, attrs: AttributeSet?) { + val view = inflate(context, R.layout.view_link, this) + B = ViewLinkBinding.bind(view) + } + + @ModelProp + fun link(link: Link) { + B.line1.text = link.title + B.line2.text = link.subtitle + if (!link.url.startsWith("http")) { + B.line3.show() + B.line3.text = link.url + } else { + B.line3.show() + } + B.imgIcon.load(link.icon) + } + + @CallbackProp + fun click(onClickListener: OnClickListener?) { + B.root.setOnClickListener(onClickListener) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/views/preference/LocaleView.kt b/app/src/main/java/com/aurora/store/view/epoxy/views/preference/LocaleView.kt new file mode 100644 index 000000000..03edd4ed5 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/views/preference/LocaleView.kt @@ -0,0 +1,81 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.epoxy.views.preference + +import android.content.Context +import android.util.AttributeSet +import android.widget.CompoundButton +import android.widget.RelativeLayout +import com.airbnb.epoxy.CallbackProp +import com.airbnb.epoxy.ModelProp +import com.airbnb.epoxy.ModelView +import com.aurora.store.R +import com.aurora.store.databinding.ViewLocaleBinding +import com.aurora.store.view.epoxy.views.BaseView +import java.util.* + +@ModelView( + autoLayout = ModelView.Size.MATCH_WIDTH_WRAP_HEIGHT, + baseModelClass = BaseView::class +) +class LocaleView : RelativeLayout { + + private lateinit var B: ViewLocaleBinding + + constructor(context: Context?) : super(context) { + init(context, null) + } + + constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) { + init(context, attrs) + } + + constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super( + context, + attrs, + defStyleAttr + ) { + init(context, attrs) + } + + private fun init(context: Context?, attrs: AttributeSet?) { + val view = inflate(context, R.layout.view_locale, this) + B = ViewLocaleBinding.bind(view) + } + + @ModelProp + fun locale(locale: Locale) { + B.line1.text = locale.displayName + B.line2.text = if (locale.displayCountry.isNotEmpty()) + locale.displayCountry + else + locale.displayLanguage + } + + @ModelProp + fun markChecked(isChecked: Boolean) { + B.checkbox.isChecked = isChecked + } + + @CallbackProp + fun checked(onCheckedChangeListener: CompoundButton.OnCheckedChangeListener?) { + B.checkbox.setOnCheckedChangeListener(onCheckedChangeListener) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/views/shimmer/AppListViewShimmer.kt b/app/src/main/java/com/aurora/store/view/epoxy/views/shimmer/AppListViewShimmer.kt new file mode 100644 index 000000000..79e62dd24 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/views/shimmer/AppListViewShimmer.kt @@ -0,0 +1,58 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.epoxy.views.shimmer + +import android.content.Context +import android.util.AttributeSet +import com.airbnb.epoxy.ModelView +import com.aurora.store.R +import com.aurora.store.databinding.ViewAppListShimmerBinding +import com.aurora.store.view.epoxy.views.BaseView +import com.facebook.shimmer.ShimmerFrameLayout + +@ModelView( + autoLayout = ModelView.Size.WRAP_WIDTH_WRAP_HEIGHT, + baseModelClass = BaseView::class +) +class AppListViewShimmer : ShimmerFrameLayout { + + private lateinit var B: ViewAppListShimmerBinding + + constructor(context: Context?) : super(context) { + init(context, null) + } + + constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) { + init(context, attrs) + } + + constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super( + context, + attrs, + defStyleAttr + ) { + init(context, attrs) + } + + private fun init(context: Context?, attrs: AttributeSet?) { + val view = inflate(context, R.layout.view_app_list_shimmer, this) + B = ViewAppListShimmerBinding.bind(view) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/views/shimmer/AppViewShimmer.kt b/app/src/main/java/com/aurora/store/view/epoxy/views/shimmer/AppViewShimmer.kt new file mode 100644 index 000000000..c651c443d --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/views/shimmer/AppViewShimmer.kt @@ -0,0 +1,58 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.epoxy.views.shimmer + +import android.content.Context +import android.util.AttributeSet +import com.airbnb.epoxy.ModelView +import com.aurora.store.R +import com.aurora.store.databinding.ViewAppShimmerBinding +import com.aurora.store.view.epoxy.views.BaseView +import com.facebook.shimmer.ShimmerFrameLayout + +@ModelView( + autoLayout = ModelView.Size.WRAP_WIDTH_WRAP_HEIGHT, + baseModelClass = BaseView::class +) +class AppViewShimmer : ShimmerFrameLayout { + + private lateinit var B: ViewAppShimmerBinding + + constructor(context: Context?) : super(context) { + init(context, null) + } + + constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) { + init(context, attrs) + } + + constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super( + context, + attrs, + defStyleAttr + ) { + init(context, attrs) + } + + private fun init(context: Context?, attrs: AttributeSet?) { + val view = inflate(context, R.layout.view_app_shimmer, this) + B = ViewAppShimmerBinding.bind(view) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/epoxy/views/shimmer/HeaderViewShimmer.kt b/app/src/main/java/com/aurora/store/view/epoxy/views/shimmer/HeaderViewShimmer.kt new file mode 100644 index 000000000..a10f74539 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/epoxy/views/shimmer/HeaderViewShimmer.kt @@ -0,0 +1,58 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.epoxy.views.shimmer + +import android.content.Context +import android.util.AttributeSet +import com.airbnb.epoxy.ModelView +import com.aurora.store.R +import com.aurora.store.databinding.ViewHeaderShimmerBinding +import com.aurora.store.view.epoxy.views.BaseView +import com.facebook.shimmer.ShimmerFrameLayout + +@ModelView( + autoLayout = ModelView.Size.WRAP_WIDTH_WRAP_HEIGHT, + baseModelClass = BaseView::class +) +class HeaderViewShimmer : ShimmerFrameLayout { + + private lateinit var B: ViewHeaderShimmerBinding + + constructor(context: Context?) : super(context) { + init(context, null) + } + + constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs) { + init(context, attrs) + } + + constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super( + context, + attrs, + defStyleAttr + ) { + init(context, attrs) + } + + private fun init(context: Context?, attrs: AttributeSet?) { + val view = inflate(context, R.layout.view_header_shimmer, this) + B = ViewHeaderShimmerBinding.bind(view) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/about/AboutActivity.kt b/app/src/main/java/com/aurora/store/view/ui/about/AboutActivity.kt new file mode 100644 index 000000000..e0ac61f97 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/about/AboutActivity.kt @@ -0,0 +1,128 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.ui.about + +import android.os.Bundle +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.aurora.store.BuildConfig +import com.aurora.store.R +import com.aurora.store.data.model.Link +import com.aurora.store.databinding.ActivityAboutBinding +import com.aurora.store.util.extensions.browse +import com.aurora.store.util.extensions.close +import com.aurora.store.util.extensions.copyToClipBoard +import com.aurora.store.util.extensions.load +import com.aurora.store.view.epoxy.views.preference.LinkViewModel_ +import com.aurora.store.view.ui.commons.BaseActivity + +class AboutActivity : BaseActivity() { + + private lateinit var B: ActivityAboutBinding + + override fun onConnected() { + + } + + override fun onDisconnected() { + + } + + override fun onReconnected() { + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + B = ActivityAboutBinding.inflate(layoutInflater) + setContentView(B.root) + + attachToolbar() + attachAppDetails() + attachRecycler() + + updateController() + } + + private fun attachToolbar() { + B.layoutToolbarAction.txtTitle.text = getString(R.string.title_about) + B.layoutToolbarAction.imgActionPrimary.setOnClickListener { + close() + } + } + + private fun attachAppDetails() { + B.imgIcon.load(R.drawable.ic_logo) + B.line2.text = ("v${BuildConfig.VERSION_NAME}.${BuildConfig.VERSION_CODE}") + } + + private fun attachRecycler() { + with(B.epoxyRecycler) { + layoutManager = LinearLayoutManager( + this@AboutActivity, + RecyclerView.VERTICAL, + false + ) + } + } + + private fun updateController() { + val linkURLS = resources.getStringArray(R.array.link_urls) + val linkTitles = resources.getStringArray(R.array.link_titles) + val linkSummary = resources.getStringArray(R.array.link_subtitle) + + val linkIcons = intArrayOf( + R.drawable.ic_bitcoin_btc, + R.drawable.ic_bitcoin_bch, + R.drawable.ic_ethereum_eth, + R.drawable.ic_bhim, + R.drawable.ic_paypal, + R.drawable.ic_libera_pay, + R.drawable.ic_gitlab, + R.drawable.ic_xda, + R.drawable.ic_telegram, + R.drawable.ic_fdroid + ) + + B.epoxyRecycler.withModels { + for (i in linkURLS.indices) { + val link = Link( + id = i, + title = linkTitles[i], + subtitle = linkSummary[i], + url = linkURLS[i], + icon = linkIcons[i] + ) + add( + LinkViewModel_() + .id(i) + .link(link) + .click { _ -> processUrl(link.url) } + ) + } + } + } + + private fun processUrl(url: String) { + if (url.startsWith("http")) + browse(url) + else + copyToClipBoard(url) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/account/AccountActivity.kt b/app/src/main/java/com/aurora/store/view/ui/account/AccountActivity.kt new file mode 100644 index 000000000..d55ff92bd --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/account/AccountActivity.kt @@ -0,0 +1,246 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.ui.account + +import android.content.Intent +import android.os.Bundle +import android.view.View +import androidx.lifecycle.ViewModelProvider +import com.aurora.gplayapi.data.models.AuthData +import com.aurora.store.MainActivity +import com.aurora.store.R +import com.aurora.store.data.AuthState +import com.aurora.store.data.event.BusEvent +import com.aurora.store.data.providers.AccountProvider +import com.aurora.store.data.providers.AuthProvider +import com.aurora.store.databinding.ActivityAccountBinding +import com.aurora.store.util.CommonUtil.getEmptyActivityBundle +import com.aurora.store.util.extensions.browse +import com.aurora.store.util.extensions.close +import com.aurora.store.util.extensions.load +import com.aurora.store.view.ui.commons.BaseActivity +import com.aurora.store.viewmodel.auth.AuthViewModel +import com.bumptech.glide.load.resource.bitmap.RoundedCorners +import nl.komponents.kovenant.task +import nl.komponents.kovenant.ui.failUi +import nl.komponents.kovenant.ui.successUi +import org.greenrobot.eventbus.EventBus +import org.greenrobot.eventbus.Subscribe + +class AccountActivity : BaseActivity() { + + private lateinit var VM: AuthViewModel + private lateinit var B: ActivityAccountBinding + + private lateinit var authData: AuthData + private lateinit var accountProvider: AccountProvider + + private val URL_TOS = "https://www.google.com/mobile/android/market-tos.html" + private val URL_LICENSE = "https://gitlab.com/AuroraOSS/AuroraStore/raw/master/LICENSE" + private val URL_DISCLAIMER = "https://gitlab.com/AuroraOSS/AuroraStore/raw/master/DISCLAIMER" + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + EventBus.getDefault().register(this); + + B = ActivityAccountBinding.inflate(layoutInflater) + VM = ViewModelProvider(this).get(AuthViewModel::class.java) + + setContentView(B.root) + + authData = AuthProvider.with(this).getAuthData() + accountProvider = AccountProvider.with(this) + + attachToolbar() + attachChips() + attachActions() + + updateContents() + + VM.liveData.observe(this, { + when (it) { + AuthState.Valid -> { + + } + + AuthState.Available -> { + updateStatus("Verifying session") + updateActionLayout(false) + } + + AuthState.Unavailable -> { + updateStatus("You need to login first") + updateActionLayout(true) + } + + AuthState.SignedIn -> { + updateContents() + } + + AuthState.SignedOut -> { + updateStatus("Last session scrapped") + updateActionLayout(true) + } + + is AuthState.Status -> { + updateStatus(it.status) + } + } + }) + } + + override fun onDestroy() { + EventBus.getDefault().unregister(this) + super.onDestroy() + } + + @Subscribe() + fun onEventReceived(event: BusEvent) { + when (event) { + is BusEvent.GoogleAAS -> { + if (event.success) { + updateStatus("Verifying Google Session") + VM.buildGoogleAuthData(event.email, event.aasToken) + } else { + updateStatus("Failed to login via Google") + } + } + else -> { + + } + } + } + + private fun updateContents() { + if (accountProvider.isSignedIn()) { + B.viewFlipper.displayedChild = 1 + updateStatus("Woah! all good.") + } else { + B.viewFlipper.displayedChild = 0 + updateStatus("Login and enjoy.") + } + + updateUserProfile() + } + + private fun updateStatus(string: String?) { + runOnUiThread { + B.txtStatus.apply { + text = string + } + } + } + + private fun updateActionLayout(isVisible: Boolean) { + if (isVisible) { + B.layoutAction.visibility = View.VISIBLE + } else { + B.layoutAction.visibility = View.INVISIBLE + } + } + + private fun attachToolbar() { + B.layoutToolbarAction.txtTitle.text = getString(R.string.title_account_manager) + B.layoutToolbarAction.imgActionPrimary.setOnClickListener { + close() + } + } + + private fun attachChips() { + B.chipDisclaimer.setOnClickListener { browse(URL_DISCLAIMER) } + B.chipLicense.setOnClickListener { browse(URL_LICENSE) } + B.chipTos.setOnClickListener { browse(URL_TOS) } + } + + + private fun attachActions() { + B.btnAnonymous.updateProgress(false) + B.btnGoogle.updateProgress(false) + + B.btnAnonymous.addOnClickListener { + B.btnAnonymous.updateProgress(true) + VM.buildAnonymousAuthData() + } + + B.btnGoogle.addOnClickListener { + B.btnGoogle.updateProgress(true) + openGoogleActivity() + } + + B.btnLogout.addOnClickListener { + task { + AccountProvider.with(this).logout() + } successUi { + B.btnAnonymous.updateProgress(false) + B.btnGoogle.updateProgress(false) + updateContents() + } failUi { + + } + } + } + + private fun updateUserProfile() { + authData = AuthProvider.with(this).getAuthData() + + if (accountProvider.isSignedIn()) { + authData.userProfile?.let { + B.imgAvatar.load(it.artwork.url) { + placeholder(R.drawable.bg_placeholder) + transform(RoundedCorners(32)) + } + + B.txtName.text = if (authData.isAnonymous) + "Anonymous" + else + it.name + + B.txtEmail.text = it.email + } + } else { + B.imgAvatar.load(R.mipmap.ic_launcher) { + transform(RoundedCorners(32)) + } + B.txtName.text = getString(R.string.app_name) + B.txtEmail.text = getString(R.string.account_logged_out) + } + } + + private fun moveToContent() { + runOnUiThread { + val intent = Intent(this, MainActivity::class.java) + intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK + startActivity(intent, getEmptyActivityBundle(this)) + } + } + + override fun onConnected() { + hideNetworkConnectivitySheet() + } + + override fun onDisconnected() { + showNetworkConnectivitySheet() + } + + override fun onReconnected() { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/account/GoogleActivity.kt b/app/src/main/java/com/aurora/store/view/ui/account/GoogleActivity.kt new file mode 100644 index 000000000..5f8913763 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/account/GoogleActivity.kt @@ -0,0 +1,141 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.ui.account + +import android.annotation.SuppressLint +import android.os.Build +import android.os.Bundle +import android.webkit.CookieManager +import android.webkit.WebSettings +import android.webkit.WebView +import android.webkit.WebViewClient +import android.widget.Toast +import com.aurora.Constants +import com.aurora.store.data.event.BusEvent +import com.aurora.store.databinding.ActivityGoogleBinding +import com.aurora.store.util.AC2DMTask +import com.aurora.store.util.Preferences +import com.aurora.store.util.Util +import com.aurora.store.util.extensions.close +import com.aurora.store.util.extensions.isLAndAbove +import com.aurora.store.view.ui.commons.BaseActivity +import nl.komponents.kovenant.task +import org.greenrobot.eventbus.EventBus + +class GoogleActivity : BaseActivity() { + + private lateinit var B: ActivityGoogleBinding + + private val cookieManager = CookieManager.getInstance() + + override fun onConnected() { + } + + override fun onDisconnected() { + showNetworkConnectivitySheet() + } + + override fun onReconnected() { + hideNetworkConnectivitySheet() + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + B = ActivityGoogleBinding.inflate(layoutInflater) + setContentView(B.root) + + setupWebView() + } + + @SuppressLint("SetJavaScriptEnabled") + private fun setupWebView() { + if (isLAndAbove()) { + cookieManager.removeAllCookies(null) + cookieManager.acceptThirdPartyCookies(B.webview) + cookieManager.setAcceptThirdPartyCookies(B.webview, true) + } + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + B.webview.settings.safeBrowsingEnabled = false + } + + B.webview.webViewClient = object : WebViewClient() { + override fun onPageFinished(view: WebView, url: String) { + val cookies = CookieManager.getInstance().getCookie(url) + val cookieMap = Util.parseCookieString(cookies) + if (cookieMap.isNotEmpty() && cookieMap[AUTH_TOKEN] != null) { + val oauthToken = cookieMap[AUTH_TOKEN] + B.webview.evaluateJavascript("(function() { return document.getElementById('profileIdentifier').innerHTML; })();") { + val email = it.replace("\"".toRegex(), "") + buildAuthData(email, oauthToken) + } + } + } + } + + B.webview.apply { + settings.apply { + allowContentAccess = true + databaseEnabled = true + domStorageEnabled = true + javaScriptEnabled = true + cacheMode = WebSettings.LOAD_DEFAULT + } + loadUrl(EMBEDDED_SETUP_URL) + } + } + + private fun buildAuthData(email: String, oauthToken: String?) { + task { + AC2DMTask().getAC2DMResponse(email, oauthToken) + } success { + if (it.isNotEmpty()) { + val aasToken = it["Token"] + if (aasToken != null) { + Preferences.putString(this, Constants.ACCOUNT_EMAIL_PLAIN, email) + Preferences.putString(this, Constants.ACCOUNT_AAS_PLAIN, aasToken) + EventBus.getDefault().post(BusEvent.GoogleAAS(true, email, aasToken)) + } else { + Preferences.putString(this, Constants.ACCOUNT_EMAIL_PLAIN, "") + Preferences.putString(this, Constants.ACCOUNT_AAS_PLAIN, "") + EventBus.getDefault().post(BusEvent.GoogleAAS(false)) + } + } else { + Toast.makeText(this, "Failed to generate AAS Token", Toast.LENGTH_LONG).show() + EventBus.getDefault().post(BusEvent.GoogleAAS(false)) + } + + //Close Activity + close() + } fail { + Toast.makeText(this, "Failed to generate AAS Token", Toast.LENGTH_LONG).show() + EventBus.getDefault().post(BusEvent.GoogleAAS(false)) + + //Close Activity + close() + } + } + + companion object { + const val EMBEDDED_SETUP_URL = + "https://accounts.google.com/EmbeddedSetup/identifier?flowName=EmbeddedSetupAndroid" + const val AUTH_TOKEN = "oauth_token" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/all/AppsGamesActivity.kt b/app/src/main/java/com/aurora/store/view/ui/all/AppsGamesActivity.kt new file mode 100644 index 000000000..9d6fea394 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/all/AppsGamesActivity.kt @@ -0,0 +1,103 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.ui.all + +import android.os.Bundle +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentManager +import androidx.lifecycle.Lifecycle +import androidx.viewpager2.adapter.FragmentStateAdapter +import com.aurora.gplayapi.data.models.AuthData +import com.aurora.store.R +import com.aurora.store.data.providers.AuthProvider +import com.aurora.store.databinding.ActivityGenericPagerBinding +import com.aurora.store.util.extensions.close +import com.aurora.store.view.ui.commons.BaseActivity +import com.google.android.material.tabs.TabLayout +import com.google.android.material.tabs.TabLayoutMediator + +class AppsGamesActivity : BaseActivity() { + + private lateinit var B: ActivityGenericPagerBinding + private lateinit var authData: AuthData + + override fun onConnected() { + hideNetworkConnectivitySheet() + } + + override fun onDisconnected() { + showNetworkConnectivitySheet() + } + + override fun onReconnected() { + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + B = ActivityGenericPagerBinding.inflate(layoutInflater) + authData = AuthProvider.with(this).getAuthData() + + setContentView(B.root) + attachToolbar() + attachViewPager() + } + + private fun attachToolbar() { + B.layoutToolbarAction.txtTitle.text = getString(R.string.title_apps_games) + B.layoutToolbarAction.imgActionPrimary.setOnClickListener { + close() + } + } + + private fun attachViewPager() { + B.pager.adapter = ViewPagerAdapter(supportFragmentManager, lifecycle, authData.isAnonymous) + B.pager.isUserInputEnabled = false + TabLayoutMediator(B.tabLayout, B.pager, true) { tab: TabLayout.Tab, position: Int -> + when (position) { + 0 -> tab.text = getString(R.string.title_installed) + 1 -> tab.text = getString(R.string.title_library) + else -> { + } + } + }.attach() + } + + internal class ViewPagerAdapter( + fragment: FragmentManager, + lifecycle: Lifecycle, + private val isAnonymous: Boolean + ) : + FragmentStateAdapter(fragment, lifecycle) { + override fun createFragment(position: Int): Fragment { + return when (position) { + 0 -> InstalledAppsFragment.newInstance() + 1 -> LibraryAppsFragment.newInstance() + else -> Fragment() + } + } + + override fun getItemCount(): Int { + return if (isAnonymous) + 1 + else + 2 + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/all/InstalledAppsFragment.kt b/app/src/main/java/com/aurora/store/view/ui/all/InstalledAppsFragment.kt new file mode 100644 index 000000000..75ea2fc9c --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/all/InstalledAppsFragment.kt @@ -0,0 +1,130 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.ui.all + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.lifecycle.ViewModelProvider +import com.aurora.Constants +import com.aurora.gplayapi.data.models.App +import com.aurora.store.R +import com.aurora.store.databinding.FragmentUpdatesBinding +import com.aurora.store.view.epoxy.views.AppListViewModel_ +import com.aurora.store.view.epoxy.views.HeaderViewModel_ +import com.aurora.store.view.epoxy.views.shimmer.AppListViewShimmerModel_ +import com.aurora.store.view.ui.commons.BaseFragment +import com.aurora.store.view.ui.sheets.AppMenuSheet +import com.aurora.store.viewmodel.all.InstalledViewModel + +class InstalledAppsFragment : BaseFragment() { + + private lateinit var VM: InstalledViewModel + private lateinit var B: FragmentUpdatesBinding + + companion object { + @JvmStatic + fun newInstance(): InstalledAppsFragment { + return InstalledAppsFragment().apply { + + } + } + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + B = FragmentUpdatesBinding.bind( + inflater.inflate( + R.layout.fragment_updates, + container, + false + ) + ) + + VM = ViewModelProvider(requireActivity()).get(InstalledViewModel::class.java) + + return B.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + VM.liveData.observe(viewLifecycleOwner, { + updateController(it) + B.swipeRefreshLayout.isRefreshing = false + }) + + B.swipeRefreshLayout.setOnRefreshListener { + VM.observe() + } + + updateController(null) + } + + private fun updateController(appList: List?) { + B.recycler.withModels { + setFilterDuplicates(true) + if (appList == null) { + for (i in 1..6) { + add( + AppListViewShimmerModel_() + .id(i) + ) + } + } else { + add( + HeaderViewModel_() + .id("header") + .title("${appList.size} apps installed") + ) + appList.forEach { app -> + add( + AppListViewModel_() + .id(app.id) + .app(app) + .click { _ -> openDetailsActivity(app) } + .longClick { _ -> + openAppMenuSheet(app) + false + } + ) + } + } + } + } + + private fun openAppMenuSheet(app: App) { + val fragment = childFragmentManager.findFragmentByTag(AppMenuSheet.TAG) + if (fragment != null) + childFragmentManager.beginTransaction().remove(fragment) + + AppMenuSheet().apply { + arguments = Bundle().apply { + putString(Constants.STRING_EXTRA, gson.toJson(app)) + } + }.show( + childFragmentManager, + AppMenuSheet.TAG + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/all/LibraryAppsFragment.kt b/app/src/main/java/com/aurora/store/view/ui/all/LibraryAppsFragment.kt new file mode 100644 index 000000000..0ebba9183 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/all/LibraryAppsFragment.kt @@ -0,0 +1,130 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.ui.all + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.lifecycle.ViewModelProvider +import com.aurora.gplayapi.data.models.StreamCluster +import com.aurora.store.R +import com.aurora.store.databinding.FragmentUpdatesBinding +import com.aurora.store.view.custom.recycler.EndlessRecyclerOnScrollListener +import com.aurora.store.view.epoxy.views.AppListViewModel_ +import com.aurora.store.view.epoxy.views.AppProgressViewModel_ +import com.aurora.store.view.epoxy.views.HeaderViewModel_ +import com.aurora.store.view.epoxy.views.shimmer.AppListViewShimmerModel_ +import com.aurora.store.view.ui.commons.BaseFragment +import com.aurora.store.viewmodel.all.LibraryAppsViewModel + +class LibraryAppsFragment : BaseFragment() { + + private lateinit var VM: LibraryAppsViewModel + private lateinit var B: FragmentUpdatesBinding + lateinit var endlessRecyclerOnScrollListener: EndlessRecyclerOnScrollListener + + companion object { + @JvmStatic + fun newInstance(): LibraryAppsFragment { + return LibraryAppsFragment().apply { + + } + } + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + B = FragmentUpdatesBinding.bind( + inflater.inflate( + R.layout.fragment_updates, + container, + false + ) + ) + + VM = ViewModelProvider(requireActivity()).get(LibraryAppsViewModel::class.java) + + return B.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + B.swipeRefreshLayout.isEnabled = false + VM.liveData.observe(viewLifecycleOwner, { + updateController(it) + }) + attachRecycler() + + updateController(null) + } + + private fun attachRecycler() { + endlessRecyclerOnScrollListener = object : EndlessRecyclerOnScrollListener() { + override fun onLoadMore(currentPage: Int) { + VM.observe() + } + } + B.recycler.addOnScrollListener(endlessRecyclerOnScrollListener) + } + + private fun updateController(streamCluster: StreamCluster?) { + B.recycler.withModels { + setFilterDuplicates(true) + if (streamCluster == null) { + for (i in 1..6) { + add( + AppListViewShimmerModel_() + .id(i) + ) + } + } else { + add( + HeaderViewModel_() + .id("header") + .title( + if (streamCluster.clusterTitle.isEmpty()) + getString(R.string.title_apps_library) + else + streamCluster.clusterTitle + ) + ) + streamCluster.clusterAppList.forEach { app -> + add( + AppListViewModel_() + .id(app.id) + .app(app) + .click { _ -> openDetailsActivity(app) } + ) + } + + if (streamCluster.hasNext()) { + add( + AppProgressViewModel_() + .id("progress") + ) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/apps/AppsContainerFragment.kt b/app/src/main/java/com/aurora/store/view/ui/apps/AppsContainerFragment.kt new file mode 100644 index 000000000..36ec47819 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/apps/AppsContainerFragment.kt @@ -0,0 +1,106 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.ui.apps + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentManager +import androidx.lifecycle.Lifecycle +import androidx.viewpager2.adapter.FragmentStateAdapter +import com.aurora.gplayapi.data.models.AuthData +import com.aurora.store.R +import com.aurora.store.data.providers.AuthProvider +import com.aurora.store.databinding.FragmentAppsGamesBinding +import com.aurora.store.view.ui.commons.CategoryFragment +import com.aurora.store.view.ui.commons.EditorChoiceFragment +import com.aurora.store.view.ui.commons.ForYouFragment +import com.google.android.material.tabs.TabLayout +import com.google.android.material.tabs.TabLayoutMediator + + +class AppsContainerFragment : Fragment() { + + private lateinit var B: FragmentAppsGamesBinding + private lateinit var authData: AuthData + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + B = FragmentAppsGamesBinding.bind( + inflater.inflate( + R.layout.fragment_apps_games, + container, + false + ) + ) + return B.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + authData = AuthProvider.with(requireContext()).getAuthData() + setupViewPager() + } + + private fun setupViewPager() { + B.pager.adapter = ViewPagerAdapter(childFragmentManager, lifecycle, authData.isAnonymous) + B.pager.isUserInputEnabled = false //Disable viewpager scroll to avoid scroll conflicts + + TabLayoutMediator(B.tabLayout, B.pager, true) { tab: TabLayout.Tab, position: Int -> + when (position) { + 0 -> tab.text = "For you" + 1 -> tab.text = "Top charts" + 2 -> tab.text = "Categories" + 3 -> tab.text = "Editor's choice" + else -> { + } + } + }.attach() + } + + internal class ViewPagerAdapter( + fragment: FragmentManager, + lifecycle: Lifecycle, + private val isAnonymous: Boolean + ) : + FragmentStateAdapter(fragment, lifecycle) { + override fun createFragment(position: Int): Fragment { + return when (position) { + 0 -> ForYouFragment.newInstance(0) + 1 -> TopChartContainerFragment() + 2 -> CategoryFragment.newInstance(0) + 3 -> EditorChoiceFragment.newInstance(0) + else -> Fragment() + } + } + + override fun getItemCount(): Int { + return if (isAnonymous) + 3 + else + 4 + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/apps/TopChartContainerFragment.kt b/app/src/main/java/com/aurora/store/view/ui/apps/TopChartContainerFragment.kt new file mode 100644 index 000000000..3140d9a19 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/apps/TopChartContainerFragment.kt @@ -0,0 +1,104 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.ui.apps + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentManager +import androidx.lifecycle.Lifecycle +import androidx.viewpager2.adapter.FragmentStateAdapter +import androidx.viewpager2.widget.ViewPager2 +import com.aurora.gplayapi.data.models.AuthData +import com.aurora.store.R +import com.aurora.store.data.providers.AuthProvider +import com.aurora.store.databinding.FragmentTopChartBinding + + +class TopChartContainerFragment : Fragment() { + + private lateinit var B: FragmentTopChartBinding + + private lateinit var authData: AuthData + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + B = FragmentTopChartBinding.bind( + inflater.inflate( + R.layout.fragment_top_chart, + container, + false + ) + ) + return B.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + authData = AuthProvider.with(requireContext()).getAuthData() + setupViewPager() + } + + private fun setupViewPager() { + B.pager.adapter = ViewPagerAdapter(childFragmentManager, lifecycle) + B.topTabGroup.setOnCheckedChangeListener { group, checkedId -> + when (checkedId) { + R.id.tab_top_free -> B.pager.setCurrentItem(0, true) + R.id.tab_top_grossing -> B.pager.setCurrentItem(1, true) + R.id.tab_trending -> B.pager.setCurrentItem(2, true) + R.id.tab_top_paid -> B.pager.setCurrentItem(3, true) + } + } + + B.pager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() { + override fun onPageSelected(position: Int) { + super.onPageSelected(position) + when (position) { + 0 -> B.topTabGroup.check(R.id.tab_top_free) + 1 -> B.topTabGroup.check(R.id.tab_top_grossing) + 2 -> B.topTabGroup.check(R.id.tab_trending) + 3 -> B.topTabGroup.check(R.id.tab_top_paid) + } + } + }) + } + + internal class ViewPagerAdapter(fragment: FragmentManager, lifecycle: Lifecycle) : + FragmentStateAdapter(fragment, lifecycle) { + override fun createFragment(position: Int): Fragment { + return when (position) { + 0 -> TopChartFragment.newInstance(0, 0) + 1 -> TopChartFragment.newInstance(0, 1) + 2 -> TopChartFragment.newInstance(0, 2) + 3 -> TopChartFragment.newInstance(0, 3) + else -> Fragment() + } + } + + override fun getItemCount(): Int { + return 4 + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/apps/TopChartFragment.kt b/app/src/main/java/com/aurora/store/view/ui/apps/TopChartFragment.kt new file mode 100644 index 000000000..9ff6b1c13 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/apps/TopChartFragment.kt @@ -0,0 +1,132 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.ui.apps + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.lifecycle.ViewModelProvider +import com.aurora.Constants +import com.aurora.gplayapi.data.models.StreamCluster +import com.aurora.store.R +import com.aurora.store.databinding.FragmentTopContainerBinding +import com.aurora.store.view.custom.recycler.EndlessRecyclerOnScrollListener +import com.aurora.store.view.epoxy.views.AppListViewModel_ +import com.aurora.store.view.epoxy.views.AppProgressViewModel_ +import com.aurora.store.view.epoxy.views.shimmer.AppListViewShimmerModel_ +import com.aurora.store.view.ui.commons.BaseFragment +import com.aurora.store.viewmodel.topchart.* + +class TopChartFragment : BaseFragment() { + + private lateinit var VM: BaseChartViewModel + private lateinit var B: FragmentTopContainerBinding + + private var chartType = 0 + private var chartCategory = 0 + + companion object { + @JvmStatic + fun newInstance(chartType: Int, chartCategory: Int): TopChartFragment { + return TopChartFragment().apply { + arguments = Bundle().apply { + putInt(Constants.TOP_CHART_TYPE, chartType) + putInt(Constants.TOP_CHART_CATEGORY, chartCategory) + } + } + } + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + B = FragmentTopContainerBinding.bind( + inflater.inflate( + R.layout.fragment_top_container, + container, + false + ) + ) + + val bundle = arguments + if (bundle != null) { + chartType = bundle.getInt(Constants.TOP_CHART_TYPE, 0) + chartCategory = bundle.getInt(Constants.TOP_CHART_CATEGORY, 0) + } + + when (chartCategory) { + 0 -> VM = ViewModelProvider(this).get(TopFreeAppChartViewModel::class.java) + 1 -> VM = ViewModelProvider(this).get(TopGrossingAppChartViewModel::class.java) + 2 -> VM = ViewModelProvider(this).get(TrendingAppChartViewModel::class.java) + 3 -> VM = ViewModelProvider(this).get(TopPaidAppChartViewModel::class.java) + } + + return B.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + VM.liveData.observe(viewLifecycleOwner, { + updateController(it) + }) + + B.recycler.addOnScrollListener(object : EndlessRecyclerOnScrollListener() { + override fun onLoadMore(currentPage: Int) { + VM.nextCluster() + } + }) + + updateController(null) + } + + private fun updateController(streamCluster: StreamCluster?) { + B.recycler.withModels { + setFilterDuplicates(true) + if (streamCluster == null) { + for (i in 1..6) { + add( + AppListViewShimmerModel_() + .id(i) + ) + } + } else { + streamCluster.clusterAppList.forEach { app -> + add( + AppListViewModel_() + .id(app.id) + .app(app) + .click { _ -> openDetailsActivity(app) } + ) + } + + if (streamCluster.hasNext()) { + add( + AppProgressViewModel_() + .id("progress") + ) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/commons/BaseActivity.kt b/app/src/main/java/com/aurora/store/view/ui/commons/BaseActivity.kt new file mode 100644 index 000000000..f74cb3fc6 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/commons/BaseActivity.kt @@ -0,0 +1,144 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.ui.commons + +import android.app.ActivityOptions +import android.content.Intent +import android.os.Build +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import androidx.fragment.app.Fragment +import com.aurora.Constants +import com.aurora.gplayapi.data.models.App +import com.aurora.store.data.providers.NetworkProvider +import com.aurora.store.util.Preferences +import com.aurora.store.util.Preferences.PREFERENCE_THEME_ACCENT +import com.aurora.store.util.Preferences.PREFERENCE_THEME_TYPE +import com.aurora.store.util.ViewUtil +import com.aurora.store.util.extensions.applyTheme +import com.aurora.store.view.ui.account.GoogleActivity +import com.aurora.store.view.ui.details.AppDetailsActivity +import com.aurora.store.view.ui.details.DetailsMoreActivity +import com.aurora.store.view.ui.details.DetailsReviewActivity +import com.aurora.store.view.ui.sheets.NetworkDialogSheet +import com.google.gson.Gson +import com.google.gson.GsonBuilder +import java.lang.reflect.Modifier + + +abstract class BaseActivity : AppCompatActivity(), NetworkProvider.NetworkListener { + + protected val gson: Gson = GsonBuilder().excludeFieldsWithModifiers(Modifier.TRANSIENT).create() + + override fun onCreate(savedInstanceState: Bundle?) { + val themeId = Preferences.getInteger(this, PREFERENCE_THEME_TYPE) + val accentId = Preferences.getInteger(this, PREFERENCE_THEME_ACCENT) + applyTheme(themeId, accentId) + super.onCreate(savedInstanceState) + } + + fun openDetailsActivity(app: App) { + val intent = Intent( + this, + AppDetailsActivity::class.java + ) + intent.putExtra(Constants.STRING_EXTRA, gson.toJson(app)) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + val options = + ActivityOptions.makeSceneTransitionAnimation(this) + startActivity(intent, options.toBundle()) + } else { + startActivity(intent) + } + } + + fun openDetailsMoreActivity(app: App) { + val intent = Intent( + this, + DetailsMoreActivity::class.java + ) + intent.putExtra(Constants.STRING_EXTRA, gson.toJson(app)) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + val options = + ActivityOptions.makeSceneTransitionAnimation(this) + startActivity(intent, options.toBundle()) + } else { + startActivity(intent) + } + } + + fun openDetailsReviewActivity(app: App) { + val intent = Intent( + this, + DetailsReviewActivity::class.java + ) + intent.putExtra(Constants.STRING_EXTRA, gson.toJson(app)) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + val options = + ActivityOptions.makeSceneTransitionAnimation(this) + startActivity(intent, options.toBundle()) + } else { + startActivity(intent) + } + } + + fun openStreamBrowseActivity(browseUrl: String) { + val intent = Intent(this, StreamBrowseActivity::class.java) + intent.putExtra(Constants.BROWSE_EXTRA, browseUrl) + startActivity( + intent, + ViewUtil.getEmptyActivityBundle(this) + ) + } + + fun openGoogleActivity() { + val intent = Intent(this, GoogleActivity::class.java) + startActivity( + intent, + ViewUtil.getEmptyActivityBundle(this) + ) + } + + fun showNetworkConnectivitySheet() { + supportFragmentManager.beginTransaction() + .add(NetworkDialogSheet.newInstance(0), "NDS") + .commitAllowingStateLoss() + } + + fun hideNetworkConnectivitySheet() { + val fragment: Fragment? = supportFragmentManager.findFragmentByTag("NDS") + if (fragment != null) { + supportFragmentManager.beginTransaction() + .remove(fragment) + .commitAllowingStateLoss() + } + } + + override fun onStart() { + super.onStart() + NetworkProvider.addListener(this) + + } + + override fun onStop() { + NetworkProvider.removeListener(this) + super.onStop() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/commons/BaseFragment.kt b/app/src/main/java/com/aurora/store/view/ui/commons/BaseFragment.kt new file mode 100644 index 000000000..d8c414009 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/commons/BaseFragment.kt @@ -0,0 +1,74 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.ui.commons + +import android.app.ActivityOptions +import android.content.Intent +import android.os.Build +import androidx.fragment.app.Fragment +import com.aurora.Constants +import com.aurora.gplayapi.data.models.App +import com.aurora.gplayapi.data.models.Category +import com.aurora.store.util.ViewUtil +import com.aurora.store.view.ui.details.AppDetailsActivity +import com.google.gson.Gson +import com.google.gson.GsonBuilder +import java.lang.reflect.Modifier + +open class BaseFragment : Fragment() { + + protected lateinit var app: App + + var gson: Gson = GsonBuilder().excludeFieldsWithModifiers( + Modifier.TRANSIENT + ).create() + + fun openDetailsActivity(app: App) { + val intent = Intent(context, AppDetailsActivity::class.java) + intent.putExtra(Constants.STRING_EXTRA, Gson().toJson(app)) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + val options = ActivityOptions.makeSceneTransitionAnimation(requireActivity()) + startActivity(intent, options.toBundle()) + } else { + startActivity(intent) + } + } + + fun openCategoryBrowseActivity(category: Category) { + val intent = Intent(context, CategoryBrowseActivity::class.java) + intent.putExtra(Constants.STRING_EXTRA, category.title) + intent.putExtra(Constants.BROWSE_EXTRA, category.browseUrl) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + val options = ActivityOptions.makeSceneTransitionAnimation(requireActivity()) + startActivity(intent, options.toBundle()) + } else { + startActivity(intent) + } + } + + fun openStreamBrowseActivity(browseUrl: String) { + val intent = Intent(requireContext(), StreamBrowseActivity::class.java) + intent.putExtra(Constants.BROWSE_EXTRA, browseUrl) + startActivity( + intent, + ViewUtil.getEmptyActivityBundle(requireContext()) + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/commons/BlacklistActivity.kt b/app/src/main/java/com/aurora/store/view/ui/commons/BlacklistActivity.kt new file mode 100644 index 000000000..4a894cfaa --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/commons/BlacklistActivity.kt @@ -0,0 +1,114 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.ui.commons + +import android.os.Bundle +import androidx.lifecycle.ViewModelProvider +import com.aurora.store.R +import com.aurora.store.data.model.Black +import com.aurora.store.data.providers.BlacklistProvider +import com.aurora.store.databinding.ActivityGenericRecyclerBinding +import com.aurora.store.util.extensions.close +import com.aurora.store.view.epoxy.views.BlackViewModel_ +import com.aurora.store.view.epoxy.views.shimmer.AppListViewShimmerModel_ +import com.aurora.store.viewmodel.all.BlacklistViewModel + + +class BlacklistActivity : BaseActivity() { + + private lateinit var B: ActivityGenericRecyclerBinding + private lateinit var VM: BlacklistViewModel + private lateinit var blacklistProvider: BlacklistProvider + + override fun onConnected() { + + } + + override fun onDisconnected() { + + } + + override fun onReconnected() { + + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + B = ActivityGenericRecyclerBinding.inflate(layoutInflater) + VM = ViewModelProvider(this).get(BlacklistViewModel::class.java) + blacklistProvider = BlacklistProvider.with(this) + + setContentView(B.root) + + VM.liveData.observe(this, { + updateController(it.sortedByDescending { app -> + blacklistProvider.isBlacklisted(app.packageName) + }) + }) + + attachToolbar() + + updateController(null) + } + + override fun onDestroy() { + blacklistProvider.save(VM.selected) + super.onDestroy() + } + + private fun attachToolbar() { + B.layoutToolbarAction.txtTitle.text = getString(R.string.title_blacklist_manager) + B.layoutToolbarAction.imgActionPrimary.setOnClickListener { + close() + } + } + + private fun updateController(blackList: List?) { + B.recycler.withModels { + setFilterDuplicates(true) + if (blackList == null) { + for (i in 1..6) { + add( + AppListViewShimmerModel_() + .id(i) + ) + } + } else { + blackList.forEach { + add( + BlackViewModel_() + .id(it.packageName.hashCode()) + .black(it) + .markChecked(VM.selected.contains(it.packageName)) + .checked { _, isChecked -> + if (isChecked) + VM.selected.add(it.packageName) + else + VM.selected.remove(it.packageName) + + requestModelBuild() + } + ) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/commons/CategoryBrowseActivity.kt b/app/src/main/java/com/aurora/store/view/ui/commons/CategoryBrowseActivity.kt new file mode 100644 index 000000000..7ef64d098 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/commons/CategoryBrowseActivity.kt @@ -0,0 +1,150 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.ui.commons + +import android.os.Bundle +import android.widget.Toast +import androidx.lifecycle.ViewModelProvider +import com.aurora.Constants +import com.aurora.gplayapi.data.models.App +import com.aurora.gplayapi.data.models.StreamBundle +import com.aurora.gplayapi.data.models.StreamCluster +import com.aurora.store.data.ViewState +import com.aurora.store.databinding.ActivityGenericRecyclerBinding +import com.aurora.store.util.extensions.close +import com.aurora.store.view.custom.recycler.EndlessRecyclerOnScrollListener +import com.aurora.store.view.epoxy.controller.CategoryCarouselController +import com.aurora.store.view.epoxy.controller.GenericCarouselController +import com.aurora.store.view.ui.sheets.AppPeekDialogSheet +import com.aurora.store.viewmodel.subcategory.SubCategoryClusterViewModel + + +class CategoryBrowseActivity : BaseActivity(), GenericCarouselController.Callbacks { + + lateinit var B: ActivityGenericRecyclerBinding + lateinit var C: GenericCarouselController + lateinit var VM: SubCategoryClusterViewModel + + lateinit var endlessRecyclerOnScrollListener: EndlessRecyclerOnScrollListener + + lateinit var title: String + lateinit var homeUrl: String + + override fun onConnected() { + hideNetworkConnectivitySheet() + } + + override fun onDisconnected() { + showNetworkConnectivitySheet() + } + + override fun onReconnected() { + + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + B = ActivityGenericRecyclerBinding.inflate(layoutInflater) + C = CategoryCarouselController(this) + VM = ViewModelProvider(this).get(SubCategoryClusterViewModel::class.java) + + setContentView(B.root) + + attachToolbar() + attachRecycler() + + intent.apply { + homeUrl = getStringExtra(Constants.BROWSE_EXTRA).toString() + title = getStringExtra(Constants.STRING_EXTRA).toString() + VM.observeCategory(homeUrl) + updateTitle(title) + } + + updateController(null) + } + + private fun updateTitle(title: String) { + B.layoutToolbarAction.txtTitle.text = title + } + + private fun attachToolbar() { + B.layoutToolbarAction.imgActionPrimary.setOnClickListener { + close() + } + } + + private fun attachRecycler() { + + B.recycler.setController(C) + + VM.liveData.observe(this, { + when (it) { + is ViewState.Empty -> { + } + is ViewState.Loading -> { + updateController(null) + } + is ViewState.Error -> { + + } + is ViewState.Success<*> -> { + updateController(it.data as StreamBundle) + } + } + }) + + endlessRecyclerOnScrollListener = + object : EndlessRecyclerOnScrollListener() { + override fun onLoadMore(currentPage: Int) { + VM.observe() + } + } + + endlessRecyclerOnScrollListener.disable() + + B.recycler.addOnScrollListener(endlessRecyclerOnScrollListener) + } + + private fun updateController(streamBundle: StreamBundle?) { + if (streamBundle != null) + endlessRecyclerOnScrollListener.enable() + C.setData(streamBundle) + } + + override fun onHeaderClicked(streamCluster: StreamCluster) { + if (streamCluster.clusterBrowseUrl.isNotEmpty()) + openStreamBrowseActivity(streamCluster.clusterBrowseUrl) + else + Toast.makeText(this, "Browse page unavailable", Toast.LENGTH_SHORT).show() + } + + override fun onClusterScrolled(streamCluster: StreamCluster) { + VM.observeCluster(streamCluster) + } + + override fun onAppClick(app: App) { + openDetailsActivity(app) + } + + override fun onAppLongClick(app: App) { + AppPeekDialogSheet.newInstance(app).show(supportFragmentManager, "APDS") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/commons/CategoryFragment.kt b/app/src/main/java/com/aurora/store/view/ui/commons/CategoryFragment.kt new file mode 100644 index 000000000..cff479aa3 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/commons/CategoryFragment.kt @@ -0,0 +1,101 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.ui.commons + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.lifecycle.ViewModelProvider +import com.aurora.Constants +import com.aurora.gplayapi.data.models.Category +import com.aurora.store.R +import com.aurora.store.databinding.FragmentGenericRecyclerBinding +import com.aurora.store.view.epoxy.views.CategoryViewModel_ +import com.aurora.store.viewmodel.category.AppCategoryViewModel +import com.aurora.store.viewmodel.category.BaseCategoryViewModel +import com.aurora.store.viewmodel.category.GameCategoryViewModel + + +class CategoryFragment : BaseFragment() { + + private lateinit var B: FragmentGenericRecyclerBinding + private lateinit var VM: BaseCategoryViewModel + + private var pageType = 0 + + companion object { + @JvmStatic + fun newInstance(pageType: Int): CategoryFragment { + return CategoryFragment().apply { + arguments = Bundle().apply { + putInt(Constants.PAGE_TYPE, pageType) + } + } + } + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + B = FragmentGenericRecyclerBinding.bind( + inflater.inflate( + R.layout.fragment_generic_recycler, + container, + false + ) + ) + + val bundle = arguments + if (bundle != null) { + pageType = bundle.getInt(Constants.PAGE_TYPE, 0) + } + + when (pageType) { + 0 -> VM = ViewModelProvider(this).get(AppCategoryViewModel::class.java) + 1 -> VM = ViewModelProvider(this).get(GameCategoryViewModel::class.java) + } + + return B.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + VM.liveData.observe(viewLifecycleOwner, { + updateController(it) + }) + } + + private fun updateController(categoryList: List) { + B.recycler.withModels { + setFilterDuplicates(true) + categoryList.forEach { + add( + CategoryViewModel_() + .id(it.title) + .category(it) + .click { _ -> openCategoryBrowseActivity(it) } + ) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/commons/EarlyAccessFragment.kt b/app/src/main/java/com/aurora/store/view/ui/commons/EarlyAccessFragment.kt new file mode 100644 index 000000000..606cec963 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/commons/EarlyAccessFragment.kt @@ -0,0 +1,141 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.ui.commons + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Toast +import androidx.lifecycle.ViewModelProvider +import com.aurora.Constants +import com.aurora.gplayapi.data.models.App +import com.aurora.gplayapi.data.models.StreamBundle +import com.aurora.gplayapi.data.models.StreamCluster +import com.aurora.store.R +import com.aurora.store.data.ViewState +import com.aurora.store.databinding.FragmentForYouBinding +import com.aurora.store.view.custom.recycler.EndlessRecyclerOnScrollListener +import com.aurora.store.view.epoxy.controller.EarlyAccessCarouselController +import com.aurora.store.view.epoxy.controller.GenericCarouselController +import com.aurora.store.viewmodel.homestream.BaseClusterViewModel +import com.aurora.store.viewmodel.homestream.EarlyAccessAppsViewModel +import com.aurora.store.viewmodel.homestream.EarlyAccessGamesViewModel + + +class EarlyAccessFragment : BaseFragment(), GenericCarouselController.Callbacks { + + private lateinit var B: FragmentForYouBinding + private lateinit var C: GenericCarouselController + private lateinit var VM: BaseClusterViewModel + + private var pageType = 0 + + companion object { + @JvmStatic + fun newInstance(pageType: Int): EarlyAccessFragment { + return EarlyAccessFragment().apply { + arguments = Bundle().apply { + putInt(Constants.PAGE_TYPE, pageType) + } + } + } + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + B = FragmentForYouBinding.bind( + inflater.inflate( + R.layout.fragment_for_you, + container, + false + ) + ) + + C = EarlyAccessCarouselController(this) + + val bundle = arguments + if (bundle != null) { + pageType = bundle.getInt(Constants.PAGE_TYPE, 0) + } + + when (pageType) { + 0 -> VM = ViewModelProvider(this).get(EarlyAccessAppsViewModel::class.java) + 1 -> VM = ViewModelProvider(this).get(EarlyAccessGamesViewModel::class.java) + } + + return B.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + B.recycler.setController(C) + + VM.liveData.observe(viewLifecycleOwner, { + when (it) { + is ViewState.Empty -> { + } + is ViewState.Loading -> { + } + is ViewState.Error -> { + } + is ViewState.Success<*> -> { + updateController(it.data as StreamBundle) + } + else -> { + + } + } + }) + + B.recycler.addOnScrollListener(object : EndlessRecyclerOnScrollListener() { + override fun onLoadMore(currentPage: Int) { + VM.observe() + } + }) + } + + private fun updateController(streamBundle: StreamBundle) { + C.setData(streamBundle) + } + + override fun onHeaderClicked(streamCluster: StreamCluster) { + if (streamCluster.clusterBrowseUrl.isNotEmpty()) + openStreamBrowseActivity(streamCluster.clusterBrowseUrl) + else + Toast.makeText(requireContext(), "Browse page unavailable", Toast.LENGTH_SHORT).show() + } + + override fun onClusterScrolled(streamCluster: StreamCluster) { + VM.observeCluster(streamCluster) + } + + override fun onAppClick(app: App) { + openDetailsActivity(app) + } + + override fun onAppLongClick(app: App) { + Toast.makeText(requireContext(), app.displayName, Toast.LENGTH_SHORT).show() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/commons/EditorChoiceFragment.kt b/app/src/main/java/com/aurora/store/view/ui/commons/EditorChoiceFragment.kt new file mode 100644 index 000000000..7452c9fde --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/commons/EditorChoiceFragment.kt @@ -0,0 +1,102 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.ui.commons + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.lifecycle.ViewModelProvider +import com.aurora.Constants +import com.aurora.gplayapi.data.models.editor.EditorChoiceBundle +import com.aurora.gplayapi.data.models.editor.EditorChoiceCluster +import com.aurora.store.R +import com.aurora.store.databinding.FragmentForYouBinding +import com.aurora.store.view.epoxy.controller.EditorChoiceController +import com.aurora.store.viewmodel.editorschoice.AppEditorChoiceViewModel +import com.aurora.store.viewmodel.editorschoice.BaseEditorChoiceViewModel +import com.aurora.store.viewmodel.editorschoice.GameEditorChoiceViewModel + +class EditorChoiceFragment : Fragment(), EditorChoiceController.Callbacks { + + private lateinit var B: FragmentForYouBinding + private lateinit var C: EditorChoiceController + private lateinit var VM: BaseEditorChoiceViewModel + + private var pageType = 0 + + companion object { + @JvmStatic + fun newInstance(pageType: Int): EditorChoiceFragment { + return EditorChoiceFragment().apply { + arguments = Bundle().apply { + putInt(Constants.PAGE_TYPE, pageType) + } + } + } + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + B = FragmentForYouBinding.bind( + inflater.inflate( + R.layout.fragment_for_you, + container, + false + ) + ) + + C = EditorChoiceController(this) + + val bundle = arguments + if (bundle != null) { + pageType = bundle.getInt(Constants.PAGE_TYPE, 0) + } + + when (pageType) { + 0 -> VM = ViewModelProvider(this).get(AppEditorChoiceViewModel::class.java) + 1 -> VM = ViewModelProvider(this).get(GameEditorChoiceViewModel::class.java) + } + + return B.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + B.recycler.setController(C) + + VM.liveData.observe(viewLifecycleOwner, { + updateController(it) + }) + } + + private fun updateController(editorChoiceBundles: List) { + C.setData(editorChoiceBundles) + } + + override fun onClick(editorChoiceCluster: EditorChoiceCluster) { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/commons/ForYouFragment.kt b/app/src/main/java/com/aurora/store/view/ui/commons/ForYouFragment.kt new file mode 100644 index 000000000..f27390f39 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/commons/ForYouFragment.kt @@ -0,0 +1,150 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.ui.commons + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Toast +import androidx.lifecycle.ViewModelProvider +import com.aurora.Constants +import com.aurora.gplayapi.data.models.App +import com.aurora.gplayapi.data.models.StreamBundle +import com.aurora.gplayapi.data.models.StreamCluster +import com.aurora.store.R +import com.aurora.store.data.ViewState +import com.aurora.store.databinding.FragmentForYouBinding +import com.aurora.store.view.custom.recycler.EndlessRecyclerOnScrollListener +import com.aurora.store.view.epoxy.controller.GenericCarouselController +import com.aurora.store.view.ui.sheets.AppPeekDialogSheet +import com.aurora.store.viewmodel.homestream.AppsForYouViewModel +import com.aurora.store.viewmodel.homestream.BaseClusterViewModel +import com.aurora.store.viewmodel.homestream.GamesForYouViewModel + + +class ForYouFragment : BaseFragment(), GenericCarouselController.Callbacks { + + private lateinit var B: FragmentForYouBinding + private lateinit var C: GenericCarouselController + private lateinit var VM: BaseClusterViewModel + + private lateinit var endlessRecyclerOnScrollListener: EndlessRecyclerOnScrollListener + + private var pageType = 0 + + companion object { + @JvmStatic + fun newInstance(pageType: Int): ForYouFragment { + return ForYouFragment().apply { + arguments = Bundle().apply { + putInt(Constants.PAGE_TYPE, pageType) + } + } + } + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + B = FragmentForYouBinding.bind( + inflater.inflate( + R.layout.fragment_for_you, + container, + false + ) + ) + + C = GenericCarouselController(this) + + val bundle = arguments + if (bundle != null) { + pageType = bundle.getInt(Constants.PAGE_TYPE, 0) + } + + when (pageType) { + 0 -> VM = + ViewModelProvider(requireActivity()).get(AppsForYouViewModel::class.java) + 1 -> VM = + ViewModelProvider(requireActivity()).get(GamesForYouViewModel::class.java) + } + + return B.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + B.recycler.setController(C) + + VM.liveData.observe(viewLifecycleOwner, { + when (it) { + is ViewState.Empty -> { + } + is ViewState.Loading -> { + updateController(null) + } + is ViewState.Error -> { + + } + is ViewState.Status -> { + + } + is ViewState.Success<*> -> { + updateController(it.data as StreamBundle) + } + } + }) + + endlessRecyclerOnScrollListener = + object : EndlessRecyclerOnScrollListener() { + override fun onLoadMore(currentPage: Int) { + VM.observe() + } + } + + B.recycler.addOnScrollListener(endlessRecyclerOnScrollListener) + } + + private fun updateController(streamBundle: StreamBundle?) { + C.setData(streamBundle) + } + + override fun onHeaderClicked(streamCluster: StreamCluster) { + if (streamCluster.clusterBrowseUrl.isNotEmpty()) + openStreamBrowseActivity(streamCluster.clusterBrowseUrl) + else + Toast.makeText(requireContext(), "Browse page unavailable", Toast.LENGTH_SHORT).show() + } + + override fun onClusterScrolled(streamCluster: StreamCluster) { + VM.observeCluster(streamCluster) + } + + override fun onAppClick(app: App) { + openDetailsActivity(app) + } + + override fun onAppLongClick(app: App) { + AppPeekDialogSheet.newInstance(app).show(parentFragmentManager, "APDS") + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/commons/StreamBrowseActivity.kt b/app/src/main/java/com/aurora/store/view/ui/commons/StreamBrowseActivity.kt new file mode 100644 index 000000000..1525cd0fc --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/commons/StreamBrowseActivity.kt @@ -0,0 +1,130 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.ui.commons + +import android.os.Bundle +import androidx.lifecycle.ViewModelProvider +import com.aurora.Constants +import com.aurora.gplayapi.data.models.StreamCluster +import com.aurora.store.databinding.ActivityGenericRecyclerBinding +import com.aurora.store.util.extensions.close +import com.aurora.store.view.custom.recycler.EndlessRecyclerOnScrollListener +import com.aurora.store.view.epoxy.views.AppListViewModel_ +import com.aurora.store.view.epoxy.views.AppProgressViewModel_ +import com.aurora.store.view.epoxy.views.shimmer.AppListViewShimmerModel_ +import com.aurora.store.viewmodel.browse.StreamBrowseViewModel + + +class StreamBrowseActivity : BaseActivity() { + + lateinit var B: ActivityGenericRecyclerBinding + lateinit var VM: StreamBrowseViewModel + + lateinit var endlessRecyclerOnScrollListener: EndlessRecyclerOnScrollListener + + lateinit var title: String + + override fun onConnected() { + hideNetworkConnectivitySheet() + } + + override fun onDisconnected() { + showNetworkConnectivitySheet() + } + + override fun onReconnected() { + + } + + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + B = ActivityGenericRecyclerBinding.inflate(layoutInflater) + VM = ViewModelProvider(this).get(StreamBrowseViewModel::class.java) + + setContentView(B.root) + + attachToolbar() + attachRecycler() + + VM.liveData.observe(this, { + updateController(it) + updateTitle(it) + }) + + intent.apply { + getStringExtra(Constants.BROWSE_EXTRA)?.let { + VM.getStreamBundle(it) + } + } + + updateController(null) + } + + private fun updateTitle(streamCluster: StreamCluster) { + B.layoutToolbarAction.txtTitle.text = streamCluster.clusterTitle + } + + private fun attachToolbar() { + B.layoutToolbarAction.imgActionPrimary.setOnClickListener { + close() + } + } + + private fun attachRecycler() { + endlessRecyclerOnScrollListener = object : EndlessRecyclerOnScrollListener() { + override fun onLoadMore(currentPage: Int) { + VM.nextCluster() + } + } + B.recycler.addOnScrollListener(endlessRecyclerOnScrollListener) + } + + private fun updateController(streamCluster: StreamCluster?) { + B.recycler.withModels { + setFilterDuplicates(true) + if (streamCluster == null) { + for (i in 1..6) { + add( + AppListViewShimmerModel_() + .id(i) + ) + } + } else { + streamCluster.clusterAppList.forEach { + add( + AppListViewModel_() + .id(it.packageName.hashCode()) + .app(it) + .click { _ -> openDetailsActivity(it) } + ) + } + + if (streamCluster.hasNext()) { + add( + AppProgressViewModel_() + .id("progress") + ) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/details/AppDetailsActivity.kt b/app/src/main/java/com/aurora/store/view/ui/details/AppDetailsActivity.kt new file mode 100644 index 000000000..bc170151e --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/details/AppDetailsActivity.kt @@ -0,0 +1,579 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.ui.details + +import android.Manifest +import android.content.ActivityNotFoundException +import android.content.Intent +import android.os.Build +import android.os.Bundle +import android.view.Menu +import android.view.MenuItem +import android.view.View +import android.widget.LinearLayout +import com.aurora.Constants +import com.aurora.gplayapi.data.models.App +import com.aurora.gplayapi.data.models.File +import com.aurora.gplayapi.helpers.AppDetailsHelper +import com.aurora.gplayapi.helpers.PurchaseHelper +import com.aurora.store.R +import com.aurora.store.data.downloader.DownloadManager +import com.aurora.store.data.downloader.RequestBuilder +import com.aurora.store.data.event.BusEvent +import com.aurora.store.data.installer.AppInstaller +import com.aurora.store.data.network.HttpClient +import com.aurora.store.data.providers.AuthProvider +import com.aurora.store.databinding.ActivityDetailsBinding +import com.aurora.store.util.* +import com.aurora.store.util.extensions.* +import com.aurora.store.view.ui.downloads.DownloadActivity +import com.bumptech.glide.load.resource.bitmap.RoundedCorners +import com.google.android.material.bottomsheet.BottomSheetBehavior +import com.google.android.material.bottomsheet.BottomSheetBehavior.BottomSheetCallback +import com.livinglifetechway.quickpermissions_kotlin.runWithPermissions +import com.tonyodev.fetch2.* +import com.tonyodev.fetch2core.DownloadBlock +import nl.komponents.kovenant.task +import nl.komponents.kovenant.ui.failUi +import nl.komponents.kovenant.ui.successUi +import org.greenrobot.eventbus.EventBus +import org.greenrobot.eventbus.Subscribe +import org.greenrobot.eventbus.ThreadMode +import java.util.* + +class AppDetailsActivity : BaseDetailsActivity() { + + private lateinit var B: ActivityDetailsBinding + private lateinit var bottomSheetBehavior: BottomSheetBehavior + + private lateinit var app: App + private lateinit var downloadManager: DownloadManager + private lateinit var fetch: Fetch + private lateinit var fetchGroupListener: FetchGroupListener + + private var isNone = false + private var status = Status.NONE + private var isInstalled: Boolean = false + + override fun onConnected() { + + } + + override fun onDisconnected() { + + } + + override fun onReconnected() { + + } + + override fun onStart() { + super.onStart() + EventBus.getDefault().register(this) + } + + override fun onStop() { + EventBus.getDefault().unregister(this) + super.onStop() + } + + @Subscribe(threadMode = ThreadMode.MAIN) + fun onEventMainThread(event: BusEvent) { + when (event) { + is BusEvent.InstallEvent -> { + attachActions() + } + is BusEvent.UninstallEvent -> { + attachActions() + } + else -> { + + } + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + B = ActivityDetailsBinding.inflate(layoutInflater) + setContentView(B.root) + + onNewIntent(intent) + } + + override fun onResume() { + if (!isLAndAbove()) { + checkAndSetupInstall() + } + super.onResume() + } + + override fun onNewIntent(intent: Intent) { + super.onNewIntent(intent) + val itemRaw: String? = intent.getStringExtra(Constants.STRING_EXTRA) + if (itemRaw != null) { + app = gson.fromJson(itemRaw, App::class.java) + isInstalled = PackageUtil.isInstalled(this, app.packageName) + + inflatePartialApp() + + fetchCompleteApp() + } else { + close() + } + } + + override fun onCreateOptionsMenu(menu: Menu?): Boolean { + menuInflater.inflate(R.menu.menu_details, menu) + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + android.R.id.home -> { + onBackPressed() + return true + } + R.id.action_share -> { + share(app) + return true + } + R.id.action_uninstall -> { + uninstallApp() + return true + } + R.id.menu_download_manager -> { + open(DownloadActivity::class.java) + return true + } + } + return super.onOptionsItemSelected(item) + } + + private fun attachToolbar() { + setSupportActionBar(B.layoutDetailsToolbar.toolbar) + val actionBar = supportActionBar + if (actionBar != null) { + actionBar.setDisplayShowCustomEnabled(true) + actionBar.setDisplayHomeAsUpEnabled(true) + actionBar.elevation = 0f + actionBar.title = "" + } + } + + private fun attachActions() { + flip(0) + checkAndSetupInstall() + } + + private fun openApp() { + val intent = PackageUtil.getLaunchIntent(this, app.packageName) + if (intent != null) { + try { + startActivity(intent) + } catch (e: ActivityNotFoundException) { + toast("Unable to open app") + } + } + } + + @Synchronized + private fun install(files: List) { + task { + AppInstaller.with(this) + .getPreferredInstaller() + .install( + app.packageName, + files + .filter { it.file.endsWith(".apk") } + .map { + it.file + }.toList() + ) + } + + runOnUiThread { + B.layoutDetailsInstall.btnDownload.setText(getString(R.string.action_installing)) + } + } + + @Synchronized + private fun uninstallApp() { + AppInstaller.with(this) + .getPreferredInstaller() + .uninstall(app.packageName) + } + + private fun attachWhiteListStatus() { + + } + + private fun fetchCompleteApp() { + task { + val authData = AuthProvider.with(this).getAuthData() + return@task AppDetailsHelper.with(authData) + .using(HttpClient.getPreferredClient()) + .getAppByPackageName(app.packageName) + } successUi { + inflateExtraDetails(it) + } failUi { + toast("Failed to fetch app details") + } + } + + private fun inflatePartialApp() { + attachWhiteListStatus() + attachHeader() + attachToolbar() + attachBottomSheet() + attachFetch() + attachActions() + } + + private fun attachHeader() { + B.layoutDetailsApp.apply { + imgIcon.load(app.iconArtwork.url) { + placeholder(R.drawable.bg_placeholder) + transform(RoundedCorners(32)) + } + + txtLine1.text = app.displayName + txtLine2.text = app.developerName + txtLine2.setOnClickListener { + NavigationUtil.openDevAppsActivity( + this@AppDetailsActivity, + app + ) + } + txtLine3.text = ("v${app.versionName}.${app.versionCode}") + + val tags = mutableListOf() + if (app.isFree) + tags.add(getString(R.string.details_free)) + else + tags.add(getString(R.string.details_paid)) + + if (app.containsAds) + tags.add(getString(R.string.details_contains_ads)) + else + tags.add(getString(R.string.details_no_ads)) + + txtLine4.text = tags.joinToString(separator = " • ") + } + } + + private fun inflateExtraDetails(app: App?) { + app?.let { + B.viewFlipper.displayedChild = 1 + inflateAppDescription(B.layoutDetailDescription, app) + inflateAppRatingAndReviews(B.layoutDetailsReview, app) + inflateAppDevInfo(B.layoutDetailsDev, app) + inflateAppPrivacy(B.layoutDetailsPrivacy, app) + } + } + + @Synchronized + private fun startDownload() { + when (status) { + Status.PAUSED -> { + fetch.resumeGroup(app.id) + isNone = false + } + Status.NONE, Status.CANCELLED -> { + fetch.deleteGroup(app.id) + isNone = true + } + Status.ADDED -> isNone = false + Status.DOWNLOADING -> { + isNone = false + flip(1) + toast("Already downloading") + } + Status.COMPLETED -> { + fetch.getFetchGroup(app.id) { + install(it.downloads) + } + } + else -> { + } + } + if (isNone) { + purchase() + } + } + + private fun purchase() { + task { + val authData = AuthProvider + .with(this) + .getAuthData() + + PurchaseHelper + .with(authData) + .using(HttpClient.getPreferredClient()) + .purchase(app.packageName, app.versionCode, app.offerType) + } successUi { + if (it.isNotEmpty()) { + enqueue(it) + } else { + Log.e("Failed to download : ${app.displayName}") + } + } failUi { + expandBottomSheet(it.message) + Log.e("Failed to purchase ${app.displayName} : ${it.message}") + } + } + + private fun enqueue(files: List) = runWithPermissions( + Manifest.permission.READ_EXTERNAL_STORAGE, + Manifest.permission.WRITE_EXTERNAL_STORAGE + ) { + val requestList = files + .filter { it.url.isNotEmpty() } + .map { + RequestBuilder.buildRequest(this, app, it) + } + .toList() + + if (requestList.isNotEmpty()) { + fetch.enqueue( + requestList + ) { + status = Status.ADDED + Log.i("Downloading Apks : %s", app.displayName) + } + } else { + expandBottomSheet(getString(R.string.purchase_no_file)) + } + } + + private fun updateProgress( + fetchGroup: FetchGroup, + etaInMilliSeconds: Long, + downloadedBytesPerSecond: Long + ) { + runOnUiThread { + val progress = if (fetchGroup.groupDownloadProgress > 0) + fetchGroup.groupDownloadProgress + else + 0 + + B.layoutDetailsInstall.apply { + txtProgressPercent.text = ("${progress}%") + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + progressDownload.setProgress(progress, true) + } else { + progressDownload.progress = progress + } + + txtEta.text = CommonUtil.getETAString( + this@AppDetailsActivity, + etaInMilliSeconds + ) + txtSpeed.text = + CommonUtil.getDownloadSpeedString( + this@AppDetailsActivity, + downloadedBytesPerSecond + ) + } + } + } + + private fun expandBottomSheet(message: String?) { + bottomSheetBehavior.isHideable = false + bottomSheetBehavior.state = BottomSheetBehavior.STATE_EXPANDED + + B.layoutDetailsInstall.txtPurchaseError.text = message + B.layoutDetailsInstall.btnDownload.updateProgress(false) + } + + private fun checkAndSetupInstall() { + isInstalled = PackageUtil.isInstalled(this, app.packageName) + + B.layoutDetailsInstall.btnDownload.let { btn -> + if (isInstalled) { + val isUpdatable = PackageUtil.isUpdatable( + this, + app.packageName, + app.versionCode.toLong() + ) + + if (isUpdatable) { + btn.setText(getString(R.string.action_update)) + btn.addOnClickListener { + btn.updateProgress(true) + startDownload() + } + } else { + btn.setText(getString(R.string.action_open)) + btn.addOnClickListener { openApp() } + } + } else { + if (app.isFree) { + btn.setText(getString(R.string.action_install)) + } else { + btn.setText(app.price) + } + + btn.addOnClickListener { + btn.setText(getString(R.string.download_metadata)) + btn.updateProgress(true) + startDownload() + } + } + + btn.updateProgress(false) + } + } + + @Synchronized + private fun flip(nextView: Int) { + runOnUiThread { + B.layoutDetailsInstall.viewFlipper.displayedChild = nextView + if (nextView == 0) + checkAndSetupInstall() + } + } + + private fun attachFetch() { + downloadManager = DownloadManager.with(this) + fetch = downloadManager.fetch + + fetch.getFetchGroup(app.id) { fetchGroup: FetchGroup -> + if (fetchGroup.groupDownloadProgress == 100 && fetchGroup.completedDownloads.isNotEmpty()) { + status = Status.COMPLETED + } else if (downloadManager.isDownloading(fetchGroup)) { + status = Status.DOWNLOADING + flip(1) + } else if (downloadManager.isCanceled(fetchGroup)) { + status = Status.CANCELLED + } else if (fetchGroup.pausedDownloads.isNotEmpty()) { + status = Status.PAUSED + } else { + status = Status.NONE + } + } + + fetchGroupListener = object : AbstractFetchGroupListener() { + + override fun onStarted( + groupId: Int, + download: Download, + downloadBlocks: List, + totalBlocks: Int, + fetchGroup: FetchGroup + ) { + if (groupId == app.id) { + status = download.status + flip(1) + } + } + + override fun onResumed(groupId: Int, download: Download, fetchGroup: FetchGroup) { + if (groupId == app.id) { + status = download.status + flip(1) + } + } + + override fun onPaused(groupId: Int, download: Download, fetchGroup: FetchGroup) { + if (groupId == app.id) { + status = download.status + flip(0) + } + } + + override fun onProgress( + groupId: Int, + download: Download, + etaInMilliSeconds: Long, + downloadedBytesPerSecond: Long, + fetchGroup: FetchGroup + ) { + if (groupId == app.id) { + updateProgress(fetchGroup, etaInMilliSeconds, downloadedBytesPerSecond) + Log.i( + "${app.displayName} : ${download.file} -> Progress : %d", + fetchGroup.groupDownloadProgress + ) + } + } + + override fun onCompleted(groupId: Int, download: Download, fetchGroup: FetchGroup) { + if (groupId == app.id && fetchGroup.groupDownloadProgress == 100) { + status = download.status + flip(0) + install(fetchGroup.downloads) + updateProgress(fetchGroup, -1, -1) + } + } + + override fun onCancelled(groupId: Int, download: Download, fetchGroup: FetchGroup) { + if (groupId == app.id) { + status = download.status + flip(0) + } + } + + override fun onError( + groupId: Int, + download: Download, + error: Error, + throwable: Throwable?, + fetchGroup: FetchGroup + ) { + if (groupId == app.id) { + status = download.status + flip(0) + } + } + } + + fetch.addListener(fetchGroupListener) + + B.layoutDetailsInstall.imgCancel.setOnClickListener { + fetch.cancelGroup( + app.id + ) + } + } + + private fun attachBottomSheet() { + B.layoutDetailsInstall.apply { + viewFlipper.setInAnimation(this@AppDetailsActivity, R.anim.fade_in) + viewFlipper.setOutAnimation(this@AppDetailsActivity, R.anim.fade_out) + } + + bottomSheetBehavior = BottomSheetBehavior.from(B.layoutDetailsInstall.bottomSheet) + bottomSheetBehavior.isDraggable = false + + bottomSheetBehavior.addBottomSheetCallback(object : BottomSheetCallback() { + override fun onStateChanged(bottomSheet: View, newState: Int) { + if (newState == BottomSheetBehavior.STATE_EXPANDED) { + bottomSheetBehavior.setDraggable(true) + } else if (newState == BottomSheetBehavior.STATE_COLLAPSED) { + bottomSheetBehavior.isDraggable = false + } + } + + override fun onSlide(bottomSheet: View, slideOffset: Float) {} + }) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/details/BaseDetailsActivity.kt b/app/src/main/java/com/aurora/store/view/ui/details/BaseDetailsActivity.kt new file mode 100644 index 000000000..fe651d72b --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/details/BaseDetailsActivity.kt @@ -0,0 +1,329 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.ui.details + +import android.app.ActivityOptions +import android.content.Intent +import android.os.Build +import android.text.Html +import android.text.Layout +import android.view.View +import android.widget.RelativeLayout +import android.widget.Toast +import androidx.core.content.ContextCompat +import androidx.core.text.HtmlCompat +import com.aurora.Constants +import com.aurora.gplayapi.data.models.App +import com.aurora.gplayapi.data.models.Review +import com.aurora.gplayapi.helpers.ReviewsHelper +import com.aurora.store.R +import com.aurora.store.data.model.ExodusReport +import com.aurora.store.data.model.Report +import com.aurora.store.data.network.HttpClient +import com.aurora.store.data.providers.AuthProvider +import com.aurora.store.databinding.LayoutDetailsDescriptionBinding +import com.aurora.store.databinding.LayoutDetailsDevBinding +import com.aurora.store.databinding.LayoutDetailsPrivacyBinding +import com.aurora.store.databinding.LayoutDetailsReviewBinding +import com.aurora.store.util.CommonUtil +import com.aurora.store.util.NavigationUtil +import com.aurora.store.util.extensions.toast +import com.aurora.store.view.custom.RatingView +import com.aurora.store.view.epoxy.views.* +import com.aurora.store.view.ui.commons.BaseActivity +import nl.komponents.kovenant.task +import nl.komponents.kovenant.ui.failUi +import nl.komponents.kovenant.ui.successUi +import org.json.JSONObject +import java.util.* + +abstract class BaseDetailsActivity : BaseActivity() { + + private val exodusBaseUrl = "https://reports.exodus-privacy.eu.org/api/search/" + private val exodusApiKey = "Token bbe6ebae4ad45a9cbacb17d69739799b8df2c7ae" + + //Sub Section Inflation + fun inflateAppDescription(B: LayoutDetailsDescriptionBinding, app: App) { + B.txtInstalls.text = CommonUtil.addDiPrefix(app.installs) + B.txtSize.text = CommonUtil.addSiPrefix(app.size) + B.txtRating.text = app.labeledRating + B.txtSdk.text = String.format("Target SDK %s", "${app.targetSdk}") + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + B.txtDescription.justificationMode = Layout.JUSTIFICATION_MODE_INTER_WORD + } + + B.txtDescription.text = Html.fromHtml(app.shortDescription) + + app.changes.apply { + if (isEmpty()) { + B.txtChangelog.text = getString(R.string.details_changelog_unavailable) + } else { + B.txtChangelog.text = Html.fromHtml(this) + } + } + + B.headerDescription.addClickListener { + openDetailsMoreActivity(app) + } + + B.epoxyRecycler.withModels { + setFilterDuplicates(true) + var position = 0 + app.screenshots + //.sortedWith { o1, o2 -> o2.height.compareTo(o1.height) } + .forEach { artwork -> + add( + ScreenshotViewModel_() + .id(artwork.url) + .artwork(artwork) + .position(position++) + .callback(object : ScreenshotView.ScreenshotCallback { + override fun onClick(position: Int) { + openScreenshotActivity(app, position) + } + }) + ) + } + } + } + + fun inflateAppRatingAndReviews(B: LayoutDetailsReviewBinding, app: App) { + B.averageRating.text = app.rating.average.toString() + B.txtReviewCount.text = app.rating.abbreviatedLabel + + var totalStars = 0L + totalStars += app.rating.oneStar + totalStars += app.rating.twoStar + totalStars += app.rating.threeStar + totalStars += app.rating.fourStar + totalStars += app.rating.fiveStar + + B.avgRatingLayout.apply { + removeAllViews() + addView(addAvgReviews(5, totalStars, app.rating.fiveStar)) + addView(addAvgReviews(4, totalStars, app.rating.fourStar)) + addView(addAvgReviews(3, totalStars, app.rating.threeStar)) + addView(addAvgReviews(2, totalStars, app.rating.twoStar)) + addView(addAvgReviews(1, totalStars, app.rating.oneStar)) + } + + B.averageRating.text = String.format(Locale.getDefault(), "%.1f", app.rating.average) + B.txtReviewCount.text = app.rating.abbreviatedLabel + + val authData = AuthProvider.with(this).getAuthData() + + B.btnPostReview.setOnClickListener { + if (authData.isAnonymous) { + toast(R.string.toast_anonymous_restriction) + } else { + addOrUpdateReview(B, app, Review().apply { + title = authData.userProfile!!.name + rating = B.userStars.rating.toInt() + comment = B.inputReview.text.toString() + }) + } + } + + B.headerRatingReviews.addClickListener { + openDetailsReviewActivity(app) + } + + task { + fetchFirstFewReviews(app) + } successUi { + B.epoxyRecycler.withModels { + it.take(4) + .forEach { + add( + ReviewViewModel_() + .id(it.timeStamp) + .review(it) + ) + } + } + } failUi { + + } + + } + + fun inflateAppPrivacy(B: LayoutDetailsPrivacyBinding, app: App) { + + task { + fetchReport(app.packageName) + } successUi { report -> + if (report.trackers.isNotEmpty()) { + B.txtStatus.apply { + setTextColor( + ContextCompat.getColor( + this@BaseDetailsActivity, + if (report.trackers.size > 4) + R.color.colorRed + else + R.color.colorOrange + ) + ) + text = + ("${report.trackers.size} ${getString(R.string.exodus_substring)} ${report.version}") + } + + B.headerPrivacy.addClickListener { + NavigationUtil.openExodusActivity(this, app, report) + } + } else { + B.txtStatus.apply { + setTextColor( + ContextCompat.getColor( + this@BaseDetailsActivity, + R.color.colorGreen + ) + ) + text = getString(R.string.exodus_no_tracker) + } + } + } failUi { + B.txtStatus.text = it.message + } + } + + fun inflateAppDevInfo(B: LayoutDetailsDevBinding, app: App) { + if (app.developerAddress.isNotEmpty()) { + B.devAddress.apply { + setTxtSubtitle( + HtmlCompat.fromHtml( + app.developerAddress, + HtmlCompat.FROM_HTML_MODE_LEGACY + ).toString() + ) + visibility = View.VISIBLE + } + } + + if (app.developerWebsite.isNotEmpty()) { + B.devWeb.apply { + setTxtSubtitle(app.developerWebsite) + visibility = View.VISIBLE + } + } + + if (app.developerEmail.isNotEmpty()) { + B.devWeb.apply { + setTxtSubtitle(app.developerEmail) + visibility = View.VISIBLE + } + } + } + + //Helpers + private fun openScreenshotActivity(app: App, position: Int) { + val intent = Intent( + this, + ScreenshotActivity::class.java + ).apply { + putExtra(Constants.STRING_EXTRA, gson.toJson(app.screenshots)) + putExtra(Constants.INT_EXTRA, position) + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + val options = + ActivityOptions.makeSceneTransitionAnimation(this) + startActivity(intent, options.toBundle()) + } else { + startActivity(intent) + } + } + + private fun addAvgReviews(number: Int, max: Long, rating: Long): RelativeLayout { + return RatingView(this, number, max.toInt(), rating.toInt()) + } + + private fun addOrUpdateReview( + B: LayoutDetailsReviewBinding, + app: App, + review: Review, + isBeta: Boolean = false + ) { + task { + val authData = AuthProvider.with(this).getAuthData() + ReviewsHelper + .with(authData) + .using(HttpClient.getPreferredClient()) + .addOrEditReview( + app.packageName, + review.title, + review.comment, + review.rating, + isBeta + ) + }.successUi { + it?.let { + B.userStars.rating = it.rating.toFloat() + Toast.makeText(this, "Rated successfully", Toast.LENGTH_SHORT).show() + } + }.failUi { + Toast.makeText(this, "Failed to submit rating", Toast.LENGTH_SHORT).show() + } + } + + private fun fetchFirstFewReviews(app: App): List { + val authData = AuthProvider + .with(this) + .getAuthData() + val reviewsHelper = ReviewsHelper + .with(authData) + .using(HttpClient.getPreferredClient()) + return reviewsHelper.getReviews(app.packageName, Review.Filter.CRITICAL) + } + + /*---------------------------------- HELPERS FOR APP PRIVACY ---------------------------------*/ + + private fun parseResponse(response: String, packageName: String): List { + try { + val jsonObject = JSONObject(response) + val exodusObject = jsonObject.getJSONObject(packageName) + val exodusReport: ExodusReport = gson.fromJson( + exodusObject.toString(), + ExodusReport::class.java + ) + return exodusReport.reports + } catch (e: Exception) { + throw Exception("No reports found") + } + } + + private fun fetchReport(packageName: String): Report { + val headers: MutableMap = mutableMapOf() + headers["Content-Type"] = "application/json" + headers["Accept"] = "application/json" + headers["Authorization"] = exodusApiKey + + val url = exodusBaseUrl + packageName + + val playResponse = HttpClient + .getPreferredClient() + .get(url, headers) + + if (playResponse.isSuccessful) { + return parseResponse(String(playResponse.responseBytes), packageName)[0] + } else { + throw Exception("Failed to fetch report") + } + } +} diff --git a/app/src/main/java/com/aurora/store/view/ui/details/DetailsExodusActivity.kt b/app/src/main/java/com/aurora/store/view/ui/details/DetailsExodusActivity.kt new file mode 100644 index 000000000..bc824178c --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/details/DetailsExodusActivity.kt @@ -0,0 +1,130 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.ui.details + +import android.os.Bundle +import com.aurora.Constants +import com.aurora.gplayapi.data.models.App +import com.aurora.store.R +import com.aurora.store.data.model.ExodusTracker +import com.aurora.store.data.model.Report +import com.aurora.store.data.providers.ExodusDataProvider +import com.aurora.store.databinding.ActivityGenericRecyclerBinding +import com.aurora.store.util.extensions.browse +import com.aurora.store.util.extensions.close +import com.aurora.store.view.epoxy.views.ExodusViewModel_ +import com.aurora.store.view.epoxy.views.HeaderViewModel_ +import com.aurora.store.view.ui.commons.BaseActivity +import org.json.JSONObject + +class DetailsExodusActivity : BaseActivity() { + + private lateinit var B: ActivityGenericRecyclerBinding + private lateinit var app: App + private lateinit var report: Report + + override fun onConnected() { + + } + + override fun onDisconnected() { + + } + + override fun onReconnected() { + + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + B = ActivityGenericRecyclerBinding.inflate(layoutInflater) + + setContentView(B.root) + + val rawApp: String? = intent.getStringExtra(Constants.STRING_APP) + val rawExodusTrackers: String? = intent.getStringExtra(Constants.STRING_EXTRA) + + if (rawApp != null) { + app = gson.fromJson(rawApp, App::class.java) + report = gson.fromJson( + rawExodusTrackers, + Report::class.java + ) + app.let { + attachToolbar() + report.let { + updateController(getExodusTrackersFromReport(report)) + } + } + } + } + + private fun attachToolbar() { + B.layoutToolbarAction.toolbar.setOnClickListener { + close() + } + B.layoutToolbarAction.txtTitle.text = app.displayName + } + + private fun updateController(reviews: List) { + B.recycler.withModels { + add( + HeaderViewModel_() + .id("header") + .title(getString(R.string.exodus_view_report)) + .browseUrl("browse") + .click { _ -> browse(Constants.EXODUS_REPORT_URL + report.id) } + ) + reviews.forEach { + add( + ExodusViewModel_() + .id(it.id) + .tracker(it) + .click { _ -> + browse(it.url) + } + ) + } + } + } + + private fun getExodusTrackersFromReport(report: Report): List { + val trackerObjects: List = fetchLocalTrackers(report.trackers) + return trackerObjects.map { + ExodusTracker().apply { + id = it.getInt("id") + name = it.getString("name") + url = it.getString("website") + signature = it.getString("code_signature") + date = it.getString("creation_date") + } + }.toList() + } + + private fun fetchLocalTrackers(trackerIds: List): List { + return try { + ExodusDataProvider + .with(this) + .getFilteredTrackers(trackerIds) + } catch (e: Exception) { + emptyList() + } + } +} diff --git a/app/src/main/java/com/aurora/store/view/ui/details/DetailsMoreActivity.kt b/app/src/main/java/com/aurora/store/view/ui/details/DetailsMoreActivity.kt new file mode 100644 index 000000000..399ec68fc --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/details/DetailsMoreActivity.kt @@ -0,0 +1,178 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.ui.details + +import android.os.Bundle +import androidx.core.text.HtmlCompat +import com.aurora.Constants +import com.aurora.gplayapi.data.models.App +import com.aurora.gplayapi.data.models.AuthData +import com.aurora.gplayapi.helpers.AppDetailsHelper +import com.aurora.store.R +import com.aurora.store.data.providers.AuthProvider +import com.aurora.store.databinding.ActivityDetailsMoreBinding +import com.aurora.store.util.extensions.close +import com.aurora.store.view.epoxy.views.FileViewModel_ +import com.aurora.store.view.epoxy.views.HeaderViewModel_ +import com.aurora.store.view.epoxy.views.app.NoAppAltViewModel_ +import com.aurora.store.view.epoxy.views.details.AppDependentViewModel_ +import com.aurora.store.view.epoxy.views.details.MoreBadgeViewModel_ +import com.aurora.store.view.ui.commons.BaseActivity +import nl.komponents.kovenant.task +import nl.komponents.kovenant.ui.failUi +import nl.komponents.kovenant.ui.successUi + +class DetailsMoreActivity : BaseActivity() { + + private lateinit var B: ActivityDetailsMoreBinding + private lateinit var app: App + + override fun onConnected() { + + } + + override fun onDisconnected() { + + } + + override fun onReconnected() { + + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + B = ActivityDetailsMoreBinding.inflate(layoutInflater) + setContentView(B.root) + + attachToolbar() + + val itemRaw: String? = intent.getStringExtra(Constants.STRING_EXTRA) + if (itemRaw != null) { + app = gson.fromJson(itemRaw, App::class.java) + app.let { + inflateDescription(app) + inflateFiles(app) + fetchDependentApps(app) + } + } + } + + private fun attachToolbar() { + B.layoutToolbarActionMore.toolbar.setOnClickListener { + close() + } + } + + private fun inflateDescription(app: App) { + B.layoutToolbarActionMore.txtTitle.text = app.displayName + B.txtDescription.text = HtmlCompat.fromHtml( + app.description, + HtmlCompat.FROM_HTML_MODE_COMPACT + ) + } + + private fun inflateFiles(app: App) { + B.recyclerMore.withModels { + //Add dependent files + if (app.fileList.isNotEmpty()) { + add( + HeaderViewModel_() + .id("badge_header") + .title("Files") + ) + + app.fileList.forEach { + add( + FileViewModel_() + .id(it.id) + .file(it) + ) + } + } + + //Add display & extra badges + if (app.infoBadges.isNotEmpty()) { + add( + HeaderViewModel_() + .id("badge_header") + .title("More") + ) + + app.infoBadges.forEach { + add( + MoreBadgeViewModel_() + .id(it.id) + .badge(it) + ) + } + + if (app.displayBadges.isNotEmpty()) { + app.displayBadges + .filter { it.textMajor.isNotEmpty() } + .forEach { + add( + MoreBadgeViewModel_() + .id(it.id) + .badge(it) + ) + } + } + } + } + } + + private fun fetchDependentApps(app: App) { + val authData: AuthData = AuthProvider + .with(this) + .getAuthData() + task { + AppDetailsHelper + .with(authData) + .getAppByPackageName(app.dependencies.dependentPackages) + } successUi { + B.recyclerDependency.withModels { + if (it.isNotEmpty()) { + it.filter { it.displayName.isNotEmpty() }.forEach { + add( + AppDependentViewModel_() + .id(it.id) + .app(it) + .click { _ -> openDetailsActivity(it) } + ) + } + } else { + add( + NoAppAltViewModel_() + .id("no_app") + .message(getString(R.string.details_no_dependencies)) + ) + } + } + } failUi { + B.recyclerDependency.withModels { + add( + NoAppAltViewModel_() + .id("no_app") + .message(getString(R.string.details_no_dependencies)) + ) + } + } + } +} diff --git a/app/src/main/java/com/aurora/store/view/ui/details/DetailsReviewActivity.kt b/app/src/main/java/com/aurora/store/view/ui/details/DetailsReviewActivity.kt new file mode 100644 index 000000000..8fec8e22e --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/details/DetailsReviewActivity.kt @@ -0,0 +1,150 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.ui.details + +import android.os.Bundle +import androidx.lifecycle.ViewModelProvider +import com.aurora.Constants +import com.aurora.gplayapi.data.models.App +import com.aurora.gplayapi.data.models.Review +import com.aurora.store.R +import com.aurora.store.data.ViewState +import com.aurora.store.databinding.ActivityDetailsReviewBinding +import com.aurora.store.util.extensions.close +import com.aurora.store.view.custom.recycler.EndlessRecyclerOnScrollListener +import com.aurora.store.view.epoxy.views.ReviewViewModel_ +import com.aurora.store.view.ui.commons.BaseActivity +import com.aurora.store.viewmodel.review.ReviewViewModel + +class DetailsReviewActivity : BaseActivity() { + + private lateinit var B: ActivityDetailsReviewBinding + private lateinit var VM: ReviewViewModel + private lateinit var endlessRecyclerOnScrollListener: EndlessRecyclerOnScrollListener + private lateinit var app: App + private lateinit var filter: Review.Filter + + override fun onConnected() { + + } + + override fun onDisconnected() { + + } + + override fun onReconnected() { + + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + B = ActivityDetailsReviewBinding.inflate(layoutInflater) + VM = ViewModelProvider(this).get(ReviewViewModel::class.java) + + setContentView(B.root) + + VM.liveData.observe(this, { + when (it) { + is ViewState.Empty -> { + } + is ViewState.Loading -> { + } + is ViewState.Error -> { + } + is ViewState.Success<*> -> { + updateController(it.data as List) + } + else -> { + + } + } + }) + + attachRecycler() + attachChips() + + val itemRaw: String? = intent.getStringExtra(Constants.STRING_EXTRA) + if (itemRaw != null) { + app = gson.fromJson(itemRaw, App::class.java) + filter = Review.Filter.ALL + + app.let { + attachToolbar() + fetchReviews() + } + } + } + + private fun attachToolbar() { + B.layoutToolbarActionReview.toolbar.setOnClickListener { + close() + } + B.layoutToolbarActionReview.txtTitle.text = app.displayName + } + + private fun attachChips() { + B.chipGroup.setOnCheckedChangeListener { group, checkedId -> + when (checkedId) { + R.id.filter_review_all -> filter = Review.Filter.ALL + R.id.filter_review_critical -> filter = Review.Filter.CRITICAL + R.id.filter_review_positive -> filter = Review.Filter.POSITIVE + R.id.filter_review_five -> filter = Review.Filter.FIVE + R.id.filter_review_four -> filter = Review.Filter.FOUR + R.id.filter_review_three -> filter = Review.Filter.THREE + R.id.filter_review_two -> filter = Review.Filter.TWO + R.id.filter_review_one -> filter = Review.Filter.ONE + } + + resetPage() + fetchReviews() + } + } + + private fun attachRecycler() { + endlessRecyclerOnScrollListener = object : EndlessRecyclerOnScrollListener() { + override fun onLoadMore(currentPage: Int) { + VM.fetchReview(app.packageName, filter) + } + } + B.recycler.addOnScrollListener(endlessRecyclerOnScrollListener) + } + + private fun updateController(reviews: List) { + B.recycler.withModels { + reviews.forEach { + add( + ReviewViewModel_() + .id(it.userName) + .review(it) + ) + } + } + } + + private fun fetchReviews() { + VM.fetchReview(app.packageName, filter) + } + + private fun resetPage() { + endlessRecyclerOnScrollListener.resetPageCount() + B.recycler.clear() + VM.reset() + } +} diff --git a/app/src/main/java/com/aurora/store/view/ui/details/DevAppsActivity.kt b/app/src/main/java/com/aurora/store/view/ui/details/DevAppsActivity.kt new file mode 100644 index 000000000..23e1f5b1c --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/details/DevAppsActivity.kt @@ -0,0 +1,127 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.ui.details + +import android.content.Intent +import android.os.Bundle +import android.view.View +import androidx.lifecycle.ViewModelProvider +import com.aurora.Constants +import com.aurora.gplayapi.data.models.App +import com.aurora.gplayapi.data.models.SearchBundle +import com.aurora.store.databinding.ActivityGenericRecyclerBinding +import com.aurora.store.util.extensions.close +import com.aurora.store.view.custom.recycler.EndlessRecyclerOnScrollListener +import com.aurora.store.view.epoxy.views.AppListViewModel_ +import com.aurora.store.view.epoxy.views.AppProgressViewModel_ +import com.aurora.store.view.ui.commons.BaseActivity +import com.aurora.store.viewmodel.search.SearchResultViewModel + +class DevAppsActivity : BaseActivity() { + + private lateinit var B: ActivityGenericRecyclerBinding + private lateinit var VM: SearchResultViewModel + + private lateinit var endlessRecyclerOnScrollListener: EndlessRecyclerOnScrollListener + private lateinit var app: App + + var searchBundle: SearchBundle = SearchBundle() + + override fun onConnected() { + hideNetworkConnectivitySheet() + } + + override fun onDisconnected() { + showNetworkConnectivitySheet() + } + + override fun onReconnected() { + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + B = ActivityGenericRecyclerBinding.inflate(layoutInflater) + VM = ViewModelProvider(this).get(SearchResultViewModel::class.java) + + setContentView(B.root) + + VM.liveData.observe(this, { + searchBundle = it + updateController(searchBundle) + }) + + attachRecycler() + + onNewIntent(intent) + } + + override fun onNewIntent(intent: Intent?) { + super.onNewIntent(intent) + intent?.let { + val rawApp: String? = intent.getStringExtra(Constants.STRING_APP) + app = gson.fromJson(rawApp, App::class.java) + app.let { + attachToolbar() + VM.observeSearchResults("pub:${app.developerName}") + } + } + } + + private fun attachToolbar() { + B.layoutToolbarAction.toolbar.setOnClickListener { + close() + } + B.layoutToolbarAction.txtTitle.text = app.developerName + } + + private fun attachRecycler() { + endlessRecyclerOnScrollListener = object : EndlessRecyclerOnScrollListener() { + override fun onLoadMore(currentPage: Int) { + VM.next(searchBundle.subBundles) + } + } + B.recycler.addOnScrollListener(endlessRecyclerOnScrollListener) + } + + private fun updateController(searchBundle: SearchBundle) { + B.recycler + .withModels { + setFilterDuplicates(true) + searchBundle.appList.forEach { app -> + add( + AppListViewModel_() + .id(app.id) + .app(app) + .click(View.OnClickListener { + openDetailsActivity(app) + }) + ) + } + + if (searchBundle.subBundles.isNotEmpty()) { + add( + AppProgressViewModel_() + .id("progress") + ) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/details/ScreenshotActivity.kt b/app/src/main/java/com/aurora/store/view/ui/details/ScreenshotActivity.kt new file mode 100644 index 000000000..003c5a543 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/details/ScreenshotActivity.kt @@ -0,0 +1,92 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.ui.details + +import android.os.Bundle +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.PagerSnapHelper +import androidx.recyclerview.widget.RecyclerView +import com.aurora.Constants +import com.aurora.gplayapi.data.models.Artwork +import com.aurora.store.databinding.ActivityScreenshotBinding +import com.aurora.store.util.extensions.close +import com.aurora.store.view.epoxy.views.LargeScreenshotViewModel_ +import com.aurora.store.view.ui.commons.BaseActivity +import com.google.gson.reflect.TypeToken + +class ScreenshotActivity : BaseActivity() { + + private lateinit var B: ActivityScreenshotBinding + private lateinit var artworks: MutableList + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + // ViewUtil.configureActivityLayout(this, false) + + B = ActivityScreenshotBinding.inflate(layoutInflater) + setContentView(B.root) + + attachRecycler() + + if (intent != null) { + val rawArtWorks = intent.getStringExtra(Constants.STRING_EXTRA).toString() + val position = intent.getIntExtra(Constants.INT_EXTRA, 0) + artworks = gson.fromJson(rawArtWorks, object : TypeToken?>() {}.type) + updateController(artworks, position) + } else { + close() + } + } + + private fun attachRecycler() { + B.recyclerView.apply { + layoutManager = LinearLayoutManager( + this@ScreenshotActivity, + RecyclerView.HORIZONTAL, + false + ) + PagerSnapHelper().attachToRecyclerView(this) + } + } + + private fun updateController(artworks: MutableList, position: Int) { + B.recyclerView.withModels { + artworks.forEach { + add( + LargeScreenshotViewModel_() + .id(it.url) + .artwork(it) + ) + } + B.recyclerView.scrollToPosition(position) + } + } + + override fun onConnected() { + + } + + override fun onDisconnected() { + } + + override fun onReconnected() { + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/downloads/DownloadActivity.kt b/app/src/main/java/com/aurora/store/view/ui/downloads/DownloadActivity.kt new file mode 100644 index 000000000..04a9c8f52 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/downloads/DownloadActivity.kt @@ -0,0 +1,243 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.ui.downloads + +import android.os.Bundle +import android.view.Menu +import android.view.MenuItem +import com.aurora.Constants +import com.aurora.gplayapi.data.models.App +import com.aurora.store.R +import com.aurora.store.data.downloader.DownloadManager +import com.aurora.store.data.model.DownloadFile +import com.aurora.store.databinding.ActivityDownloadBinding +import com.aurora.store.view.epoxy.views.DownloadViewModel_ +import com.aurora.store.view.epoxy.views.app.NoAppViewModel_ +import com.aurora.store.view.ui.commons.BaseActivity +import com.aurora.store.view.ui.sheets.DownloadMenuSheet +import com.tonyodev.fetch2.* + +class DownloadActivity : BaseActivity() { + + private lateinit var B: ActivityDownloadBinding + private lateinit var fetch: Fetch + + private var fetchListener: FetchListener = object : AbstractFetchListener() { + override fun onAdded(download: Download) { + updateDownloadsList() + } + + override fun onQueued(download: Download, waitingOnNetwork: Boolean) { + updateDownloadsList() + } + + override fun onCompleted(download: Download) { + updateDownloadsList() + } + + override fun onError(download: Download, error: Error, throwable: Throwable?) { + updateDownloadsList() + } + + override fun onProgress( + download: Download, + etaInMilliSeconds: Long, + downloadedBytesPerSecond: Long + ) { + updateDownloadsList() + } + + override fun onPaused(download: Download) { + updateDownloadsList() + } + + override fun onResumed(download: Download) { + updateDownloadsList() + } + + override fun onCancelled(download: Download) { + updateDownloadsList() + } + + override fun onRemoved(download: Download) { + updateDownloadsList() + } + + override fun onDeleted(download: Download) { + updateDownloadsList() + } + } + + override fun onConnected() { + + } + + override fun onDisconnected() { + + } + + override fun onReconnected() { + + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + B = ActivityDownloadBinding.inflate(layoutInflater) + setContentView(B.root) + + attachToolbar() + + fetch = DownloadManager.with(this).fetch + updateDownloadsList() + + B.swipeRefreshLayout.setOnRefreshListener { + updateDownloadsList() + } + } + + override fun onResume() { + super.onResume() + if (::fetch.isInitialized) + fetch.addListener(fetchListener) + } + + override fun onPause() { + if (::fetch.isInitialized) + fetch.removeListener(fetchListener) + super.onPause() + } + + override fun onDestroy() { + if (::fetch.isInitialized) + fetch.removeListener(fetchListener) + super.onDestroy() + } + + override fun onCreateOptionsMenu(menu: Menu?): Boolean { + menuInflater.inflate(R.menu.menu_download_main, menu) + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + android.R.id.home -> { + onBackPressed() + return true + } + R.id.action_pause_all -> { + fetch.pauseAll() + return true + } + R.id.action_resume_all -> { + fetch.resumeAll() + return true + } + R.id.action_cancel_all -> { + fetch.cancelAll() + return true + } + R.id.action_clear_completed -> { + fetch.removeAllWithStatus(Status.COMPLETED) + return true + } + R.id.action_force_clear_all -> { + fetch.deleteAll() + return true + } + } + return super.onOptionsItemSelected(item) + } + + private fun attachToolbar() { + setSupportActionBar(B.layoutToolbarAction.toolbar) + val actionBar = supportActionBar + if (actionBar != null) { + actionBar.setDisplayShowCustomEnabled(true) + actionBar.setDisplayHomeAsUpEnabled(true) + actionBar.elevation = 0f + actionBar.setTitle(R.string.title_download_manager) + } + } + + private fun updateDownloadsList() { + if (::fetch.isInitialized) + fetch.getDownloads { downloads -> + updateController( + downloads + .sortedWith { o1, o2 -> o2.created.compareTo(o1.created) } + .map { DownloadFile(it) } + ) + } + } + + private fun updateController(downloads: List) { + B.recycler.withModels { + if (downloads.isEmpty()) { + add( + NoAppViewModel_() + .id("no_downloads") + .message(getString(R.string.download_none)) + ) + } else { + downloads.forEach { + add( + DownloadViewModel_() + .id(it.download.id, it.download.progress, it.download.status.value) + .download(it) + .click { _ -> openDetailsActivity(it) } + .longClick { _ -> + openDownloadMenuSheet(it) + false + } + ) + } + } + } + B.swipeRefreshLayout.isRefreshing = false + } + + private fun openDetailsActivity(downloadFile: DownloadFile) { + val app: App = gson.fromJson( + downloadFile.download.extras.getString(Constants.STRING_EXTRA, "{}"), + App::class.java + ) + openDetailsActivity(app) + } + + private fun openDownloadMenuSheet(downloadFile: DownloadFile) { + with(downloadFile) { + val fragment = supportFragmentManager.findFragmentByTag(DownloadMenuSheet.TAG) + if (fragment != null) + supportFragmentManager.beginTransaction().remove(fragment) + + DownloadMenuSheet().apply { + arguments = Bundle().apply { + putInt(DownloadMenuSheet.DOWNLOAD_ID, download.id) + putInt(DownloadMenuSheet.DOWNLOAD_STATUS, download.status.value) + putString(DownloadMenuSheet.DOWNLOAD_URL, download.url) + } + }.show( + supportFragmentManager, + DownloadMenuSheet.TAG + ) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/games/GamesContainerFragment.kt b/app/src/main/java/com/aurora/store/view/ui/games/GamesContainerFragment.kt new file mode 100644 index 000000000..0850681bf --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/games/GamesContainerFragment.kt @@ -0,0 +1,109 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.ui.games + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentManager +import androidx.lifecycle.Lifecycle +import androidx.viewpager2.adapter.FragmentStateAdapter +import com.aurora.gplayapi.data.models.AuthData +import com.aurora.store.R +import com.aurora.store.data.providers.AuthProvider +import com.aurora.store.databinding.FragmentAppsGamesBinding +import com.aurora.store.view.ui.commons.CategoryFragment +import com.aurora.store.view.ui.commons.EarlyAccessFragment +import com.aurora.store.view.ui.commons.EditorChoiceFragment +import com.aurora.store.view.ui.commons.ForYouFragment +import com.google.android.material.tabs.TabLayout +import com.google.android.material.tabs.TabLayoutMediator + + +class GamesContainerFragment : Fragment() { + + private lateinit var B: FragmentAppsGamesBinding + private lateinit var authData: AuthData + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + B = FragmentAppsGamesBinding.bind( + inflater.inflate( + R.layout.fragment_apps_games, + container, + false + ) + ) + return B.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + authData = AuthProvider.with(requireContext()).getAuthData() + setupViewPager() + } + + private fun setupViewPager() { + B.pager.adapter = ViewPagerAdapter(childFragmentManager, lifecycle, authData.isAnonymous) + B.pager.isUserInputEnabled = false + + TabLayoutMediator(B.tabLayout, B.pager, true) { tab: TabLayout.Tab, position: Int -> + when (position) { + 0 -> tab.text = "For you" + 1 -> tab.text = "Top charts" + 2 -> tab.text = "Categories" + 3 -> tab.text = "Early access" + 4 -> tab.text = "Editor choice" + else -> { + } + } + }.attach() + } + + internal class ViewPagerAdapter( + fragment: FragmentManager, + lifecycle: Lifecycle, + private val isAnonymous: Boolean + ) : + FragmentStateAdapter(fragment, lifecycle) { + override fun createFragment(position: Int): Fragment { + return when (position) { + 0 -> ForYouFragment.newInstance(1) + 1 -> TopChartContainerFragment() + 2 -> CategoryFragment.newInstance(1) + 3 -> EarlyAccessFragment.newInstance(1) + 4 -> EditorChoiceFragment.newInstance(1) + else -> Fragment() + } + } + + override fun getItemCount(): Int { + return if (isAnonymous) + 3 + else + 5 + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/games/TopChartContainerFragment.kt b/app/src/main/java/com/aurora/store/view/ui/games/TopChartContainerFragment.kt new file mode 100644 index 000000000..10c1b2e9e --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/games/TopChartContainerFragment.kt @@ -0,0 +1,104 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.ui.games + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentManager +import androidx.lifecycle.Lifecycle +import androidx.viewpager2.adapter.FragmentStateAdapter +import androidx.viewpager2.widget.ViewPager2 +import com.aurora.gplayapi.data.models.AuthData +import com.aurora.store.R +import com.aurora.store.data.providers.AuthProvider +import com.aurora.store.databinding.FragmentTopChartBinding + + +class TopChartContainerFragment : Fragment() { + + private lateinit var B: FragmentTopChartBinding + + private lateinit var authData: AuthData + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + B = FragmentTopChartBinding.bind( + inflater.inflate( + R.layout.fragment_top_chart, + container, + false + ) + ) + return B.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + authData = AuthProvider.with(requireContext()).getAuthData() + setupViewPager() + } + + private fun setupViewPager() { + B.pager.adapter = ViewPagerAdapter(childFragmentManager, lifecycle) + B.topTabGroup.setOnCheckedChangeListener { group, checkedId -> + when (checkedId) { + R.id.tab_top_free -> B.pager.setCurrentItem(0, true) + R.id.tab_top_grossing -> B.pager.setCurrentItem(1, true) + R.id.tab_trending -> B.pager.setCurrentItem(2, true) + R.id.tab_top_paid -> B.pager.setCurrentItem(3, true) + } + } + + B.pager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() { + override fun onPageSelected(position: Int) { + super.onPageSelected(position) + when (position) { + 0 -> B.topTabGroup.check(R.id.tab_top_free) + 1 -> B.topTabGroup.check(R.id.tab_top_grossing) + 2 -> B.topTabGroup.check(R.id.tab_trending) + 3 -> B.topTabGroup.check(R.id.tab_top_paid) + } + } + }) + } + + internal class ViewPagerAdapter(fragment: FragmentManager, lifecycle: Lifecycle) : + FragmentStateAdapter(fragment, lifecycle) { + override fun createFragment(position: Int): Fragment { + return when (position) { + 0 -> TopChartFragment.newInstance(1, 0) + 1 -> TopChartFragment.newInstance(1, 1) + 2 -> TopChartFragment.newInstance(1, 2) + 3 -> TopChartFragment.newInstance(1, 3) + else -> Fragment() + } + } + + override fun getItemCount(): Int { + return 4 + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/games/TopChartFragment.kt b/app/src/main/java/com/aurora/store/view/ui/games/TopChartFragment.kt new file mode 100644 index 000000000..c9c284562 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/games/TopChartFragment.kt @@ -0,0 +1,136 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.ui.games + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.lifecycle.ViewModelProvider +import com.aurora.Constants +import com.aurora.gplayapi.data.models.StreamCluster +import com.aurora.store.R +import com.aurora.store.databinding.FragmentTopContainerBinding +import com.aurora.store.view.custom.recycler.EndlessRecyclerOnScrollListener +import com.aurora.store.view.epoxy.views.AppListViewModel_ +import com.aurora.store.view.epoxy.views.AppProgressViewModel_ +import com.aurora.store.view.epoxy.views.shimmer.AppListViewShimmerModel_ +import com.aurora.store.view.ui.commons.BaseFragment +import com.aurora.store.viewmodel.topchart.* + +class TopChartFragment : BaseFragment() { + + private lateinit var VM: BaseChartViewModel + private lateinit var B: FragmentTopContainerBinding + + private var chartType = 1 + private var chartCategory = 0 + + companion object { + @JvmStatic + fun newInstance(chartType: Int, chartCategory: Int): TopChartFragment { + return TopChartFragment().apply { + arguments = Bundle().apply { + putInt(Constants.TOP_CHART_TYPE, chartType) + putInt(Constants.TOP_CHART_CATEGORY, chartCategory) + } + } + } + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + B = FragmentTopContainerBinding.bind( + inflater.inflate( + R.layout.fragment_top_container, + container, + false + ) + ) + + val bundle = arguments + if (bundle != null) { + chartType = bundle.getInt(Constants.TOP_CHART_TYPE, 0) + chartCategory = bundle.getInt(Constants.TOP_CHART_CATEGORY, 0) + } + + when (chartCategory) { + 0 -> VM = + ViewModelProvider(requireActivity()).get(TopFreeGameChartViewModel::class.java) + 1 -> VM = + ViewModelProvider(requireActivity()).get(TopGrossingGameChartViewModel::class.java) + 2 -> VM = + ViewModelProvider(requireActivity()).get(TrendingGameChartViewModel::class.java) + 3 -> VM = + ViewModelProvider(requireActivity()).get(TopPaidGameChartViewModel::class.java) + } + + return B.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + VM.liveData.observe(viewLifecycleOwner, { + updateController(it) + }) + + B.recycler.addOnScrollListener(object : EndlessRecyclerOnScrollListener() { + override fun onLoadMore(currentPage: Int) { + VM.nextCluster() + } + }) + + updateController(null) + } + + private fun updateController(streamCluster: StreamCluster?) { + B.recycler.withModels { + setFilterDuplicates(true) + if (streamCluster == null) { + for (i in 1..6) { + add( + AppListViewShimmerModel_() + .id(i) + ) + } + } else { + streamCluster.clusterAppList.forEach { app -> + add( + AppListViewModel_() + .id(app.id) + .app(app) + .click { _ -> openDetailsActivity(app) } + ) + } + + if (streamCluster.hasNext()) { + add( + AppProgressViewModel_() + .id("progress") + ) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/onboarding/AccentFragment.kt b/app/src/main/java/com/aurora/store/view/ui/onboarding/AccentFragment.kt new file mode 100644 index 000000000..e7f71001c --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/onboarding/AccentFragment.kt @@ -0,0 +1,195 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.ui.onboarding + +import android.animation.Animator +import android.animation.AnimatorListenerAdapter +import android.graphics.Bitmap +import android.graphics.Canvas +import android.os.Build +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewAnimationUtils +import android.view.ViewGroup +import com.aurora.store.R +import com.aurora.store.data.model.Accent +import com.aurora.store.databinding.FragmentOnboardingAccentBinding +import com.aurora.store.util.Preferences +import com.aurora.store.util.Preferences.PREFERENCE_THEME_ACCENT +import com.aurora.store.util.Preferences.PREFERENCE_THEME_TYPE +import com.aurora.store.util.extensions.applyTheme +import com.aurora.store.util.extensions.hide +import com.aurora.store.util.extensions.isVisible +import com.aurora.store.util.extensions.show +import com.aurora.store.util.save +import com.aurora.store.view.custom.CubicBezierInterpolator +import com.aurora.store.view.epoxy.views.AccentViewModel_ +import com.aurora.store.view.ui.commons.BaseFragment +import com.gara.store.view.epoxy.controller.FlexLayoutManager +import com.google.android.flexbox.FlexDirection +import com.google.android.flexbox.FlexWrap +import com.google.android.flexbox.JustifyContent +import com.google.gson.reflect.TypeToken +import java.nio.charset.StandardCharsets +import kotlin.math.sqrt + + +class AccentFragment : BaseFragment() { + + private lateinit var B: FragmentOnboardingAccentBinding + + private var themeId: Int = 0 + private var accentId: Int = 0 + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + B = FragmentOnboardingAccentBinding.bind( + inflater.inflate( + R.layout.fragment_onboarding_accent, + container, + false + ) + ) + + return B.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + themeId = Preferences.getInteger( + requireContext(), + PREFERENCE_THEME_TYPE + ) + + accentId = Preferences.getInteger( + requireContext(), + PREFERENCE_THEME_ACCENT + ) + + attachRecycler() + + val accentList = loadAccentsFromAssets() + updateController(accentList) + } + + private fun attachRecycler() { + with(B.epoxyRecycler) { + layoutManager = FlexLayoutManager(requireContext()).apply { + justifyContent = JustifyContent.SPACE_BETWEEN + flexWrap = FlexWrap.WRAP + flexDirection = FlexDirection.ROW + } + } + } + + private fun updateController(accentList: List) { + B.epoxyRecycler.withModels { + setFilterDuplicates(true) + + accentList.forEach { + add( + AccentViewModel_() + .id(it.id) + .accent(it) + .markChecked(accentId == it.id) + .click { v -> + accentId = it.id + updateAccent(accentId) + requestModelBuild() + animate(v) + } + ) + } + } + } + + private fun updateAccent(accentId: Int) { + applyTheme(themeId, accentId, position = 3) + save(PREFERENCE_THEME_ACCENT, accentId) + } + + private fun animate(view: View) { + if (Build.VERSION.SDK_INT >= 21) { + if (B.themeSwitchImage.isVisible()) { + return; + } + try { + val pos = IntArray(2) + view.getLocationInWindow(pos) + val w: Int = B.root.measuredWidth + val h: Int = B.root.measuredHeight + + val bitmap = Bitmap.createBitmap( + B.root.measuredWidth, + B.root.measuredHeight, + Bitmap.Config.ARGB_8888 + ) + + val canvas = Canvas(bitmap) + B.root.draw(canvas) + B.themeSwitchImage.setImageBitmap(bitmap) + B.themeSwitchImage.show() + + val finalRadius = sqrt( + ((w - pos[0]) * (w - pos[0]) + (h - pos[1]) * (h - pos[1])).toDouble() + ).coerceAtLeast( + sqrt((pos[0] * pos[0] + (h - pos[1]) * (h - pos[1])).toDouble()) + ).toFloat() + + val anim: Animator = ViewAnimationUtils.createCircularReveal( + B.root, + pos[0], + pos[1], + 0f, + finalRadius + ) + + anim.duration = 450 + anim.interpolator = CubicBezierInterpolator.EASE_IN_OUT_QUAD + anim.addListener(object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator?) { + B.themeSwitchImage.setImageDrawable(null) + B.themeSwitchImage.hide() + } + }) + anim.start() + } catch (ignore: Throwable) { + } + } + } + + private fun loadAccentsFromAssets(): List { + val inputStream = requireContext().assets.open("accent.json") + val bytes = ByteArray(inputStream.available()) + inputStream.read(bytes) + inputStream.close() + + val json = String(bytes, StandardCharsets.UTF_8) + return gson.fromJson?>( + json, + object : TypeToken?>() {}.type + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/onboarding/InstallerFragment.kt b/app/src/main/java/com/aurora/store/view/ui/onboarding/InstallerFragment.kt new file mode 100644 index 000000000..666ef5a97 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/onboarding/InstallerFragment.kt @@ -0,0 +1,128 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.ui.onboarding + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.aurora.store.R +import com.aurora.store.data.model.Installer +import com.aurora.store.databinding.FragmentOnboardingInstallerBinding +import com.aurora.store.util.Preferences +import com.aurora.store.util.Preferences.PREFERENCE_INSTALLER_ID +import com.aurora.store.util.extensions.runOnUiThread +import com.aurora.store.util.extensions.toast +import com.aurora.store.util.save +import com.aurora.store.view.epoxy.views.preference.InstallerViewModel_ +import com.aurora.store.view.ui.commons.BaseFragment +import com.google.gson.reflect.TypeToken +import com.topjohnwu.superuser.Shell +import java.nio.charset.StandardCharsets + + +class InstallerFragment : BaseFragment() { + + private lateinit var B: FragmentOnboardingInstallerBinding + + var installerId: Int = 0 + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + B = FragmentOnboardingInstallerBinding.bind( + inflater.inflate( + R.layout.fragment_onboarding_installer, + container, + false + ) + ) + + return B.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + installerId = Preferences.getInteger( + requireContext(), + PREFERENCE_INSTALLER_ID + ) + + val installerList = loadInstallersFromAssets() + updateController(installerList) + } + + private fun updateController(installerList: List) { + B.epoxyRecycler.withModels { + setFilterDuplicates(true) + installerList.forEach { + add( + InstallerViewModel_() + .id(it.id) + .installer(it) + .markChecked(installerId == it.id) + .checked { _, checked -> + if (checked) { + installerId = it.id + save(installerId) + requestModelBuild() + } + } + ) + } + } + } + + private fun save(installerId: Int) { + if (installerId == 2) { + checkRoot() + } + save(PREFERENCE_INSTALLER_ID, installerId) + } + + private fun checkRoot() { + Shell.getShell { + runOnUiThread { + requireContext().toast( + if (it.isRoot) + getString(R.string.installer_root_available) + else + getString(R.string.installer_root_unavailable) + ) + } + } + } + + private fun loadInstallersFromAssets(): List { + val inputStream = requireContext().assets.open("installers.json") + val bytes = ByteArray(inputStream.available()) + inputStream.read(bytes) + inputStream.close() + + val json = String(bytes, StandardCharsets.UTF_8) + return gson.fromJson?>( + json, + object : TypeToken?>() {}.type + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/onboarding/OnboardingActivity.kt b/app/src/main/java/com/aurora/store/view/ui/onboarding/OnboardingActivity.kt new file mode 100644 index 000000000..b49754f5c --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/onboarding/OnboardingActivity.kt @@ -0,0 +1,145 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.ui.onboarding + +import android.content.Intent +import android.os.Bundle +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentActivity +import androidx.viewpager2.adapter.FragmentStateAdapter +import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback +import com.aurora.Constants +import com.aurora.store.R +import com.aurora.store.databinding.ActivityOnboardingBinding +import com.aurora.store.util.Preferences +import com.aurora.store.util.Preferences.PREFERENCE_INTRO +import com.aurora.store.util.extensions.open +import com.aurora.store.util.save +import com.aurora.store.view.ui.commons.BaseActivity +import com.aurora.store.view.ui.splash.SplashActivity +import com.google.android.material.tabs.TabLayoutMediator + +class OnboardingActivity : BaseActivity() { + + lateinit var B: ActivityOnboardingBinding + + override fun onConnected() {} + + override fun onDisconnected() {} + + override fun onReconnected() {} + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + val isIntroDone = Preferences.getBoolean(this, PREFERENCE_INTRO) + if (isIntroDone) + runOnUiThread { open(SplashActivity::class.java, true) } + + B = ActivityOnboardingBinding.inflate(layoutInflater) + + setContentView(B.root) + + attachViewPager() + + B.btnForward.setOnClickListener { + moveForward() + } + + B.btnBackward.setOnClickListener { + moveBackward() + } + + onNewIntent(intent) + } + + override fun onNewIntent(intent: Intent?) { + super.onNewIntent(intent) + intent?.let { + val pos = intent.getIntExtra(Constants.INT_EXTRA, 0) + B.viewpager2.setCurrentItem(pos, false) + + } + } + + private fun attachViewPager() { + B.viewpager2.adapter = PagerAdapter(this) + B.viewpager2.isUserInputEnabled = false + B.viewpager2.setCurrentItem(0, true) + B.viewpager2.registerOnPageChangeCallback(object : OnPageChangeCallback() { + override fun onPageSelected(position: Int) { + runOnUiThread { + B.btnBackward.isEnabled = position != 0 + if (position == 3) { + B.btnForward.text = getString(R.string.action_finish) + B.btnForward.setOnClickListener { + save(PREFERENCE_INTRO, true) + open(SplashActivity::class.java, true) + } + } else { + B.btnForward.text = getString(R.string.action_next) + B.btnForward.setOnClickListener { + B.viewpager2.setCurrentItem( + B.viewpager2.currentItem + 1, true + ) + } + } + } + } + }) + + TabLayoutMediator(B.tabLayout, B.viewpager2, true) { tab, position -> + tab.text = (position + 1).toString() + }.attach() + } + + private fun moveForward() { + B.viewpager2.setCurrentItem(B.viewpager2.currentItem + 1, true) + } + + private fun moveBackward() { + B.viewpager2.setCurrentItem(B.viewpager2.currentItem - 1, true) + } + + override fun onBackPressed() { + if (B.viewpager2.currentItem == 0) { + super.onBackPressed() + } else { + B.viewpager2.currentItem = B.viewpager2.currentItem - 1 + } + } + + internal class PagerAdapter(fragmentActivity: FragmentActivity) : + FragmentStateAdapter(fragmentActivity) { + override fun createFragment(position: Int): Fragment { + when (position) { + 0 -> return WelcomeFragment() + 1 -> return InstallerFragment() + 2 -> return ThemeFragment() + 3 -> return AccentFragment() + } + return Fragment() + } + + override fun getItemCount(): Int { + return 4 + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/onboarding/ThemeFragment.kt b/app/src/main/java/com/aurora/store/view/ui/onboarding/ThemeFragment.kt new file mode 100644 index 000000000..63783e452 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/onboarding/ThemeFragment.kt @@ -0,0 +1,180 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.ui.onboarding + +import android.animation.Animator +import android.animation.AnimatorListenerAdapter +import android.graphics.Bitmap +import android.graphics.Canvas +import android.os.Build +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewAnimationUtils +import android.view.ViewGroup +import com.aurora.store.R +import com.aurora.store.data.model.Theme +import com.aurora.store.databinding.FragmentOnboardingThemeBinding +import com.aurora.store.util.Preferences +import com.aurora.store.util.Preferences.PREFERENCE_THEME_ACCENT +import com.aurora.store.util.Preferences.PREFERENCE_THEME_TYPE +import com.aurora.store.util.extensions.applyTheme +import com.aurora.store.util.extensions.hide +import com.aurora.store.util.extensions.isVisible +import com.aurora.store.util.extensions.show +import com.aurora.store.util.save +import com.aurora.store.view.custom.CubicBezierInterpolator +import com.aurora.store.view.epoxy.views.ThemeViewModel_ +import com.aurora.store.view.ui.commons.BaseFragment +import com.google.gson.reflect.TypeToken +import java.nio.charset.StandardCharsets +import kotlin.math.sqrt + + +class ThemeFragment : BaseFragment() { + + private lateinit var B: FragmentOnboardingThemeBinding + + private var themeId: Int = 0 + private var accentId: Int = 0 + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + B = FragmentOnboardingThemeBinding.bind( + inflater.inflate( + R.layout.fragment_onboarding_theme, + container, + false + ) + ) + + return B.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + themeId = Preferences.getInteger( + requireContext(), + PREFERENCE_THEME_TYPE + ) + + accentId = Preferences.getInteger( + requireContext(), + PREFERENCE_THEME_ACCENT + ) + + val themeList = loadThemesFromAssets() + updateController(themeList) + } + + private fun updateController(themeList: List) { + B.epoxyRecycler.withModels { + setFilterDuplicates(true) + themeList.forEach { + add( + ThemeViewModel_() + .id(it.id) + .theme(it) + .markChecked(themeId == it.id) + .checked { v, checked -> + if (checked) { + themeId = it.id + update(themeId) + requestModelBuild() + animate(v) + } + } + ) + } + } + } + + private fun update(themeId: Int) { + applyTheme(themeId) + save(PREFERENCE_THEME_TYPE, themeId) + } + + private fun animate(view: View) { + if (Build.VERSION.SDK_INT >= 21) { + if (B.themeSwitchImage.isVisible()) { + return; + } + try { + val pos = IntArray(2) + view.getLocationInWindow(pos) + val w: Int = B.root.measuredWidth + val h: Int = B.root.measuredHeight + + val bitmap = Bitmap.createBitmap( + B.root.measuredWidth, + B.root.measuredHeight, + Bitmap.Config.ARGB_8888 + ) + + val canvas = Canvas(bitmap) + B.root.draw(canvas) + B.themeSwitchImage.setImageBitmap(bitmap) + B.themeSwitchImage.show() + + val finalRadius = sqrt( + ((w - pos[0]) * (w - pos[0]) + (h - pos[1]) * (h - pos[1])).toDouble() + ).coerceAtLeast( + sqrt((pos[0] * pos[0] + (h - pos[1]) * (h - pos[1])).toDouble()) + ).toFloat() + + val anim: Animator = ViewAnimationUtils.createCircularReveal( + B.root, + pos[0], + pos[1], + 0f, + finalRadius + ) + + anim.duration = 450 + anim.interpolator = CubicBezierInterpolator.EASE_IN_OUT_QUAD + anim.addListener(object : AnimatorListenerAdapter() { + override fun onAnimationEnd(animation: Animator?) { + B.themeSwitchImage.setImageDrawable(null) + B.themeSwitchImage.hide() + } + }) + anim.start() + } catch (ignore: Throwable) { + } + } + } + + private fun loadThemesFromAssets(): List { + val inputStream = requireContext().assets.open("themes.json") + val bytes = ByteArray(inputStream.available()) + inputStream.read(bytes) + inputStream.close() + + val json = String(bytes, StandardCharsets.UTF_8) + return gson.fromJson?>( + json, + object : TypeToken?>() {}.type + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/onboarding/WelcomeFragment.kt b/app/src/main/java/com/aurora/store/view/ui/onboarding/WelcomeFragment.kt new file mode 100644 index 000000000..fdbc4e99b --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/onboarding/WelcomeFragment.kt @@ -0,0 +1,104 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.ui.onboarding + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.aurora.store.R +import com.aurora.store.data.model.Dash +import com.aurora.store.databinding.FragmentOnboardingWelcomeBinding +import com.aurora.store.util.extensions.browse +import com.aurora.store.view.epoxy.views.preference.DashViewModel_ +import com.aurora.store.view.ui.commons.BaseFragment +import com.google.gson.reflect.TypeToken +import java.nio.charset.StandardCharsets + +class WelcomeFragment : BaseFragment() { + + private lateinit var B: FragmentOnboardingWelcomeBinding + + companion object { + @JvmStatic + fun newInstance(): WelcomeFragment { + return WelcomeFragment().apply { + + } + } + + val icMap: MutableMap = mutableMapOf( + "ic_faq" to R.drawable.ic_faq, + "ic_code" to R.drawable.ic_code, + "ic_license" to R.drawable.ic_license, + "ic_privacy" to R.drawable.ic_privacy, + "ic_disclaimer" to R.drawable.ic_disclaimer, + ) + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + B = FragmentOnboardingWelcomeBinding.bind( + inflater.inflate( + R.layout.fragment_onboarding_welcome, + container, + false + ) + ) + + return B.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + val dashList = loadDashFromAssets() + updateController(dashList) + } + + private fun updateController(dashList: List) { + B.epoxyRecycler.withModels { + setFilterDuplicates(true) + dashList.forEach { + add( + DashViewModel_() + .id(it.id) + .dash(it) + .click { _ -> requireContext().browse(it.url) } + ) + } + } + } + + private fun loadDashFromAssets(): List { + val inputStream = requireContext().assets.open("dash.json") + val bytes = ByteArray(inputStream.available()) + inputStream.read(bytes) + inputStream.close() + + val json = String(bytes, StandardCharsets.UTF_8) + return gson.fromJson?>( + json, + object : TypeToken?>() {}.type + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/preferences/DownloadPreference.kt b/app/src/main/java/com/aurora/store/view/ui/preferences/DownloadPreference.kt new file mode 100644 index 000000000..f1cb2a0c5 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/preferences/DownloadPreference.kt @@ -0,0 +1,31 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.ui.preferences + +import android.os.Bundle +import androidx.preference.PreferenceFragmentCompat +import com.aurora.store.R + + +class DownloadPreference : PreferenceFragmentCompat() { + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + setPreferencesFromResource(R.xml.preferences_download, rootKey) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/preferences/FilterPreference.kt b/app/src/main/java/com/aurora/store/view/ui/preferences/FilterPreference.kt new file mode 100644 index 000000000..827234e2f --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/preferences/FilterPreference.kt @@ -0,0 +1,30 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.ui.preferences + +import android.os.Bundle +import androidx.preference.PreferenceFragmentCompat +import com.aurora.store.R + +class FilterPreference : PreferenceFragmentCompat() { + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + setPreferencesFromResource(R.xml.preferences_filter, rootKey) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/preferences/InstallationPreference.kt b/app/src/main/java/com/aurora/store/view/ui/preferences/InstallationPreference.kt new file mode 100644 index 000000000..5244ebdc7 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/preferences/InstallationPreference.kt @@ -0,0 +1,54 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.ui.preferences + +import android.os.Bundle +import android.view.View +import androidx.preference.Preference +import androidx.preference.PreferenceFragmentCompat +import com.aurora.store.R +import com.aurora.store.util.CommonUtil +import com.aurora.store.util.Preferences +import com.aurora.store.util.extensions.runOnUiThread +import com.aurora.store.util.extensions.toast + + +class InstallationPreference : PreferenceFragmentCompat() { + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + setPreferencesFromResource(R.xml.preferences_installation, rootKey) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + val abandonPreference: Preference? = + findPreference(Preferences.INSTALLATION_ABANDON_SESSION) + abandonPreference?.let { + it.onPreferenceClickListener = + Preference.OnPreferenceClickListener { + CommonUtil.cleanupInstallationSessions(requireContext()) + runOnUiThread { + requireContext().toast(R.string.toast_abandon_sessions) + } + false + } + } + } +} diff --git a/app/src/main/java/com/aurora/store/view/ui/preferences/MainPreference.kt b/app/src/main/java/com/aurora/store/view/ui/preferences/MainPreference.kt new file mode 100644 index 000000000..640584b4b --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/preferences/MainPreference.kt @@ -0,0 +1,31 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.ui.preferences + +import android.os.Bundle +import androidx.preference.PreferenceFragmentCompat +import com.aurora.store.R + + +class MainPreference : PreferenceFragmentCompat() { + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + setPreferencesFromResource(R.xml.preferences_main, rootKey) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/preferences/SettingsActivity.kt b/app/src/main/java/com/aurora/store/view/ui/preferences/SettingsActivity.kt new file mode 100644 index 000000000..94a5d39ea --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/preferences/SettingsActivity.kt @@ -0,0 +1,152 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.ui.preferences + +import android.graphics.drawable.ColorDrawable +import android.os.Bundle +import android.view.MenuItem +import androidx.preference.Preference +import androidx.preference.PreferenceFragmentCompat +import com.aurora.store.R +import com.aurora.store.databinding.ActivitySettingBinding +import com.aurora.store.util.ViewUtil.getStyledAttribute +import com.aurora.store.util.extensions.restartApp +import com.aurora.store.view.ui.commons.BaseActivity +import com.google.android.material.dialog.MaterialAlertDialogBuilder + +class SettingsActivity : BaseActivity(), + PreferenceFragmentCompat.OnPreferenceStartFragmentCallback { + + private lateinit var B: ActivitySettingBinding + + companion object { + var shouldRestart = false + const val titleTag = "titleTag" + } + + override fun onConnected() { + } + + override fun onDisconnected() { + } + + override fun onReconnected() { + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + B = ActivitySettingBinding.inflate(layoutInflater) + setContentView(B.root) + + if (savedInstanceState == null) { + supportFragmentManager + .beginTransaction() + .replace(R.id.settings, MainPreference()) + .commit() + } else { + title = savedInstanceState.getCharSequence(titleTag) + } + + supportFragmentManager.addOnBackStackChangedListener { + if (supportFragmentManager.backStackEntryCount == 0) { + B.layoutToolbarAction.toolbar.setTitle(R.string.title_settings) + if (shouldRestart) + askRestart() + } + } + + attachToolbar() + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + android.R.id.home -> { + onBackPressed() + return true + } + } + return super.onOptionsItemSelected(item) + } + + private fun attachToolbar() { + setSupportActionBar(B.layoutToolbarAction.toolbar) + val actionBar = supportActionBar + if (actionBar != null) { + actionBar.setDisplayShowCustomEnabled(true) + actionBar.setDisplayHomeAsUpEnabled(true) + actionBar.elevation = 0f + actionBar.setTitle(R.string.title_settings) + } + } + + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + outState.putCharSequence(titleTag, title) + } + + override fun onSupportNavigateUp(): Boolean { + if (supportFragmentManager.popBackStackImmediate()) { + return true + } + return super.onSupportNavigateUp() + } + + override fun onPreferenceStartFragment( + caller: PreferenceFragmentCompat, + preference: Preference + ): Boolean { + with(supportFragmentManager) { + val args = preference.extras + + val fragment = fragmentFactory.instantiate( + classLoader, + preference.fragment + ).apply { + arguments = args + setTargetFragment(caller, 0) + } + + beginTransaction() + .replace(R.id.settings, fragment) + .addToBackStack(null) + .commit() + + B.layoutToolbarAction.toolbar.title = preference.title + } + + return true + } + + private fun askRestart() { + val builder = MaterialAlertDialogBuilder(this) + .setTitle(getString(R.string.action_restart)) + .setMessage(getString(R.string.pref_dialog_to_apply_restart)) + .setPositiveButton(getString(R.string.action_restart)) { _, _ -> + shouldRestart = false + restartApp() + } + .setNegativeButton(getString(R.string.action_later)) { dialog, _ -> dialog.dismiss() } + val backGroundColor = getStyledAttribute(this, android.R.attr.colorBackground) + builder.background = ColorDrawable(backGroundColor) + builder.create() + builder.show() + } +} diff --git a/app/src/main/java/com/aurora/store/view/ui/preferences/UIPreference.kt b/app/src/main/java/com/aurora/store/view/ui/preferences/UIPreference.kt new file mode 100644 index 000000000..cd353a2a1 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/preferences/UIPreference.kt @@ -0,0 +1,76 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.ui.preferences + +import android.os.Bundle +import android.view.View +import androidx.preference.ListPreference +import androidx.preference.PreferenceFragmentCompat +import com.aurora.store.R +import com.aurora.store.util.Preferences +import com.aurora.store.util.extensions.applyTheme +import com.aurora.store.util.save + + +class UIPreference : PreferenceFragmentCompat() { + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { + setPreferencesFromResource(R.xml.preferences_ui, rootKey) + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + val themePreference: ListPreference? = findPreference(Preferences.PREFERENCE_THEME_TYPE) + themePreference?.let { + it.setOnPreferenceChangeListener { _, newValue -> + val themeId = Integer.parseInt(newValue.toString()) + val accentId = Preferences.getInteger( + requireContext(), + Preferences.PREFERENCE_THEME_ACCENT + ) + + save(Preferences.PREFERENCE_THEME_TYPE, themeId) + + applyTheme(themeId, accentId, shouldApplyTransition = false) + + SettingsActivity.shouldRestart = true + true + } + } + + val accentPreference: ListPreference? = findPreference(Preferences.PREFERENCE_THEME_ACCENT) + accentPreference?.let { + it.setOnPreferenceChangeListener { _, newValue -> + val themeId = Preferences.getInteger( + requireContext(), + Preferences.PREFERENCE_THEME_TYPE + ) + val accentId = Integer.parseInt(newValue.toString()) + + save(Preferences.PREFERENCE_THEME_ACCENT, accentId) + + applyTheme(themeId, accentId, shouldApplyTransition = false) + + SettingsActivity.shouldRestart = true + true + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/sale/AppSalesActivity.kt b/app/src/main/java/com/aurora/store/view/ui/sale/AppSalesActivity.kt new file mode 100644 index 000000000..521431f96 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/sale/AppSalesActivity.kt @@ -0,0 +1,121 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.ui.sale + +import android.os.Bundle +import androidx.lifecycle.ViewModelProvider +import com.aurora.gplayapi.data.models.App +import com.aurora.store.R +import com.aurora.store.databinding.ActivityGenericRecyclerBinding +import com.aurora.store.util.extensions.close +import com.aurora.store.view.custom.recycler.EndlessRecyclerOnScrollListener +import com.aurora.store.view.epoxy.views.AppListViewModel_ +import com.aurora.store.view.epoxy.views.AppProgressViewModel_ +import com.aurora.store.view.epoxy.views.shimmer.AppListViewShimmerModel_ +import com.aurora.store.view.ui.commons.BaseActivity +import com.aurora.store.viewmodel.sale.AppSalesViewModel + + +class AppSalesActivity : BaseActivity() { + + lateinit var B: ActivityGenericRecyclerBinding + lateinit var VM: AppSalesViewModel + + lateinit var endlessRecyclerOnScrollListener: EndlessRecyclerOnScrollListener + + override fun onConnected() { + hideNetworkConnectivitySheet() + } + + override fun onDisconnected() { + showNetworkConnectivitySheet() + } + + override fun onReconnected() { + + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + B = ActivityGenericRecyclerBinding.inflate(layoutInflater) + VM = ViewModelProvider(this).get(AppSalesViewModel::class.java) + + setContentView(B.root) + + attachRecycler() + attachToolbar() + + VM.liveAppList.observe(this, { + updateController(it) + }) + } + + private fun attachToolbar() { + B.layoutToolbarAction.txtTitle.text = getString(R.string.title_apps_sale) + B.layoutToolbarAction.imgActionPrimary.setOnClickListener { + close() + } + } + + private fun attachRecycler() { + endlessRecyclerOnScrollListener = object : EndlessRecyclerOnScrollListener() { + override fun onLoadMore(currentPage: Int) { + VM.next() + } + } + B.recycler.addOnScrollListener(endlessRecyclerOnScrollListener) + updateController(null) + } + + private fun updateController(appList: List?) { + B.recycler + .withModels { + setFilterDuplicates(true) + if (appList == null) { + for (i in 1..6) { + add( + AppListViewShimmerModel_() + .id(i) + ) + } + } else { + appList + .filter { it.packageName.isNotEmpty() } + .forEach { + add( + AppListViewModel_() + .id(it.packageName.hashCode()) + .app(it) + .click { _ -> openDetailsActivity(it) } + ) + setFilterDuplicates(true) + } + + if (appList.isNotEmpty()) { + add( + AppProgressViewModel_() + .id("progress") + ) + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/search/SearchResultsActivity.kt b/app/src/main/java/com/aurora/store/view/ui/search/SearchResultsActivity.kt new file mode 100644 index 000000000..3b8f89d52 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/search/SearchResultsActivity.kt @@ -0,0 +1,176 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.ui.search + +import android.os.Bundle +import android.text.Editable +import android.text.TextWatcher +import android.view.KeyEvent +import android.view.View +import android.view.inputmethod.EditorInfo +import android.widget.TextView +import androidx.lifecycle.ViewModelProvider +import com.aurora.Constants +import com.aurora.gplayapi.data.models.SearchBundle +import com.aurora.store.databinding.ActivitySearchResultBinding +import com.aurora.store.util.extensions.close +import com.aurora.store.util.extensions.open +import com.aurora.store.view.custom.recycler.EndlessRecyclerOnScrollListener +import com.aurora.store.view.epoxy.views.AppListViewModel_ +import com.aurora.store.view.epoxy.views.AppProgressViewModel_ +import com.aurora.store.view.ui.commons.BaseActivity +import com.aurora.store.view.ui.downloads.DownloadActivity +import com.aurora.store.viewmodel.search.SearchResultViewModel +import com.google.android.material.textfield.TextInputEditText + + +class SearchResultsActivity : BaseActivity() { + + lateinit var B: ActivitySearchResultBinding + lateinit var VM: SearchResultViewModel + + lateinit var endlessRecyclerOnScrollListener: EndlessRecyclerOnScrollListener + lateinit var searchView: TextInputEditText + + var query: String? = null + var searchBundle: SearchBundle = SearchBundle() + + override fun onConnected() { + hideNetworkConnectivitySheet() + } + + override fun onDisconnected() { + showNetworkConnectivitySheet() + } + + override fun onReconnected() { + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + B = ActivitySearchResultBinding.inflate(layoutInflater) + VM = ViewModelProvider(this).get(SearchResultViewModel::class.java) + + setContentView(B.root) + + VM.liveData.observe(this, { + searchBundle = it + updateController(searchBundle) + }) + + attachToolbar() + attachSearch() + attachRecycler() + + query = intent.getStringExtra(Constants.STRING_EXTRA) + query?.let { + updateQuery(it) + } + } + + private fun attachToolbar() { + searchView = B.layoutViewToolbar.inputSearch + + B.layoutViewToolbar.imgActionPrimary.setOnClickListener { + close() + } + B.layoutViewToolbar.imgActionSecondary.setOnClickListener { + open(DownloadActivity::class.java) + } + } + + private fun attachRecycler() { + endlessRecyclerOnScrollListener = object : EndlessRecyclerOnScrollListener() { + override fun onLoadMore(currentPage: Int) { + VM.next(searchBundle.subBundles) + } + } + B.recycler.addOnScrollListener(endlessRecyclerOnScrollListener) + } + + private fun updateController(searchBundle: SearchBundle) { + B.recycler + .withModels { + setFilterDuplicates(true) + searchBundle.appList.forEach { app -> + add( + AppListViewModel_() + .id(app.id) + .app(app) + .click(View.OnClickListener { + openDetailsActivity(app) + }) + ) + } + + if (searchBundle.subBundles.isNotEmpty()) { + add( + AppProgressViewModel_() + .id("progress") + ) + } + } + } + + private fun attachSearch() { + searchView.addTextChangedListener(object : TextWatcher { + override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) { + + } + + override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { + if (s.isNotEmpty()) { + /*val query = s.toString() + if (query.isNotEmpty()) { + VM.observeSearchResults(query) + }*/ + } + } + + override fun afterTextChanged(s: Editable) {} + }) + + searchView.setOnEditorActionListener { _: TextView?, actionId: Int, _: KeyEvent? -> + if (actionId == EditorInfo.IME_ACTION_SEARCH) { + val query = searchView.text.toString() + if (query.isNotEmpty()) { + queryViewModel(query) + return@setOnEditorActionListener true + } + } + false + } + } + + private fun updateQuery(query: String) { + searchView.text = Editable.Factory.getInstance().newEditable(query) + searchView.setSelection(query.length) + queryViewModel(query) + } + + private fun queryViewModel(query: String) { + endlessRecyclerOnScrollListener.resetPageCount() + B.recycler.clear() + searchBundle.subBundles.clear() + searchBundle.appList.clear() + VM.observeSearchResults(query) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/search/SearchSuggestionActivity.kt b/app/src/main/java/com/aurora/store/view/ui/search/SearchSuggestionActivity.kt new file mode 100644 index 000000000..b91d96b0e --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/search/SearchSuggestionActivity.kt @@ -0,0 +1,159 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.ui.search + +import android.content.Intent +import android.os.Bundle +import android.text.Editable +import android.text.TextWatcher +import android.view.KeyEvent +import android.view.inputmethod.EditorInfo +import android.widget.TextView +import androidx.lifecycle.ViewModelProvider +import com.aurora.Constants +import com.aurora.gplayapi.SearchSuggestEntry +import com.aurora.store.databinding.ActivitySearchSuggestionBinding +import com.aurora.store.util.ViewUtil +import com.aurora.store.util.extensions.close +import com.aurora.store.util.extensions.open +import com.aurora.store.util.extensions.showKeyboard +import com.aurora.store.view.epoxy.views.SearchSuggestionViewModel_ +import com.aurora.store.view.ui.commons.BaseActivity +import com.aurora.store.view.ui.downloads.DownloadActivity +import com.aurora.store.viewmodel.search.SearchSuggestionViewModel +import com.google.android.material.textfield.TextInputEditText + + +class SearchSuggestionActivity : BaseActivity() { + + lateinit var B: ActivitySearchSuggestionBinding + lateinit var VM: SearchSuggestionViewModel + + lateinit var searchView: TextInputEditText + + var query: String = String() + + override fun onConnected() { + hideNetworkConnectivitySheet() + } + + override fun onDisconnected() { + showNetworkConnectivitySheet() + } + + override fun onReconnected() { + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + B = ActivitySearchSuggestionBinding.inflate(layoutInflater) + VM = ViewModelProvider(this).get(SearchSuggestionViewModel::class.java) + + setContentView(B.root) + + attachToolbar() + + VM.liveSearchSuggestions.observe(this, { + updateController(it) + }) + + setupSearch() + } + + override fun onResume() { + super.onResume() + if (::searchView.isInitialized) { + searchView.showKeyboard() + } + } + + private fun attachToolbar() { + searchView = B.layoutToolbarSearch.inputSearch + + B.layoutToolbarSearch.imgActionPrimary.setOnClickListener { + close() + } + B.layoutToolbarSearch.imgActionSecondary.setOnClickListener { + open(DownloadActivity::class.java) + } + } + + private fun updateController(searchSuggestions: List) { + B.recycler.withModels { + setFilterDuplicates(true) + searchSuggestions.forEach { + add( + SearchSuggestionViewModel_() + .id(it.title) + .entry(it) + .action { _ -> + updateQuery(it.title) + } + .click { _ -> + search(it.title) + } + ) + } + } + } + + private fun setupSearch() { + searchView.addTextChangedListener(object : TextWatcher { + override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {} + + override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) { + if (s.isNotEmpty()) { + query = s.toString() + if (query.isNotEmpty()) { + VM.observeStreamBundles(query) + } + } + } + + override fun afterTextChanged(s: Editable) {} + }) + + searchView.setOnEditorActionListener { _: TextView?, actionId: Int, _: KeyEvent? -> + if (actionId == EditorInfo.IME_ACTION_SEARCH) { + query = searchView.text.toString() + if (query.isNotEmpty()) { + search(query) + return@setOnEditorActionListener true + } + } + false + } + } + + private fun updateQuery(query: String) { + searchView.text = Editable.Factory.getInstance().newEditable(query) + searchView.setSelection(query.length) + } + + private fun search(query: String) { + val intent = Intent(this, SearchResultsActivity::class.java) + intent.putExtra(Constants.STRING_EXTRA, query) + startActivity( + intent, + ViewUtil.getEmptyActivityBundle(this) + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/sheets/AppMenuSheet.kt b/app/src/main/java/com/aurora/store/view/ui/sheets/AppMenuSheet.kt new file mode 100644 index 000000000..f3d93f60d --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/sheets/AppMenuSheet.kt @@ -0,0 +1,143 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.ui.sheets + +import android.Manifest +import android.os.Bundle +import android.view.LayoutInflater +import android.view.MenuItem +import android.view.View +import android.view.ViewGroup +import com.aurora.Constants +import com.aurora.gplayapi.data.models.App +import com.aurora.store.R +import com.aurora.store.data.event.BusEvent +import com.aurora.store.data.installer.AppInstaller +import com.aurora.store.data.providers.BlacklistProvider +import com.aurora.store.databinding.SheetAppMenuBinding +import com.aurora.store.util.ApkCopier +import com.aurora.store.util.PackageUtil +import com.aurora.store.util.extensions.isQAndAbove +import com.aurora.store.util.extensions.openInfo +import com.aurora.store.util.extensions.toast +import com.livinglifetechway.quickpermissions_kotlin.runWithPermissions +import nl.komponents.kovenant.task +import org.greenrobot.eventbus.EventBus + +class AppMenuSheet : BaseBottomSheet() { + + private lateinit var B: SheetAppMenuBinding + private lateinit var app: App + + override fun onCreateContentView( + inflater: LayoutInflater, + container: ViewGroup, + savedInstanceState: Bundle? + ): View { + B = SheetAppMenuBinding.inflate(layoutInflater) + return B.root + } + + override fun onContentViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + if (arguments != null) { + val bundle = arguments + val stringExtra = bundle!!.getString(Constants.STRING_EXTRA) + app = gson.fromJson(stringExtra, App::class.java) + setupNavigationView() + } else { + dismissAllowingStateLoss() + } + } + + private fun setupNavigationView() { + + val blacklistProvider = BlacklistProvider.with(requireContext()) + val isBlacklisted: Boolean = blacklistProvider.isBlacklisted(app.packageName) + + with(B.navigationView) { + //Switch strings for Add/Remove Blacklist + val blackListMenu: MenuItem = menu.findItem(R.id.action_blacklist) + blackListMenu.setTitle( + if (isBlacklisted) + R.string.action_whitelist + else + R.string.action_blacklist_add + ) + + //Show/Hide actions based on installed status + val installed = PackageUtil.isInstalled(requireContext(), app.packageName) + menu.findItem(R.id.action_uninstall).isVisible = installed + menu.findItem(R.id.action_local).isVisible = installed + + if (isQAndAbove()) { + //TODO: Add Scoped Storage Access + menu.findItem(R.id.action_local).isVisible = false + } + + setNavigationItemSelectedListener { item -> + when (item.itemId) { + R.id.action_blacklist -> { + + if (isBlacklisted) { + blacklistProvider.whitelist(app.packageName) + requireContext().toast(R.string.toast_apk_whitelisted) + } else { + blacklistProvider.blacklist(app.packageName) + requireContext().toast(R.string.toast_apk_blacklisted) + } + + EventBus.getDefault() + .post(BusEvent.Blacklisted(app.packageName, "")) + } + + R.id.action_local -> { + export() + } + + R.id.action_uninstall -> { + AppInstaller + .with(requireContext()) + .getPreferredInstaller().uninstall(app.packageName) + } + + R.id.action_info -> { + requireContext().openInfo(app.packageName) + } + } + dismissAllowingStateLoss() + false + } + } + } + + private fun export() = runWithPermissions( + Manifest.permission.READ_EXTERNAL_STORAGE, + Manifest.permission.WRITE_EXTERNAL_STORAGE + ) { + task { + ApkCopier(requireContext(), app.packageName).copy() + } + } + + companion object { + const val TAG = "APP_MENU_SHEET" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/sheets/AppPeekDialogSheet.kt b/app/src/main/java/com/aurora/store/view/ui/sheets/AppPeekDialogSheet.kt new file mode 100644 index 000000000..6c469e1cd --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/sheets/AppPeekDialogSheet.kt @@ -0,0 +1,96 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.ui.sheets + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.aurora.Constants +import com.aurora.gplayapi.data.models.App +import com.aurora.store.R +import com.aurora.store.databinding.SheetAppPeekBinding +import com.aurora.store.util.CommonUtil +import com.aurora.store.util.extensions.load +import com.bumptech.glide.load.resource.bitmap.RoundedCorners + +class AppPeekDialogSheet : BaseBottomSheet() { + + lateinit var B: SheetAppPeekBinding + lateinit var app: App + + private var rawApp = String() + + companion object { + @JvmStatic + fun newInstance(app: App): AppPeekDialogSheet { + return AppPeekDialogSheet().apply { + arguments = Bundle().apply { + putString(Constants.STRING_EXTRA, gson.toJson(app)) + } + } + } + } + + override fun onCreateContentView( + inflater: LayoutInflater, + container: ViewGroup, + savedInstanceState: Bundle? + ): View? { + B = SheetAppPeekBinding.inflate(inflater, container, false) + val bundle = arguments + if (bundle != null) { + rawApp = bundle.getString(Constants.STRING_EXTRA, "") + if (rawApp.isNotEmpty()) { + app = gson.fromJson(rawApp, App::class.java) + if (app.packageName.isNotEmpty()) { + draw() + } else { + dismissAllowingStateLoss() + } + } else { + dismissAllowingStateLoss() + } + } + + return B.root + } + + override fun onContentViewCreated(view: View, savedInstanceState: Bundle?) { + + } + + private fun draw() { + B.txtLine1.text = app.displayName + B.imgIcon.load(app.iconArtwork.url) { + transform(RoundedCorners(25)) + } + B.txtLine2.text = app.developerName + B.txtLine3.text = String.format( + requireContext().getString(R.string.app_list_rating), + CommonUtil.addSiPrefix(app.size), + app.labeledRating, + if (app.isFree) + "Free" + else + "Paid" + ) + } +} diff --git a/app/src/main/java/com/aurora/store/view/ui/sheets/BaseBottomSheet.kt b/app/src/main/java/com/aurora/store/view/ui/sheets/BaseBottomSheet.kt new file mode 100644 index 000000000..a65fbb73c --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/sheets/BaseBottomSheet.kt @@ -0,0 +1,81 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.ui.sheets + +import android.app.Dialog +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.FrameLayout +import com.aurora.store.R +import com.aurora.store.databinding.SheetBaseBinding +import com.google.android.material.bottomsheet.BottomSheetBehavior +import com.google.android.material.bottomsheet.BottomSheetDialog +import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import com.google.gson.Gson +import com.google.gson.GsonBuilder +import java.lang.reflect.Modifier + +abstract class BaseBottomSheet : BottomSheetDialogFragment() { + + lateinit var VM: SheetBaseBinding + + var gson: Gson = GsonBuilder().excludeFieldsWithModifiers(Modifier.TRANSIENT).create() + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + val bottomSheetDialog = BottomSheetDialog( + requireContext(), + R.style.Aurora_BottomSheetDialog + ) + + VM = SheetBaseBinding.inflate(layoutInflater) + + bottomSheetDialog.setContentView(VM.root) + + val container = VM.container + val contentView = onCreateContentView( + LayoutInflater.from(requireContext()), + container, + savedInstanceState + ) + + if (contentView != null) { + onContentViewCreated(contentView, savedInstanceState) + container.addView(contentView) + } + + bottomSheetDialog.setOnShowListener { + val bottomSheet = + bottomSheetDialog.findViewById(com.google.android.material.R.id.design_bottom_sheet) + if (bottomSheet != null) + BottomSheetBehavior.from(bottomSheet).state = BottomSheetBehavior.STATE_EXPANDED + } + return bottomSheetDialog + } + + abstract fun onCreateContentView( + inflater: LayoutInflater, + container: ViewGroup, + savedInstanceState: Bundle? + ): View? + + abstract fun onContentViewCreated(view: View, savedInstanceState: Bundle?) +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/sheets/DownloadMenuSheet.kt b/app/src/main/java/com/aurora/store/view/ui/sheets/DownloadMenuSheet.kt new file mode 100644 index 000000000..25dee9d3c --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/sheets/DownloadMenuSheet.kt @@ -0,0 +1,122 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.ui.sheets + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.aurora.store.R +import com.aurora.store.data.downloader.DownloadManager +import com.aurora.store.databinding.SheetDownloadMenuBinding +import com.aurora.store.util.extensions.copyToClipBoard +import com.aurora.store.util.extensions.toast +import com.tonyodev.fetch2.Fetch +import com.tonyodev.fetch2.Status + +class DownloadMenuSheet : BaseBottomSheet() { + + private lateinit var B: SheetDownloadMenuBinding + private lateinit var fetch: Fetch + + private var downloadId = 0 + private var status = 0 + private var url: String? = null + + + override fun onCreateContentView( + inflater: LayoutInflater, + container: ViewGroup, + savedInstanceState: Bundle? + ): View { + B = SheetDownloadMenuBinding.inflate(layoutInflater) + return B.root + } + + override fun onContentViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + fetch = DownloadManager + .with(requireContext()) + .getFetchInstance() + + if (arguments != null) { + val bundle = arguments + if (bundle != null) { + downloadId = bundle.getInt(DOWNLOAD_ID) + status = bundle.getInt(DOWNLOAD_STATUS) + url = bundle.getString(DOWNLOAD_URL) + attachNavigation() + } else { + dismissAllowingStateLoss() + } + } else { + dismissAllowingStateLoss() + } + } + + private fun attachNavigation() { + with(B.navigationView) { + if (status == Status.PAUSED.value || status == Status.COMPLETED.value || status == Status.CANCELLED.value) { + menu.findItem(R.id.action_pause).isVisible = false + } + + if (status == Status.DOWNLOADING.value || status == Status.COMPLETED.value || status == Status.QUEUED.value) { + menu.findItem(R.id.action_resume).isVisible = false + } + + if (status == Status.COMPLETED.value || status == Status.CANCELLED.value) { + menu.findItem(R.id.action_cancel).isVisible = false + } + + setNavigationItemSelectedListener { item -> + when (item.itemId) { + R.id.action_copy -> { + requireContext().copyToClipBoard(url) + requireContext().toast(requireContext().getString(R.string.toast_clipboard_copied)) + } + R.id.action_pause -> { + fetch.pause(downloadId) + } + R.id.action_resume -> if (status == Status.FAILED.value || status == Status.CANCELLED.value) { + fetch.retry(downloadId) + } else { + fetch.resume(downloadId) + } + R.id.action_cancel -> { + fetch.cancel(downloadId) + } + R.id.action_clear -> { + fetch.delete(downloadId) + } + } + dismissAllowingStateLoss() + false + } + } + } + + companion object { + const val TAG = "DOWNLOAD_MENU_SHEET" + const val DOWNLOAD_ID = "DOWNLOAD_ID" + const val DOWNLOAD_STATUS = "DOWNLOAD_STATUS" + const val DOWNLOAD_URL = "DOWNLOAD_URL" + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/sheets/NetworkDialogSheet.kt b/app/src/main/java/com/aurora/store/view/ui/sheets/NetworkDialogSheet.kt new file mode 100644 index 000000000..ecee09a52 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/sheets/NetworkDialogSheet.kt @@ -0,0 +1,64 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.ui.sheets + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.aurora.Constants +import com.aurora.store.databinding.SheetNetworkBinding + +class NetworkDialogSheet : BaseBottomSheet() { + + lateinit var B: SheetNetworkBinding + + private var type = 0 + + companion object { + @JvmStatic + fun newInstance(type: Int): NetworkDialogSheet { + return NetworkDialogSheet().apply { + arguments = Bundle().apply { + putInt(Constants.INT_EXTRA, type) + } + } + } + } + + override fun onCreateContentView( + inflater: LayoutInflater, + container: ViewGroup, + savedInstanceState: Bundle? + ): View? { + B = SheetNetworkBinding.inflate(inflater, container, false) + + val bundle = arguments + if (bundle != null) { + type = bundle.getInt(Constants.INT_EXTRA, 0) + } + + return B.root + } + + override fun onContentViewCreated(view: View, savedInstanceState: Bundle?) { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/splash/SplashActivity.kt b/app/src/main/java/com/aurora/store/view/ui/splash/SplashActivity.kt new file mode 100644 index 000000000..41872eba9 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/splash/SplashActivity.kt @@ -0,0 +1,199 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.ui.splash + +import android.os.Bundle +import android.view.Menu +import android.view.MenuItem +import android.view.View +import androidx.lifecycle.ViewModelProvider +import com.aurora.store.MainActivity +import com.aurora.store.R +import com.aurora.store.data.AuthState +import com.aurora.store.data.event.BusEvent +import com.aurora.store.databinding.ActivitySplashBinding +import com.aurora.store.util.extensions.load +import com.aurora.store.util.extensions.open +import com.aurora.store.view.ui.commons.BaseActivity +import com.aurora.store.view.ui.commons.BlacklistActivity +import com.aurora.store.view.ui.spoof.SpoofActivity +import com.aurora.store.viewmodel.auth.AuthViewModel +import com.bumptech.glide.load.resource.bitmap.RoundedCorners +import org.greenrobot.eventbus.EventBus +import org.greenrobot.eventbus.Subscribe + +class SplashActivity : BaseActivity() { + + private lateinit var VM: AuthViewModel + private lateinit var B: ActivitySplashBinding + + override fun onConnected() { + hideNetworkConnectivitySheet() + } + + override fun onDisconnected() { + showNetworkConnectivitySheet() + } + + override fun onReconnected() { + + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + EventBus.getDefault().register(this); + + B = ActivitySplashBinding.inflate(layoutInflater) + VM = ViewModelProvider(this).get(AuthViewModel::class.java) + + setContentView(B.root) + + B.imgIcon.load(R.drawable.ic_logo) { + transform(RoundedCorners(32)) + } + + attachToolbar() + attachActions() + + //Initial status + updateStatus("Getting things ready..") + + VM.liveData.observe(this, { + when (it) { + AuthState.Valid -> { + moveToContent() + } + + AuthState.Available -> { + updateStatus("Verifying session") + updateActionLayout(false) + } + + AuthState.Unavailable -> { + updateStatus("You need to login first") + updateActionLayout(true) + } + + AuthState.SignedIn -> { + moveToContent() + } + + AuthState.SignedOut -> { + updateStatus("Last session scrapped") + updateActionLayout(true) + } + + is AuthState.Status -> { + updateStatus(it.status) + } + } + }) + } + + override fun onResume() { + if (::VM.isInitialized) { + VM.observe() + } + super.onResume() + } + + override fun onCreateOptionsMenu(menu: Menu?): Boolean { + menuInflater.inflate(R.menu.menu_splash, menu) + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.menu_blacklist_manager -> { + open(BlacklistActivity::class.java) + return true + } + R.id.menu_spoof_manager -> { + open(SpoofActivity::class.java) + return true + } + } + return super.onOptionsItemSelected(item) + } + + private fun attachToolbar() { + setSupportActionBar(B.layoutToolbarAction.toolbar) + val actionBar = supportActionBar + if (actionBar != null) { + actionBar.elevation = 0f + actionBar.title = "" + } + } + + override fun onDestroy() { + EventBus.getDefault().unregister(this) + super.onDestroy() + } + + @Subscribe() + fun onEventReceived(event: BusEvent) { + when (event) { + is BusEvent.GoogleAAS -> { + if (event.success) { + updateStatus("Verifying Google Session") + VM.buildGoogleAuthData(event.email, event.aasToken) + } else { + updateStatus("Failed to login via Google") + } + } + else -> { + + } + } + } + + private fun updateStatus(string: String?) { + runOnUiThread { + B.txtStatus.apply { + text = string + } + } + } + + private fun updateActionLayout(isVisible: Boolean) { + if (isVisible) { + B.layoutAction.visibility = View.VISIBLE + } else { + B.layoutAction.visibility = View.INVISIBLE + } + } + + private fun attachActions() { + B.btnAnonymous.addOnClickListener { + B.btnAnonymous.updateProgress(true) + VM.buildAnonymousAuthData() + } + + B.btnGoogle.addOnClickListener { + B.btnGoogle.updateProgress(true) + openGoogleActivity() + } + } + + private fun moveToContent() { + runOnUiThread { open(MainActivity::class.java, true) } + } +} diff --git a/app/src/main/java/com/aurora/store/view/ui/spoof/DeviceSpoofFragment.kt b/app/src/main/java/com/aurora/store/view/ui/spoof/DeviceSpoofFragment.kt new file mode 100644 index 000000000..cbe71b64a --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/spoof/DeviceSpoofFragment.kt @@ -0,0 +1,120 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.ui.spoof + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.aurora.store.R +import com.aurora.store.data.providers.NativeDeviceInfoProvider +import com.aurora.store.data.providers.SpoofDeviceProvider +import com.aurora.store.data.providers.SpoofProvider +import com.aurora.store.databinding.FragmentGenericRecyclerBinding +import com.aurora.store.util.Log +import com.aurora.store.util.extensions.toast +import com.aurora.store.view.epoxy.views.preference.DeviceViewModel_ +import com.aurora.store.view.ui.commons.BaseFragment +import nl.komponents.kovenant.task +import nl.komponents.kovenant.ui.failUi +import nl.komponents.kovenant.ui.successUi +import java.util.* + + +class DeviceSpoofFragment : BaseFragment() { + + private lateinit var B: FragmentGenericRecyclerBinding + private lateinit var spoofProvider: SpoofProvider + + private var properties: Properties = Properties() + + companion object { + @JvmStatic + fun newInstance(): DeviceSpoofFragment { + return DeviceSpoofFragment().apply { + + } + } + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + B = FragmentGenericRecyclerBinding.bind( + inflater.inflate( + R.layout.fragment_generic_recycler, + container, + false + ) + ) + + properties = NativeDeviceInfoProvider(requireContext()).getNativeDeviceProperties() + spoofProvider = SpoofProvider.with(requireContext()) + + return B.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + if (spoofProvider.isDeviceSpoofEnabled()) + properties = spoofProvider.getSpoofDeviceProperties() + + task { + fetchAvailableDevices() + } successUi { + updateController(it) + } failUi { + Log.e("Could not get spoof device properties") + } + } + + private fun updateController(locales: List) { + B.recycler.withModels { + setFilterDuplicates(true) + locales.forEach { + add( + DeviceViewModel_() + .id(it.hashCode()) + .markChecked(properties == it) + .checked { _, checked -> + if (checked) { + properties = it + saveSelection(it) + requestModelBuild() + } + } + .properties(it) + ) + } + } + } + + private fun fetchAvailableDevices(): List { + return SpoofDeviceProvider.with(requireContext()).availableDevice + } + + private fun saveSelection(properties: Properties) { + requireContext().toast(R.string.spoof_apply) + spoofProvider.setSpoofDeviceProperties(properties) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/spoof/LocaleSpoofFragment.kt b/app/src/main/java/com/aurora/store/view/ui/spoof/LocaleSpoofFragment.kt new file mode 100644 index 000000000..4e211ba59 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/spoof/LocaleSpoofFragment.kt @@ -0,0 +1,121 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.ui.spoof + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.aurora.store.R +import com.aurora.store.data.providers.SpoofProvider +import com.aurora.store.databinding.FragmentGenericRecyclerBinding +import com.aurora.store.util.Log +import com.aurora.store.util.extensions.toast +import com.aurora.store.view.epoxy.views.preference.LocaleViewModel_ +import com.aurora.store.view.ui.commons.BaseFragment +import nl.komponents.kovenant.task +import nl.komponents.kovenant.ui.failUi +import nl.komponents.kovenant.ui.successUi +import java.util.* + + +class LocaleSpoofFragment : BaseFragment() { + + private lateinit var B: FragmentGenericRecyclerBinding + private lateinit var spoofProvider: SpoofProvider + + private var locale: Locale = Locale.getDefault() + + companion object { + @JvmStatic + fun newInstance(): LocaleSpoofFragment { + return LocaleSpoofFragment().apply { + + } + } + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + B = FragmentGenericRecyclerBinding.bind( + inflater.inflate( + R.layout.fragment_generic_recycler, + container, + false + ) + ) + + spoofProvider = SpoofProvider.with(requireContext()) + + return B.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + if (spoofProvider.isLocaleSpoofEnabled()) + locale = spoofProvider.getSpoofLocale() + + task { + fetchAvailableLocales() + } successUi { + updateController(it) + } failUi { + Log.e("Could not get available locales") + } + } + + private fun updateController(locales: List) { + B.recycler.withModels { + setFilterDuplicates(true) + locales.forEach { + add( + LocaleViewModel_() + .id(it.language) + .markChecked(locale == it) + .checked { _, checked -> + if (checked) { + locale = it + saveSelection(it) + requestModelBuild() + } + } + .locale(it) + ) + } + } + } + + private fun fetchAvailableLocales(): List { + val locales = Locale.getAvailableLocales() + val localeList: MutableList = ArrayList() + localeList.addAll(locales) + localeList.add(0, Locale.getDefault()) + return localeList + } + + private fun saveSelection(locale: Locale) { + requireContext().toast(R.string.spoof_apply) + spoofProvider.setSpoofLocale(locale) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/spoof/SpoofActivity.kt b/app/src/main/java/com/aurora/store/view/ui/spoof/SpoofActivity.kt new file mode 100644 index 000000000..914a12e37 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/spoof/SpoofActivity.kt @@ -0,0 +1,92 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.ui.spoof + +import android.os.Bundle +import androidx.fragment.app.Fragment +import androidx.fragment.app.FragmentManager +import androidx.lifecycle.Lifecycle +import androidx.viewpager2.adapter.FragmentStateAdapter +import com.aurora.store.R +import com.aurora.store.databinding.ActivityGenericPagerBinding +import com.aurora.store.util.extensions.close +import com.aurora.store.view.ui.commons.BaseActivity +import com.google.android.material.tabs.TabLayout +import com.google.android.material.tabs.TabLayoutMediator + +class SpoofActivity : BaseActivity() { + + private lateinit var B: ActivityGenericPagerBinding + + override fun onConnected() { + + } + + override fun onDisconnected() { + + } + + override fun onReconnected() { + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + B = ActivityGenericPagerBinding.inflate(layoutInflater) + setContentView(B.root) + + attachToolbar() + attachViewPager() + } + + private fun attachToolbar() { + B.layoutToolbarAction.txtTitle.text = getString(R.string.title_spoof_manager) + B.layoutToolbarAction.imgActionPrimary.setOnClickListener { + close() + } + } + + private fun attachViewPager() { + B.pager.adapter = ViewPagerAdapter(supportFragmentManager, lifecycle) + + TabLayoutMediator(B.tabLayout, B.pager, true) { tab: TabLayout.Tab, position: Int -> + when (position) { + 0 -> tab.text = getString(R.string.title_device) + 1 -> tab.text = getString(R.string.title_language) + else -> { + } + } + }.attach() + } + + internal class ViewPagerAdapter(fragment: FragmentManager, lifecycle: Lifecycle) : + FragmentStateAdapter(fragment, lifecycle) { + override fun createFragment(position: Int): Fragment { + return when (position) { + 0 -> DeviceSpoofFragment.newInstance() + 1 -> LocaleSpoofFragment.newInstance() + else -> Fragment() + } + } + + override fun getItemCount(): Int { + return 2 + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/view/ui/updates/UpdatesFragment.kt b/app/src/main/java/com/aurora/store/view/ui/updates/UpdatesFragment.kt new file mode 100644 index 000000000..113ba9743 --- /dev/null +++ b/app/src/main/java/com/aurora/store/view/ui/updates/UpdatesFragment.kt @@ -0,0 +1,277 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.view.ui.updates + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.lifecycle.ViewModelProvider +import com.aurora.Constants +import com.aurora.gplayapi.data.models.App +import com.aurora.gplayapi.data.models.AuthData +import com.aurora.gplayapi.helpers.PurchaseHelper +import com.aurora.store.R +import com.aurora.store.data.downloader.DownloadManager +import com.aurora.store.data.downloader.RequestBuilder +import com.aurora.store.data.installer.AppInstaller +import com.aurora.store.data.providers.AuthProvider +import com.aurora.store.databinding.FragmentUpdatesBinding +import com.aurora.store.util.Log +import com.aurora.store.util.extensions.flushAndAdd +import com.aurora.store.util.extensions.toast +import com.aurora.store.view.epoxy.views.AppUpdateViewModel_ +import com.aurora.store.view.epoxy.views.UpdateHeaderViewModel_ +import com.aurora.store.view.epoxy.views.app.NoAppViewModel_ +import com.aurora.store.view.epoxy.views.shimmer.AppListViewShimmerModel_ +import com.aurora.store.view.ui.commons.BaseFragment +import com.aurora.store.view.ui.sheets.AppMenuSheet +import com.aurora.store.viewmodel.all.UpdatesViewModel +import com.tonyodev.fetch2.AbstractFetchGroupListener +import com.tonyodev.fetch2.Download +import com.tonyodev.fetch2.Fetch +import com.tonyodev.fetch2.FetchGroup +import nl.komponents.kovenant.task +import nl.komponents.kovenant.ui.failUi +import nl.komponents.kovenant.ui.successUi + +class UpdatesFragment : BaseFragment() { + + private lateinit var B: FragmentUpdatesBinding + private lateinit var VM: UpdatesViewModel + + private lateinit var authData: AuthData + private lateinit var purchaseHelper: PurchaseHelper + + private lateinit var fetch: Fetch + private lateinit var fetchListener: AbstractFetchGroupListener + + private val appList: MutableList = mutableListOf() + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + B = FragmentUpdatesBinding.bind( + inflater.inflate( + R.layout.fragment_updates, + container, + false + ) + ) + + VM = ViewModelProvider(requireActivity()).get(UpdatesViewModel::class.java) + + authData = AuthProvider.with(requireContext()).getAuthData() + purchaseHelper = PurchaseHelper.with(authData) + + fetch = DownloadManager.with(requireContext()).fetch + fetchListener = object : AbstractFetchGroupListener() { + override fun onCompleted(groupId: Int, download: Download, fetchGroup: FetchGroup) { + super.onCompleted(groupId, download, fetchGroup) + if (fetchGroup.groupDownloadProgress == 100) { + install(download.tag!!, fetchGroup.downloads) + } + } + } + + fetch.addListener(fetchListener) + + return B.root + } + + override fun onDestroyView() { + if (::fetch.isInitialized && ::fetchListener.isInitialized) { + fetch.removeListener(fetchListener) + } + super.onDestroyView() + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + VM.liveData.observe(viewLifecycleOwner, { + appList.flushAndAdd(it) + updateController(it) + B.swipeRefreshLayout.isRefreshing = false + }) + + B.swipeRefreshLayout.setOnRefreshListener { + VM.observe() + } + + updateController(null) + } + + private fun updateController(appList: List?) { + B.recycler.withModels { + setFilterDuplicates(true) + if (appList == null) { + for (i in 1..6) { + add( + AppListViewShimmerModel_() + .id(i) + ) + } + } else { + if (appList.isEmpty()) { + add( + NoAppViewModel_() + .id("no_update") + .icon(R.drawable.ic_updates) + .message(getString(R.string.details_no_updates)) + ) + } else { + add( + if (VM.selectedUpdates.isEmpty()) { + UpdateHeaderViewModel_() + .id("header_all") + .title("${appList.size} updates available") + .action(getString(R.string.action_update_all)) + .click { v -> + updateAction(false) + } + } else { + UpdateHeaderViewModel_() + .id("header_selected") + .title("${VM.selectedUpdates.size} updates selected") + .action(getString(R.string.action_update)) + .click { v -> + updateAction(true) + } + } + ) + + appList.forEach { app -> + add( + AppUpdateViewModel_() + .id(app.id) + .app(app) + .click { _ -> openDetailsActivity(app) } + .longClick { _ -> + openAppMenuSheet(app) + false + } + .markChecked(VM.selectedUpdates.contains(app.packageName)) + .checked { _, isChecked -> + if (isChecked) + VM.selectedUpdates.add(app.packageName) + else + VM.selectedUpdates.remove(app.packageName) + + requestModelBuild() + } + ) + } + } + } + } + } + + private fun updateAction(selectedOnly: Boolean) { + if (VM.isUpdating) { + requireContext().toast("Update in progress, let previous batch finish first") + } else { + if (selectedOnly) { + updateSelected() + } else { + updateAll() + } + } + } + + private fun updateAll() { + appList.forEach { app -> + task { + val files = purchaseHelper.purchase(app.packageName, app.versionCode, app.offerType) + files.map { RequestBuilder.buildRequest(requireContext(), app, it) } + } successUi { + val requests = it.filter { request -> request.url.isNotEmpty() }.toList() + if (requests.isNotEmpty()) { + VM.isUpdating = true + fetch.enqueue(requests) { + Log.i("Updating ${app.displayName}") + } + } else { + requireContext().toast("Failed to update ${app.displayName}") + } + } failUi { + Log.e("Failed to update ${app.displayName}") + } + } + } + + private fun updateSelected() { + val selectedPackages = VM.selectedUpdates + appList.forEach { app -> + if (selectedPackages.contains(app.packageName)) { + task { + val files = purchaseHelper.purchase( + app.packageName, + app.versionCode, + app.offerType + ) + files.map { RequestBuilder.buildRequest(requireContext(), app, it) } + } successUi { + val requests = it.filter { request -> request.url.isNotEmpty() }.toList() + if (requests.isNotEmpty()) { + VM.isUpdating = true + fetch.enqueue(requests) { + Log.i("Updating ${app.displayName}") + } + } else { + requireContext().toast("Failed to update ${app.displayName}") + } + } failUi { + Log.e("Failed to update ${app.displayName}") + } + } + } + } + + @Synchronized + private fun install(packageName: String, files: List) { + task { + AppInstaller.with(requireContext()) + .getPreferredInstaller() + .install( + packageName, + files + .filter { it.file.endsWith(".apk") } + .map { it.file }.toList() + ) + } + } + + private fun openAppMenuSheet(app: App) { + val fragment = childFragmentManager.findFragmentByTag(AppMenuSheet.TAG) + if (fragment != null) + childFragmentManager.beginTransaction().remove(fragment) + + AppMenuSheet().apply { + arguments = Bundle().apply { + putString(Constants.STRING_EXTRA, gson.toJson(app)) + } + }.show( + childFragmentManager, + AppMenuSheet.TAG + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/viewmodel/BaseAndroidViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/BaseAndroidViewModel.kt new file mode 100644 index 000000000..b852296ce --- /dev/null +++ b/app/src/main/java/com/aurora/store/viewmodel/BaseAndroidViewModel.kt @@ -0,0 +1,80 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.viewmodel + +import android.app.Application +import androidx.lifecycle.AndroidViewModel +import com.aurora.store.data.RequestState +import com.aurora.store.data.providers.NetworkProvider +import com.aurora.store.util.Log +import com.google.gson.Gson +import com.google.gson.GsonBuilder +import java.lang.reflect.Modifier + +abstract class BaseAndroidViewModel(application: Application) : AndroidViewModel(application), + NetworkProvider.NetworkListener { + + private lateinit var networkListener: NetworkProvider.NetworkListener + + protected val gson: Gson = GsonBuilder() + .excludeFieldsWithModifiers(Modifier.TRANSIENT, Modifier.STATIC) + .create() + + protected var requestState: RequestState + + init { + Log.i("${javaClass.simpleName} Created") + + requestState = RequestState.Init + + NetworkProvider.addListener(this) + } + + abstract fun observe() + + override fun onConnected() { + + } + + override fun onDisconnected() { + + } + + override fun onReconnected() { + redoLastNetworkTask() + } + + private fun redoLastNetworkTask() { + when (requestState) { + RequestState.Pending -> { + observe() + } + else -> { + + } + } + } + + override fun onCleared() { + Log.i("${javaClass.simpleName} Destroyed") + NetworkProvider.removeListener(this) + super.onCleared() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/viewmodel/all/BaseAppsViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/all/BaseAppsViewModel.kt new file mode 100644 index 000000000..b9fd86e96 --- /dev/null +++ b/app/src/main/java/com/aurora/store/viewmodel/all/BaseAppsViewModel.kt @@ -0,0 +1,93 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.viewmodel.all + +import android.app.Application +import android.content.pm.PackageInfo +import androidx.lifecycle.MutableLiveData +import com.aurora.gplayapi.data.models.App +import com.aurora.gplayapi.helpers.AppDetailsHelper +import com.aurora.store.data.network.HttpClient +import com.aurora.store.data.providers.AuthProvider +import com.aurora.store.data.providers.BlacklistProvider +import com.aurora.store.util.PackageUtil +import com.aurora.store.util.Preferences +import com.aurora.store.viewmodel.BaseAndroidViewModel +import java.util.* + +abstract class BaseAppsViewModel(application: Application) : BaseAndroidViewModel(application) { + + val authData = AuthProvider + .with(application) + .getAuthData() + + val appDetailsHelper = + AppDetailsHelper + .with(authData) + .using(HttpClient.getPreferredClient()) + + var blacklistProvider = BlacklistProvider + .with(application) + + val liveData: MutableLiveData> = MutableLiveData() + + var appList: MutableList = mutableListOf() + var packageInfoMap: MutableMap = mutableMapOf() + + fun getFilteredApps(): List { + val blackList = blacklistProvider.getBlackList() + val gapps = getGApps() + + val isGoogleFilterEnabled = Preferences.getBoolean( + getApplication(), + Preferences.PREFERENCE_FILTER_GOOGLE + ) + + packageInfoMap.clear() + packageInfoMap = PackageUtil.getPackageInfoMap(getApplication()) + + packageInfoMap.keys.let { packages -> + /*Filter black list*/ + var filtersPackages = packages + .filter { !blackList.contains(it) } + + /*Filter google apps*/ + if (isGoogleFilterEnabled) + filtersPackages = filtersPackages + .filter { !it.startsWith("com.google") } + .filter { !gapps.contains(it) } + + return appDetailsHelper + .getAppByPackageName(filtersPackages) + .filter { it.displayName.isNotEmpty() } + } + } + + open fun getGApps(): Set { + val gapps: MutableSet = HashSet() + gapps.add("com.chrome.beta") + gapps.add("com.chrome.canary") + gapps.add("com.chrome.dev") + gapps.add("com.android.chrome") + gapps.add("com.niksoftware.snapseed") + gapps.add("com.google.toontastic") + return gapps + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/viewmodel/all/BlacklistViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/all/BlacklistViewModel.kt new file mode 100644 index 000000000..bb3d71b58 --- /dev/null +++ b/app/src/main/java/com/aurora/store/viewmodel/all/BlacklistViewModel.kt @@ -0,0 +1,86 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.viewmodel.all + +import android.app.Application +import android.content.pm.PackageManager +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.viewModelScope +import com.aurora.store.data.RequestState +import com.aurora.store.data.model.Black +import com.aurora.store.data.providers.BlacklistProvider +import com.aurora.store.util.PackageUtil +import com.aurora.store.viewmodel.BaseAndroidViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.supervisorScope + +class BlacklistViewModel(application: Application) : BaseAndroidViewModel(application) { + + private val packageManager: PackageManager = application.packageManager + private val blacklistProvider: BlacklistProvider = BlacklistProvider.with(application) + + var blackList: MutableList = mutableListOf() + var selected: MutableSet = mutableSetOf() + + val liveData: MutableLiveData> = MutableLiveData() + + init { + requestState = RequestState.Init + selected = blacklistProvider.getBlackList() + observe() + } + + override fun observe() { + viewModelScope.launch(Dispatchers.IO) { + supervisorScope { + try { + val packageInfoMap = PackageUtil.getPackageInfoMap(getApplication()) + packageInfoMap.values + .filter { + it.packageName != null + && it.versionName != null + && it.applicationInfo != null + && it.applicationInfo.enabled + } + .forEach { + val black = Black(it.packageName).apply { + displayName = packageManager.getApplicationLabel(it.applicationInfo) + .toString() + versionCode = it.versionCode + versionName = it.versionName + drawable = packageManager.getApplicationIcon(packageName) + } + blackList.add(black) + } + liveData.postValue(blackList.sortedBy { it.displayName }) + requestState = RequestState.Complete + } catch (e: Exception) { + e.printStackTrace() + requestState = RequestState.Pending + } + } + } + } + + override fun onCleared() { + super.onCleared() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/viewmodel/all/InstalledViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/all/InstalledViewModel.kt new file mode 100644 index 000000000..0e43c31ed --- /dev/null +++ b/app/src/main/java/com/aurora/store/viewmodel/all/InstalledViewModel.kt @@ -0,0 +1,88 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.viewmodel.all + +import android.app.Application +import androidx.lifecycle.viewModelScope +import com.aurora.store.data.RequestState +import com.aurora.store.data.event.BusEvent +import com.aurora.store.util.extensions.flushAndAdd +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import org.greenrobot.eventbus.EventBus +import org.greenrobot.eventbus.Subscribe +import org.greenrobot.eventbus.ThreadMode + +class InstalledViewModel(application: Application) : BaseAppsViewModel(application) { + + init { + EventBus.getDefault().register(this) + + requestState = RequestState.Init + observe() + } + + override fun observe() { + viewModelScope.launch(Dispatchers.IO) { + try { + appList.flushAndAdd(getFilteredApps()) + liveData.postValue(appList.sortedBy { it.displayName }) + requestState = RequestState.Complete + } catch (e: Exception) { + requestState = RequestState.Pending + } + } + } + + @Subscribe(threadMode = ThreadMode.BACKGROUND) + fun onEvent(event: BusEvent) { + when (event) { + is BusEvent.InstallEvent -> { + updateListAndPost(event.packageName) + } + is BusEvent.UninstallEvent -> { + updateListAndPost(event.packageName) + } + is BusEvent.Blacklisted -> { + observe() + } + else -> { + + } + } + } + + private fun updateListAndPost(packageName: String) { + //Remove from current list + val updatedList = appList.filter { + it.packageName != packageName + }.toList() + + appList.flushAndAdd(updatedList) + + //Post new update list + liveData.postValue(appList.sortedBy { it.displayName }) + } + + override fun onCleared() { + EventBus.getDefault().unregister(this) + super.onCleared() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/viewmodel/all/LibraryAppsViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/all/LibraryAppsViewModel.kt new file mode 100644 index 000000000..5cf475a23 --- /dev/null +++ b/app/src/main/java/com/aurora/store/viewmodel/all/LibraryAppsViewModel.kt @@ -0,0 +1,82 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.viewmodel.all + +import android.app.Application +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.viewModelScope +import com.aurora.gplayapi.data.models.AuthData +import com.aurora.gplayapi.data.models.StreamCluster +import com.aurora.gplayapi.helpers.ClusterHelper +import com.aurora.store.data.RequestState +import com.aurora.store.data.network.HttpClient +import com.aurora.store.data.providers.AuthProvider +import com.aurora.store.viewmodel.BaseAndroidViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.supervisorScope + +class LibraryAppsViewModel(application: Application) : BaseAndroidViewModel(application) { + + private val authData: AuthData = AuthProvider.with(application).getAuthData() + private val clusterHelper: ClusterHelper = + ClusterHelper.with(authData).using(HttpClient.getPreferredClient()) + + val liveData: MutableLiveData = MutableLiveData() + var streamCluster: StreamCluster = StreamCluster() + + init { + observe() + } + + override fun observe() { + viewModelScope.launch(Dispatchers.IO) { + supervisorScope { + try { + when { + streamCluster.clusterAppList.isEmpty() -> { + val newCluster = + clusterHelper.getCluster(ClusterHelper.Type.MY_APPS_LIBRARY) + updateCluster(newCluster) + liveData.postValue(streamCluster) + } + streamCluster.hasNext() -> { + val newCluster = clusterHelper.next(streamCluster.clusterNextPageUrl) + updateCluster(newCluster) + liveData.postValue(streamCluster) + } + else -> { + requestState = RequestState.Complete + } + } + } catch (e: Exception) { + requestState = RequestState.Pending + } + } + } + } + + private fun updateCluster(newCluster: StreamCluster) { + streamCluster.apply { + clusterAppList.addAll(newCluster.clusterAppList) + clusterNextPageUrl = newCluster.clusterNextPageUrl + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/viewmodel/all/UpdatesViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/all/UpdatesViewModel.kt new file mode 100644 index 000000000..eea21b568 --- /dev/null +++ b/app/src/main/java/com/aurora/store/viewmodel/all/UpdatesViewModel.kt @@ -0,0 +1,112 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.viewmodel.all + +import android.app.Application +import androidx.lifecycle.viewModelScope +import com.aurora.gplayapi.data.models.App +import com.aurora.store.data.RequestState +import com.aurora.store.data.event.BusEvent +import com.aurora.store.util.extensions.flushAndAdd +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import org.greenrobot.eventbus.EventBus +import org.greenrobot.eventbus.Subscribe +import org.greenrobot.eventbus.ThreadMode + +class UpdatesViewModel(application: Application) : BaseAppsViewModel(application) { + + var selectedUpdates: MutableSet = mutableSetOf() + var isUpdating: Boolean = false + + init { + EventBus.getDefault().register(this) + + requestState = RequestState.Init + observe() + } + + override fun observe() { + viewModelScope.launch(Dispatchers.IO) { + try { + val marketApps = getFilteredApps() + checkUpdate(marketApps) + } catch (e: Exception) { + requestState = RequestState.Pending + } + } + } + + private fun checkUpdate(subAppList: List) { + subAppList.filter { + val packageInfo = packageInfoMap[it.packageName] + if (packageInfo != null) { + it.versionCode > packageInfo.versionCode + } else { + false + } + }.also { apps -> + appList.flushAndAdd(apps) + liveData.postValue(appList.sortedBy { it.displayName }) + requestState = RequestState.Complete + } + } + + @Subscribe(threadMode = ThreadMode.BACKGROUND) + fun onEvent(event: BusEvent) { + when (event) { + is BusEvent.InstallEvent -> { + updateListAndPost(event.packageName) + } + is BusEvent.UninstallEvent -> { + updateListAndPost(event.packageName) + } + is BusEvent.Blacklisted -> { + updateListAndPost(event.packageName) + } + else -> { + + } + } + } + + private fun updateListAndPost(packageName: String) { + //Remove from selected updates + selectedUpdates.remove(packageName) + + if (selectedUpdates.isEmpty()) + isUpdating = false + + //Remove from current update list + val updatedList = appList.filter { + it.packageName != packageName + }.toList() + + appList.flushAndAdd(updatedList) + + //Post new update list + liveData.postValue(appList.sortedBy { it.displayName }) + } + + override fun onCleared() { + EventBus.getDefault().unregister(this) + super.onCleared() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/viewmodel/auth/AuthViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/auth/AuthViewModel.kt new file mode 100644 index 000000000..72f603282 --- /dev/null +++ b/app/src/main/java/com/aurora/store/viewmodel/auth/AuthViewModel.kt @@ -0,0 +1,224 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.viewmodel.auth + +import android.app.Application +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.viewModelScope +import com.aurora.Constants +import com.aurora.gplayapi.data.models.AuthData +import com.aurora.gplayapi.helpers.AuthHelper +import com.aurora.gplayapi.helpers.AuthValidator +import com.aurora.store.AccountType +import com.aurora.store.data.AuthState +import com.aurora.store.data.RequestState +import com.aurora.store.data.network.HttpClient +import com.aurora.store.data.providers.AccountProvider +import com.aurora.store.data.providers.NativeDeviceInfoProvider +import com.aurora.store.data.providers.SpoofProvider +import com.aurora.store.util.Preferences +import com.aurora.store.util.Preferences.PREFERENCE_AUTH_DATA +import com.aurora.store.viewmodel.BaseAndroidViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.supervisorScope +import nl.komponents.kovenant.task +import java.net.ConnectException +import java.net.UnknownHostException + +class AuthViewModel(application: Application) : BaseAndroidViewModel(application) { + + private val spoofProvider = SpoofProvider.with(getApplication()) + + val liveData: MutableLiveData = MutableLiveData() + + init { + requestState = RequestState.Init + } + + override fun observe() { + val signedIn = Preferences.getBoolean(getApplication(), Constants.ACCOUNT_SIGNED_IN) + if (signedIn) { + liveData.postValue(AuthState.Available) + buildSavedAuthData() + } else { + liveData.postValue(AuthState.Unavailable) + } + } + + fun buildGoogleAuthData(email: String, aasToken: String) { + updateStatus("Requesting new session") + + task { + var properties = NativeDeviceInfoProvider(getApplication()).getNativeDeviceProperties() + if (spoofProvider.isDeviceSpoofEnabled()) + properties = spoofProvider.getSpoofDeviceProperties() + + return@task AuthHelper.build(email, aasToken, properties) + } success { + verifyAndSaveAuth(it, AccountType.GOOGLE) + } fail { + updateStatus("Failed to generate Session") + } + } + + fun buildAnonymousAuthData() { + updateStatus("Requesting new session") + + task { + var properties = NativeDeviceInfoProvider(getApplication()) + .getNativeDeviceProperties() + + if (spoofProvider.isDeviceSpoofEnabled()) + properties = spoofProvider.getSpoofDeviceProperties() + + val playResponse = HttpClient + .getPreferredClient() + .postAuth( + Constants.URL_DISPENSER, + gson.toJson(properties).toByteArray() + ) + + if (playResponse.isSuccessful) { + return@task gson.fromJson( + String(playResponse.responseBytes), + AuthData::class.java + ) + } else { + when (playResponse.code) { + 404 -> throw Exception("Server unreachable") + 429 -> throw Exception("Oops, You are rate limited") + else -> throw Exception(playResponse.errorString) + } + } + } success { + //Set AuthData as anonymous + it.isAnonymous = true + verifyAndSaveAuth(it, AccountType.ANONYMOUS) + } fail { + updateStatus(it.message.toString()) + } + } + + private fun buildSavedAuthData() { + viewModelScope.launch(Dispatchers.IO) { + supervisorScope { + try { + //Load & validate saved AuthData + val savedAuthData = getSavedAuthData() + + if (isValid(savedAuthData)) { + liveData.postValue(AuthState.Valid) + requestState = RequestState.Complete + } else { + //Generate and validate new auth + val type = AccountProvider.with(getApplication()).getAccountType() + when (type) { + AccountType.GOOGLE -> { + val email = Preferences.getString( + getApplication(), + Constants.ACCOUNT_EMAIL_PLAIN + ) + val aasToken = Preferences.getString( + getApplication(), + Constants.ACCOUNT_AAS_PLAIN + ) + buildGoogleAuthData(email, aasToken) + } + AccountType.ANONYMOUS -> { + buildAnonymousAuthData() + } + } + } + } catch (e: Exception) { + when (e) { + is UnknownHostException -> updateStatus("No network") + is ConnectException -> updateStatus("Could not connect to server") + else -> updateStatus("Unknown error") + } + requestState = RequestState.Pending + } + } + } + } + + private fun getSavedAuthData(): AuthData { + val rawAuth: String = Preferences.getString(getApplication(), PREFERENCE_AUTH_DATA) + return if (rawAuth.isNotBlank()) + gson.fromJson(rawAuth, AuthData::class.java) + else + AuthData("", "") + } + + private fun isValid(authData: AuthData): Boolean { + return try { + AuthValidator.with(authData) + .using(HttpClient.getPreferredClient()) + .isValid() + } catch (e: Exception) { + false + } + } + + private fun verifyAndSaveAuth(authData: AuthData, type: AccountType) { + updateStatus("Verifying new session") + + if (spoofProvider.isLocaleSpoofEnabled()) { + authData.locale = spoofProvider.getSpoofLocale() + } + + if (authData.authToken.isNotEmpty() && authData.deviceConfigToken.isNotEmpty()) { + configAuthPref(authData, type, true) + liveData.postValue(AuthState.SignedIn) + requestState = RequestState.Complete + } else { + configAuthPref(authData, type, false) + liveData.postValue(AuthState.SignedOut) + requestState = RequestState.Pending + + updateStatus("Failed to verify session") + } + } + + private fun configAuthPref(authData: AuthData, type: AccountType, signedIn: Boolean) { + if (signedIn) { + //Save Auth Data + Preferences.putString( + getApplication(), + PREFERENCE_AUTH_DATA, + gson.toJson(authData) + ) + } + + //Save Auth Type + Preferences.putString( + getApplication(), + Constants.ACCOUNT_TYPE, + type.name // ANONYMOUS OR GOOGLE + ) + + //Save Auth Status + Preferences.putBoolean(getApplication(), Constants.ACCOUNT_SIGNED_IN, signedIn) + } + + private fun updateStatus(status: String) { + liveData.postValue(AuthState.Status(status)) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/viewmodel/browse/StreamBrowseViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/browse/StreamBrowseViewModel.kt new file mode 100644 index 000000000..6f8dfc8d3 --- /dev/null +++ b/app/src/main/java/com/aurora/store/viewmodel/browse/StreamBrowseViewModel.kt @@ -0,0 +1,102 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.viewmodel.browse + +import android.app.Application +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.viewModelScope +import com.aurora.gplayapi.data.models.AuthData +import com.aurora.gplayapi.data.models.StreamCluster +import com.aurora.gplayapi.helpers.StreamHelper +import com.aurora.store.data.RequestState +import com.aurora.store.data.network.HttpClient +import com.aurora.store.data.providers.AuthProvider +import com.aurora.store.util.Log +import com.aurora.store.viewmodel.BaseAndroidViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.supervisorScope + +class StreamBrowseViewModel(application: Application) : BaseAndroidViewModel(application) { + + private val authData: AuthData = AuthProvider.with(application).getAuthData() + private val streamHelper: StreamHelper = StreamHelper + .with(authData) + .using(HttpClient.getPreferredClient()) + + val liveData: MutableLiveData = MutableLiveData() + var streamCluster: StreamCluster = StreamCluster() + + fun getStreamBundle(browseUrl: String) { + viewModelScope.launch(Dispatchers.IO) { + liveData.postValue(getInitialCluster(browseUrl)) + } + } + + private fun getInitialCluster( + browseUrl: String + ): StreamCluster { + + requestState = RequestState.Init + + val browseResponse = streamHelper.getBrowseStreamResponse(browseUrl) + + if (browseResponse.contentsUrl.isNotEmpty()) + streamCluster = streamHelper.getNextStreamCluster(browseResponse.contentsUrl) + else if (browseResponse.hasBrowseTab()) + streamCluster = streamHelper.getNextStreamCluster(browseResponse.browseTab.listUrl) + + streamCluster.apply { + clusterTitle = browseResponse.title + } + + return streamCluster + } + + fun nextCluster() { + viewModelScope.launch(Dispatchers.IO) { + supervisorScope { + try { + if (streamCluster.hasNext()) { + val newCluster = streamHelper.getNextStreamCluster( + streamCluster.clusterNextPageUrl + ) + + streamCluster.apply { + clusterAppList.addAll(newCluster.clusterAppList) + clusterNextPageUrl = newCluster.clusterNextPageUrl + } + + liveData.postValue(streamCluster) + } else { + Log.i("End of Bundle") + requestState = RequestState.Complete + } + } catch (e: Exception) { + requestState = RequestState.Pending + } + } + } + } + + override fun observe() { + requestState = RequestState.Init + } +} diff --git a/app/src/main/java/com/aurora/store/viewmodel/category/BaseCategoryViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/category/BaseCategoryViewModel.kt new file mode 100644 index 000000000..d03ad4e70 --- /dev/null +++ b/app/src/main/java/com/aurora/store/viewmodel/category/BaseCategoryViewModel.kt @@ -0,0 +1,59 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.viewmodel.category + +import android.app.Application +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.viewModelScope +import com.aurora.gplayapi.data.models.AuthData +import com.aurora.gplayapi.data.models.Category +import com.aurora.gplayapi.helpers.CategoryHelper +import com.aurora.store.data.RequestState +import com.aurora.store.data.network.HttpClient +import com.aurora.store.data.providers.AuthProvider +import com.aurora.store.viewmodel.BaseAndroidViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +abstract class BaseCategoryViewModel(application: Application) : BaseAndroidViewModel(application) { + + private val authData: AuthData = AuthProvider + .with(application) + .getAuthData() + + private val streamHelper: CategoryHelper = CategoryHelper + .with(authData) + .using(HttpClient.getPreferredClient()) + + val liveData: MutableLiveData> = MutableLiveData() + + lateinit var type: Category.Type + + override fun observe() { + viewModelScope.launch(Dispatchers.IO) { + try { + liveData.postValue(streamHelper.getAllCategoriesList(type)) + requestState = RequestState.Complete + } catch (e: Exception) { + requestState = RequestState.Pending + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/viewmodel/category/CategoryViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/category/CategoryViewModel.kt new file mode 100644 index 000000000..00bdb0205 --- /dev/null +++ b/app/src/main/java/com/aurora/store/viewmodel/category/CategoryViewModel.kt @@ -0,0 +1,42 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.viewmodel.category + +import android.app.Application +import com.aurora.gplayapi.data.models.Category +import com.aurora.store.data.RequestState + +class AppCategoryViewModel(application: Application) : BaseCategoryViewModel(application) { + + init { + type = Category.Type.APPLICATION + requestState = RequestState.Init + observe() + } +} + +class GameCategoryViewModel(application: Application) : BaseCategoryViewModel(application) { + + init { + type = Category.Type.GAME + requestState = RequestState.Init + observe() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/viewmodel/editorschoice/BaseEditorChoiceViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/editorschoice/BaseEditorChoiceViewModel.kt new file mode 100644 index 000000000..bd744cb3d --- /dev/null +++ b/app/src/main/java/com/aurora/store/viewmodel/editorschoice/BaseEditorChoiceViewModel.kt @@ -0,0 +1,63 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.viewmodel.editorschoice + +import android.app.Application +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.viewModelScope +import com.aurora.gplayapi.data.models.AuthData +import com.aurora.gplayapi.data.models.editor.EditorChoiceBundle +import com.aurora.gplayapi.helpers.StreamHelper +import com.aurora.store.data.RequestState +import com.aurora.store.data.network.HttpClient +import com.aurora.store.data.providers.AuthProvider +import com.aurora.store.viewmodel.BaseAndroidViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.supervisorScope + +open class BaseEditorChoiceViewModel(application: Application) : BaseAndroidViewModel(application) { + + private val authData: AuthData = AuthProvider + .with(application) + .getAuthData() + + private val streamHelper: StreamHelper = StreamHelper + .with(authData) + .using(HttpClient.getPreferredClient()) + + lateinit var category: StreamHelper.Category + + val liveData: MutableLiveData> = MutableLiveData() + + override fun observe() { + viewModelScope.launch(Dispatchers.IO) { + supervisorScope { + try { + val editorChoiceBundle = streamHelper.getEditorChoiceStream(category) + liveData.postValue(editorChoiceBundle) + requestState = RequestState.Complete + } catch (e: Exception) { + requestState = RequestState.Pending + } + } + } + } +} diff --git a/app/src/main/java/com/aurora/store/viewmodel/editorschoice/EditorChoiceViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/editorschoice/EditorChoiceViewModel.kt new file mode 100644 index 000000000..695657d21 --- /dev/null +++ b/app/src/main/java/com/aurora/store/viewmodel/editorschoice/EditorChoiceViewModel.kt @@ -0,0 +1,38 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.viewmodel.editorschoice + +import android.app.Application +import com.aurora.gplayapi.helpers.StreamHelper + +class AppEditorChoiceViewModel(application: Application) : BaseEditorChoiceViewModel(application) { + init { + category = StreamHelper.Category.APPLICATION + observe() + } +} + + +class GameEditorChoiceViewModel(application: Application) : BaseEditorChoiceViewModel(application) { + init { + category = StreamHelper.Category.GAME + observe() + } +} diff --git a/app/src/main/java/com/aurora/store/viewmodel/homestream/BaseClusterViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/homestream/BaseClusterViewModel.kt new file mode 100644 index 000000000..b70349892 --- /dev/null +++ b/app/src/main/java/com/aurora/store/viewmodel/homestream/BaseClusterViewModel.kt @@ -0,0 +1,122 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.viewmodel.homestream + +import android.app.Application +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.viewModelScope +import com.aurora.gplayapi.data.models.AuthData +import com.aurora.gplayapi.data.models.StreamBundle +import com.aurora.gplayapi.data.models.StreamCluster +import com.aurora.gplayapi.helpers.StreamHelper +import com.aurora.store.data.RequestState +import com.aurora.store.data.ViewState +import com.aurora.store.data.network.HttpClient +import com.aurora.store.data.providers.AuthProvider +import com.aurora.store.util.Log +import com.aurora.store.viewmodel.BaseAndroidViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.supervisorScope + +abstract class BaseClusterViewModel(application: Application) : BaseAndroidViewModel(application) { + + var authData: AuthData = AuthProvider.with(application).getAuthData() + var streamHelper: StreamHelper = StreamHelper + .with(authData) + .using(HttpClient.getPreferredClient()) + + val liveData: MutableLiveData = MutableLiveData() + var streamBundle: StreamBundle = StreamBundle() + + lateinit var type: StreamHelper.Type + lateinit var category: StreamHelper.Category + + open fun getStreamBundle( + nextPageUrl: String, + category: StreamHelper.Category, + type: StreamHelper.Type + ): StreamBundle { + return if (streamBundle.streamClusters.isEmpty()) + streamHelper.getNavStream(type, category) + else + streamHelper.next(nextPageUrl) + } + + override fun observe() { + viewModelScope.launch(Dispatchers.IO) { + supervisorScope { + try { + if (!streamBundle.hasCluster() || streamBundle.hasNext()) { + + //Fetch new stream bundle + val newBundle = getStreamBundle( + streamBundle.streamNextPageUrl, + category, + type + ) + + //Update old bundle + streamBundle.apply { + streamClusters.putAll(newBundle.streamClusters) + streamNextPageUrl = newBundle.streamNextPageUrl + } + + //Post updated to UI + liveData.postValue(ViewState.Success(streamBundle)) + } else { + Log.i("End of Bundle") + requestState = RequestState.Complete + } + } catch (e: Exception) { + requestState = RequestState.Pending + liveData.postValue(ViewState.Error(e.message)) + } + } + } + } + + fun observeCluster(streamCluster: StreamCluster) { + viewModelScope.launch(Dispatchers.IO) { + supervisorScope { + try { + if (streamCluster.hasNext()) { + val newCluster = + streamHelper.getNextStreamCluster(streamCluster.clusterNextPageUrl) + updateCluster(newCluster) + liveData.postValue(ViewState.Success(streamBundle)) + } else { + Log.i("End of cluster") + streamCluster.clusterNextPageUrl = String() + } + } catch (e: Exception) { + liveData.postValue(ViewState.Error(e.message)) + } + } + } + } + + private fun updateCluster(newCluster: StreamCluster) { + streamBundle.streamClusters[newCluster.id]?.apply { + clusterAppList.addAll(newCluster.clusterAppList) + clusterNextPageUrl = newCluster.clusterNextPageUrl + } + } +} diff --git a/app/src/main/java/com/aurora/store/viewmodel/homestream/EarlyAccessViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/homestream/EarlyAccessViewModel.kt new file mode 100644 index 000000000..162112b53 --- /dev/null +++ b/app/src/main/java/com/aurora/store/viewmodel/homestream/EarlyAccessViewModel.kt @@ -0,0 +1,45 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.viewmodel.homestream + +import android.app.Application +import com.aurora.gplayapi.helpers.StreamHelper +import com.aurora.store.data.ViewState + +class EarlyAccessAppsViewModel(application: Application) : BaseClusterViewModel(application) { + + init { + category = StreamHelper.Category.APPLICATION + type = StreamHelper.Type.EARLY_ACCESS + liveData.postValue(ViewState.Loading) + observe() + } +} + +class EarlyAccessGamesViewModel(application: Application) : + BaseClusterViewModel(application) { + + init { + category = StreamHelper.Category.GAME + type = StreamHelper.Type.EARLY_ACCESS + liveData.postValue(ViewState.Loading) + observe() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/viewmodel/homestream/ForYouViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/homestream/ForYouViewModel.kt new file mode 100644 index 000000000..2c1ef62f8 --- /dev/null +++ b/app/src/main/java/com/aurora/store/viewmodel/homestream/ForYouViewModel.kt @@ -0,0 +1,44 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.viewmodel.homestream + +import android.app.Application +import com.aurora.gplayapi.helpers.StreamHelper +import com.aurora.store.data.ViewState + +class AppsForYouViewModel(application: Application) : BaseClusterViewModel(application) { + + init { + category = StreamHelper.Category.APPLICATION + type = StreamHelper.Type.HOME + liveData.postValue(ViewState.Loading) + observe() + } +} + +class GamesForYouViewModel(application: Application) : BaseClusterViewModel(application) { + + init { + category = StreamHelper.Category.GAME + type = StreamHelper.Type.HOME + liveData.postValue(ViewState.Loading) + observe() + } +} diff --git a/app/src/main/java/com/aurora/store/viewmodel/review/ReviewViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/review/ReviewViewModel.kt new file mode 100644 index 000000000..280f4d676 --- /dev/null +++ b/app/src/main/java/com/aurora/store/viewmodel/review/ReviewViewModel.kt @@ -0,0 +1,75 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.viewmodel.review + +import android.app.Application +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.viewModelScope +import com.aurora.gplayapi.data.models.AuthData +import com.aurora.gplayapi.data.models.Review +import com.aurora.gplayapi.helpers.ReviewsHelper +import com.aurora.store.data.RequestState +import com.aurora.store.data.ViewState +import com.aurora.store.data.network.HttpClient +import com.aurora.store.data.providers.AuthProvider +import com.aurora.store.viewmodel.BaseAndroidViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.supervisorScope + +class ReviewViewModel(application: Application) : BaseAndroidViewModel(application) { + + var authData: AuthData = AuthProvider + .with(application) + .getAuthData() + + var reviewsHelper: ReviewsHelper = ReviewsHelper + .with(authData) + .using(HttpClient.getPreferredClient()) + + val liveData: MutableLiveData = MutableLiveData() + + private val reviews: MutableList = mutableListOf() + private var offset: Int = 0 + + fun fetchReview(packageName: String, filter: Review.Filter) { + viewModelScope.launch(Dispatchers.IO) { + supervisorScope { + try { + val newReviews = reviewsHelper.getReviews(packageName, filter, offset) + reviews.addAll(newReviews) + offset = reviews.size + liveData.postValue(ViewState.Success(reviews)) + } catch (e: Exception) { + requestState = RequestState.Pending + } + } + } + } + + fun reset() { + reviews.clear() + offset = 0 + } + + override fun observe() { + + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/viewmodel/sale/AppSalesViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/sale/AppSalesViewModel.kt new file mode 100644 index 000000000..9505fe3a1 --- /dev/null +++ b/app/src/main/java/com/aurora/store/viewmodel/sale/AppSalesViewModel.kt @@ -0,0 +1,71 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.viewmodel.sale + +import android.app.Application +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.viewModelScope +import com.aurora.gplayapi.data.models.App +import com.aurora.gplayapi.data.models.AuthData +import com.aurora.gplayapi.helpers.AppSalesHelper +import com.aurora.store.data.network.HttpClient +import com.aurora.store.data.providers.AuthProvider +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +class AppSalesViewModel(application: Application) : AndroidViewModel(application) { + + private val authData: AuthData = AuthProvider.with(application).getAuthData() + private val appSalesHelper: AppSalesHelper = + AppSalesHelper.with(authData).using(HttpClient.getPreferredClient()) + + val liveAppList: MutableLiveData> = MutableLiveData() + + private var page: Int = 0 + private val appList: MutableList = mutableListOf() + + init { + observeAppSales() + } + + private fun observeAppSales() { + appList.clear() + viewModelScope.launch(Dispatchers.IO) { + appList.addAll(getSearchResults()) + liveAppList.postValue(appList) + } + } + + private fun getSearchResults( + ): List { + return appSalesHelper.getAppsOnSale(page = page++, offer = 100) + } + + fun next() { + viewModelScope.launch(Dispatchers.IO) { + val newAppList = getSearchResults() + if (newAppList.isNotEmpty()) { + appList.addAll(getSearchResults()) + liveAppList.postValue(appList) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/viewmodel/search/SearchResultViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/search/SearchResultViewModel.kt new file mode 100644 index 000000000..8e01f4550 --- /dev/null +++ b/app/src/main/java/com/aurora/store/viewmodel/search/SearchResultViewModel.kt @@ -0,0 +1,95 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.viewmodel.search + +import android.app.Application +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.viewModelScope +import com.aurora.gplayapi.data.models.AuthData +import com.aurora.gplayapi.data.models.SearchBundle +import com.aurora.gplayapi.helpers.SearchHelper +import com.aurora.store.data.network.HttpClient +import com.aurora.store.data.providers.AuthProvider +import com.aurora.store.util.extensions.flushAndAdd +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.supervisorScope + +class SearchResultViewModel(application: Application) : AndroidViewModel(application) { + + private val authData: AuthData = AuthProvider + .with(application) + .getAuthData() + + private val searchHelper: SearchHelper = SearchHelper + .with(authData) + .using(HttpClient.getPreferredClient()) + + val liveData: MutableLiveData = MutableLiveData() + + private var searchBundle: SearchBundle = SearchBundle() + + fun observeSearchResults(query: String) { + //Clear old results + searchBundle.subBundles.clear() + searchBundle.appList.clear() + //Fetch new results + viewModelScope.launch(Dispatchers.IO) { + supervisorScope { + try { + searchBundle = search(query) + liveData.postValue(searchBundle) + } catch (e: Exception) { + + } + } + } + } + + private fun search( + query: String + ): SearchBundle { + return searchHelper.searchResults(query) + } + + @Synchronized + fun next(nextSubBundleSet: MutableSet) { + viewModelScope.launch(Dispatchers.IO) { + supervisorScope { + try { + if (nextSubBundleSet.isNotEmpty()) { + val newSearchBundle = searchHelper.next(nextSubBundleSet) + if (newSearchBundle.appList.isNotEmpty()) { + searchBundle.apply { + subBundles.flushAndAdd(newSearchBundle.subBundles) + appList.addAll(newSearchBundle.appList) + } + + liveData.postValue(searchBundle) + } + } + } catch (e: Exception) { + + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/viewmodel/search/SearchSuggestionViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/search/SearchSuggestionViewModel.kt new file mode 100644 index 000000000..374567175 --- /dev/null +++ b/app/src/main/java/com/aurora/store/viewmodel/search/SearchSuggestionViewModel.kt @@ -0,0 +1,57 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.viewmodel.search + +import android.app.Application +import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.viewModelScope +import com.aurora.gplayapi.SearchSuggestEntry +import com.aurora.gplayapi.data.models.AuthData +import com.aurora.gplayapi.helpers.SearchHelper +import com.aurora.store.data.network.HttpClient +import com.aurora.store.data.providers.AuthProvider +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch + +class SearchSuggestionViewModel(application: Application) : AndroidViewModel(application) { + + private val authData: AuthData = AuthProvider + .with(application) + .getAuthData() + + private val searchHelper: SearchHelper = SearchHelper + .with(authData) + .using(HttpClient.getPreferredClient()) + + val liveSearchSuggestions: MutableLiveData> = MutableLiveData() + + fun observeStreamBundles(query: String) { + viewModelScope.launch(Dispatchers.IO) { + liveSearchSuggestions.postValue(getSearchSuggestions(query)) + } + } + + private fun getSearchSuggestions( + query: String + ): List { + return searchHelper.searchSuggestions(query) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/aurora/store/viewmodel/subcategory/SubCategoryClusterViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/subcategory/SubCategoryClusterViewModel.kt new file mode 100644 index 000000000..cb76de896 --- /dev/null +++ b/app/src/main/java/com/aurora/store/viewmodel/subcategory/SubCategoryClusterViewModel.kt @@ -0,0 +1,126 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.viewmodel.subcategory + +import android.app.Application +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.viewModelScope +import com.aurora.gplayapi.data.models.AuthData +import com.aurora.gplayapi.data.models.StreamBundle +import com.aurora.gplayapi.data.models.StreamCluster +import com.aurora.gplayapi.helpers.CategoryHelper +import com.aurora.store.data.RequestState +import com.aurora.store.data.ViewState +import com.aurora.store.data.network.HttpClient +import com.aurora.store.data.providers.AuthProvider +import com.aurora.store.util.Log +import com.aurora.store.viewmodel.BaseAndroidViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.supervisorScope + +class SubCategoryClusterViewModel(application: Application) : BaseAndroidViewModel(application) { + + var authData: AuthData = AuthProvider.with(application).getAuthData() + var categoryHelper: CategoryHelper = CategoryHelper + .with(authData) + .using(HttpClient.getPreferredClient()) + + val liveData: MutableLiveData = MutableLiveData() + var streamBundle: StreamBundle = StreamBundle() + + lateinit var homeUrl: String + + init { + liveData.postValue(ViewState.Loading) + } + + private fun getCategoryStreamBundle( + nextPageUrl: String + ): StreamBundle { + return if (streamBundle.streamClusters.isEmpty()) + categoryHelper.getSubCategoryBundle(homeUrl) + else + categoryHelper.getSubCategoryBundle(nextPageUrl) + } + + fun observeCategory(homeUrl: String) { + this.homeUrl = homeUrl + observe() + } + + override fun observe() { + viewModelScope.launch(Dispatchers.IO) { + supervisorScope { + try { + if (!streamBundle.hasCluster() || streamBundle.hasNext()) { + //Fetch new stream bundle + val newBundle = getCategoryStreamBundle( + streamBundle.streamNextPageUrl + ) + + //Update old bundle + streamBundle.apply { + streamClusters.putAll(newBundle.streamClusters) + streamNextPageUrl = newBundle.streamNextPageUrl + } + + //Post updated to UI + liveData.postValue(ViewState.Success(streamBundle)) + } else { + Log.i("End of Bundle") + requestState = RequestState.Complete + } + } catch (e: Exception) { + e.printStackTrace() + requestState = RequestState.Pending + liveData.postValue(ViewState.Error(e.message)) + } + } + } + } + + fun observeCluster(streamCluster: StreamCluster) { + viewModelScope.launch(Dispatchers.IO) { + supervisorScope { + try { + if (streamCluster.hasNext()) { + val newCluster = + categoryHelper.getNextStreamCluster(streamCluster.clusterNextPageUrl) + updateCluster(newCluster) + liveData.postValue(ViewState.Success(streamBundle)) + } else { + Log.i("End of cluster") + streamCluster.clusterNextPageUrl = String() + } + } catch (e: Exception) { + liveData.postValue(ViewState.Error(e.message)) + } + } + } + } + + private fun updateCluster(newCluster: StreamCluster) { + streamBundle.streamClusters[newCluster.id]?.apply { + clusterAppList.addAll(newCluster.clusterAppList) + clusterNextPageUrl = newCluster.clusterNextPageUrl + } + } +} diff --git a/app/src/main/java/com/aurora/store/viewmodel/topchart/AppChartViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/topchart/AppChartViewModel.kt new file mode 100644 index 000000000..f4793556b --- /dev/null +++ b/app/src/main/java/com/aurora/store/viewmodel/topchart/AppChartViewModel.kt @@ -0,0 +1,60 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.viewmodel.topchart + +import android.app.Application +import com.aurora.gplayapi.helpers.TopChartsHelper + +class TopFreeAppChartViewModel(application: Application) : BaseChartViewModel(application) { + + init { + type = TopChartsHelper.Type.APPLICATION + chart = TopChartsHelper.Chart.TOP_SELLING_FREE + observe() + } +} + +class TopGrossingAppChartViewModel(application: Application) : BaseChartViewModel(application) { + + init { + type = TopChartsHelper.Type.APPLICATION + chart = TopChartsHelper.Chart.TOP_GROSSING + observe() + } +} + +class TrendingAppChartViewModel(application: Application) : BaseChartViewModel(application) { + + init { + type = TopChartsHelper.Type.APPLICATION + chart = TopChartsHelper.Chart.MOVERS_SHAKERS + observe() + } +} + +class TopPaidAppChartViewModel(application: Application) : BaseChartViewModel(application) { + + init { + type = TopChartsHelper.Type.APPLICATION + chart = TopChartsHelper.Chart.TOP_SELLING_PAID + observe() + } +} + diff --git a/app/src/main/java/com/aurora/store/viewmodel/topchart/BaseChartViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/topchart/BaseChartViewModel.kt new file mode 100644 index 000000000..cca4e9c2f --- /dev/null +++ b/app/src/main/java/com/aurora/store/viewmodel/topchart/BaseChartViewModel.kt @@ -0,0 +1,90 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.viewmodel.topchart + +import android.app.Application +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.viewModelScope +import com.aurora.gplayapi.data.models.AuthData +import com.aurora.gplayapi.data.models.StreamCluster +import com.aurora.gplayapi.helpers.TopChartsHelper +import com.aurora.store.data.RequestState +import com.aurora.store.data.network.HttpClient +import com.aurora.store.data.providers.AuthProvider +import com.aurora.store.viewmodel.BaseAndroidViewModel +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch +import kotlinx.coroutines.supervisorScope + +abstract class BaseChartViewModel(application: Application) : BaseAndroidViewModel(application) { + + private val authData: AuthData = AuthProvider.with(application).getAuthData() + private val topChartsHelper: TopChartsHelper = + TopChartsHelper.with(authData).using(HttpClient.getPreferredClient()) + + lateinit var type: TopChartsHelper.Type + lateinit var chart: TopChartsHelper.Chart + + val liveData: MutableLiveData = MutableLiveData() + var streamCluster: StreamCluster = StreamCluster() + + fun getStreamCluster( + type: TopChartsHelper.Type, + chart: TopChartsHelper.Chart + ): StreamCluster { + return topChartsHelper.getCluster(type, chart) + } + + override fun observe() { + viewModelScope.launch(Dispatchers.IO) { + try { + streamCluster = getStreamCluster(type, chart) + liveData.postValue(streamCluster) + } catch (e: Exception) { + requestState = RequestState.Pending + } + } + } + + fun nextCluster() { + viewModelScope.launch(Dispatchers.IO) { + supervisorScope { + try { + if (streamCluster.hasNext()) { + val newCluster = topChartsHelper.getNextStreamCluster( + streamCluster.clusterNextPageUrl + ) + + streamCluster.apply { + clusterAppList.addAll(newCluster.clusterAppList) + clusterNextPageUrl = newCluster.clusterNextPageUrl + } + + liveData.postValue(streamCluster) + } else { + requestState = RequestState.Complete + } + } catch (e: Exception) { + requestState = RequestState.Pending + } + } + } + } +} diff --git a/app/src/main/java/com/aurora/store/viewmodel/topchart/GameChartViewModel.kt b/app/src/main/java/com/aurora/store/viewmodel/topchart/GameChartViewModel.kt new file mode 100644 index 000000000..0b819252a --- /dev/null +++ b/app/src/main/java/com/aurora/store/viewmodel/topchart/GameChartViewModel.kt @@ -0,0 +1,60 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store.viewmodel.topchart + +import android.app.Application +import com.aurora.gplayapi.helpers.TopChartsHelper + +class TopFreeGameChartViewModel(application: Application) : BaseChartViewModel(application) { + + init { + type = TopChartsHelper.Type.GAME + chart = TopChartsHelper.Chart.TOP_SELLING_FREE + observe() + } +} + +class TopGrossingGameChartViewModel(application: Application) : BaseChartViewModel(application) { + + init { + type = TopChartsHelper.Type.GAME + chart = TopChartsHelper.Chart.TOP_GROSSING + observe() + } +} + +class TrendingGameChartViewModel(application: Application) : BaseChartViewModel(application) { + + init { + type = TopChartsHelper.Type.GAME + chart = TopChartsHelper.Chart.MOVERS_SHAKERS + observe() + } +} + +class TopPaidGameChartViewModel(application: Application) : BaseChartViewModel(application) { + + init { + type = TopChartsHelper.Type.GAME + chart = TopChartsHelper.Chart.TOP_SELLING_PAID + observe() + } +} + diff --git a/app/src/main/res/anim/fade_in.xml b/app/src/main/res/anim/fade_in.xml new file mode 100644 index 000000000..2023c0781 --- /dev/null +++ b/app/src/main/res/anim/fade_in.xml @@ -0,0 +1,24 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/anim/fade_out.xml b/app/src/main/res/anim/fade_out.xml new file mode 100644 index 000000000..408a3c18d --- /dev/null +++ b/app/src/main/res/anim/fade_out.xml @@ -0,0 +1,24 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/color-v21/chip_txt_selector.xml b/app/src/main/res/color-v21/chip_txt_selector.xml new file mode 100644 index 000000000..672bff308 --- /dev/null +++ b/app/src/main/res/color-v21/chip_txt_selector.xml @@ -0,0 +1,23 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/color/chip_stroke_selector.xml b/app/src/main/res/color/chip_stroke_selector.xml new file mode 100644 index 000000000..3c20a1261 --- /dev/null +++ b/app/src/main/res/color/chip_stroke_selector.xml @@ -0,0 +1,23 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/color/chip_surface_selector.xml b/app/src/main/res/color/chip_surface_selector.xml new file mode 100644 index 000000000..ac3d3d5f6 --- /dev/null +++ b/app/src/main/res/color/chip_surface_selector.xml @@ -0,0 +1,23 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/color/chip_txt_selector.xml b/app/src/main/res/color/chip_txt_selector.xml new file mode 100644 index 000000000..0f1477cdc --- /dev/null +++ b/app/src/main/res/color/chip_txt_selector.xml @@ -0,0 +1,23 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-v21/bg_bottomsheet.xml b/app/src/main/res/drawable-v21/bg_bottomsheet.xml new file mode 100644 index 000000000..277d05c23 --- /dev/null +++ b/app/src/main/res/drawable-v21/bg_bottomsheet.xml @@ -0,0 +1,29 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-v21/bg_outlined_padded.xml b/app/src/main/res/drawable-v21/bg_outlined_padded.xml new file mode 100644 index 000000000..5b14e0853 --- /dev/null +++ b/app/src/main/res/drawable-v21/bg_outlined_padded.xml @@ -0,0 +1,33 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-v21/bg_placeholder.xml b/app/src/main/res/drawable-v21/bg_placeholder.xml new file mode 100644 index 000000000..0730630a8 --- /dev/null +++ b/app/src/main/res/drawable-v21/bg_placeholder.xml @@ -0,0 +1,25 @@ + + + + + + + diff --git a/app/src/main/res/drawable-v21/bg_rounded.xml b/app/src/main/res/drawable-v21/bg_rounded.xml new file mode 100644 index 000000000..b90a90041 --- /dev/null +++ b/app/src/main/res/drawable-v21/bg_rounded.xml @@ -0,0 +1,25 @@ + + + + + + + diff --git a/app/src/main/res/drawable-v21/bg_search.xml b/app/src/main/res/drawable-v21/bg_search.xml new file mode 100644 index 000000000..c66a75909 --- /dev/null +++ b/app/src/main/res/drawable-v21/bg_search.xml @@ -0,0 +1,27 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-v21/bg_sheet.xml b/app/src/main/res/drawable-v21/bg_sheet.xml new file mode 100644 index 000000000..c1bc5fbcd --- /dev/null +++ b/app/src/main/res/drawable-v21/bg_sheet.xml @@ -0,0 +1,27 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-v21/divider.xml b/app/src/main/res/drawable-v21/divider.xml new file mode 100644 index 000000000..084d1feba --- /dev/null +++ b/app/src/main/res/drawable-v21/divider.xml @@ -0,0 +1,26 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-v21/divider_alt.xml b/app/src/main/res/drawable-v21/divider_alt.xml new file mode 100644 index 000000000..386cb0ec6 --- /dev/null +++ b/app/src/main/res/drawable-v21/divider_alt.xml @@ -0,0 +1,26 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-v21/divider_line.xml b/app/src/main/res/drawable-v21/divider_line.xml new file mode 100644 index 000000000..1211d8fba --- /dev/null +++ b/app/src/main/res/drawable-v21/divider_line.xml @@ -0,0 +1,26 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-v21/ic_cancel.xml b/app/src/main/res/drawable-v21/ic_cancel.xml new file mode 100644 index 000000000..fc5191f65 --- /dev/null +++ b/app/src/main/res/drawable-v21/ic_cancel.xml @@ -0,0 +1,30 @@ + + + + + diff --git a/app/src/main/res/drawable-v21/ic_shield.xml b/app/src/main/res/drawable-v21/ic_shield.xml new file mode 100644 index 000000000..bc8ccccbc --- /dev/null +++ b/app/src/main/res/drawable-v21/ic_shield.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable-v21/tab_default_onboarding.xml b/app/src/main/res/drawable-v21/tab_default_onboarding.xml new file mode 100644 index 000000000..c8f8e6fb6 --- /dev/null +++ b/app/src/main/res/drawable-v21/tab_default_onboarding.xml @@ -0,0 +1,30 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-v21/tab_indicator.xml b/app/src/main/res/drawable-v21/tab_indicator.xml new file mode 100644 index 000000000..9b7a64f9c --- /dev/null +++ b/app/src/main/res/drawable-v21/tab_indicator.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-v21/tab_selected_onboarding.xml b/app/src/main/res/drawable-v21/tab_selected_onboarding.xml new file mode 100644 index 000000000..929f249f6 --- /dev/null +++ b/app/src/main/res/drawable-v21/tab_selected_onboarding.xml @@ -0,0 +1,30 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-v21/tab_selector_onboarding.xml b/app/src/main/res/drawable-v21/tab_selector_onboarding.xml new file mode 100644 index 000000000..e4e61187f --- /dev/null +++ b/app/src/main/res/drawable-v21/tab_selector_onboarding.xml @@ -0,0 +1,23 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-v24/bg_outlined_rounded.xml b/app/src/main/res/drawable-v24/bg_outlined_rounded.xml new file mode 100644 index 000000000..18a82ccd1 --- /dev/null +++ b/app/src/main/res/drawable-v24/bg_outlined_rounded.xml @@ -0,0 +1,31 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-v24/bg_progressbar.xml b/app/src/main/res/drawable-v24/bg_progressbar.xml new file mode 100644 index 000000000..836dd351b --- /dev/null +++ b/app/src/main/res/drawable-v24/bg_progressbar.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-v24/ic_arrow_left.xml b/app/src/main/res/drawable-v24/ic_arrow_left.xml new file mode 100644 index 000000000..424dcfaf0 --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_arrow_left.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 000000000..1957cd575 --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,162 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/bg_bottomsheet.xml b/app/src/main/res/drawable/bg_bottomsheet.xml new file mode 100644 index 000000000..04b17158c --- /dev/null +++ b/app/src/main/res/drawable/bg_bottomsheet.xml @@ -0,0 +1,29 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_cancel.xml b/app/src/main/res/drawable/bg_cancel.xml new file mode 100644 index 000000000..c5d625d38 --- /dev/null +++ b/app/src/main/res/drawable/bg_cancel.xml @@ -0,0 +1,25 @@ + + + + + + + diff --git a/app/src/main/res/drawable/bg_changelog.xml b/app/src/main/res/drawable/bg_changelog.xml new file mode 100644 index 000000000..03585a476 --- /dev/null +++ b/app/src/main/res/drawable/bg_changelog.xml @@ -0,0 +1,30 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/bg_circle.xml b/app/src/main/res/drawable/bg_circle.xml new file mode 100644 index 000000000..ab74b3dcb --- /dev/null +++ b/app/src/main/res/drawable/bg_circle.xml @@ -0,0 +1,24 @@ + + + + + + diff --git a/app/src/main/res/drawable/bg_gradient_linear.xml b/app/src/main/res/drawable/bg_gradient_linear.xml new file mode 100644 index 000000000..08e67c70f --- /dev/null +++ b/app/src/main/res/drawable/bg_gradient_linear.xml @@ -0,0 +1,30 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_outlined_padded.xml b/app/src/main/res/drawable/bg_outlined_padded.xml new file mode 100644 index 000000000..ddc12705f --- /dev/null +++ b/app/src/main/res/drawable/bg_outlined_padded.xml @@ -0,0 +1,33 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_outlined_rounded.xml b/app/src/main/res/drawable/bg_outlined_rounded.xml new file mode 100644 index 000000000..18a82ccd1 --- /dev/null +++ b/app/src/main/res/drawable/bg_outlined_rounded.xml @@ -0,0 +1,31 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_placeholder.xml b/app/src/main/res/drawable/bg_placeholder.xml new file mode 100644 index 000000000..87a42b0b8 --- /dev/null +++ b/app/src/main/res/drawable/bg_placeholder.xml @@ -0,0 +1,25 @@ + + + + + + + diff --git a/app/src/main/res/drawable/bg_progressbar.xml b/app/src/main/res/drawable/bg_progressbar.xml new file mode 100644 index 000000000..8cfaa0b3b --- /dev/null +++ b/app/src/main/res/drawable/bg_progressbar.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_round_solid.xml b/app/src/main/res/drawable/bg_round_solid.xml new file mode 100644 index 000000000..45e4fbb09 --- /dev/null +++ b/app/src/main/res/drawable/bg_round_solid.xml @@ -0,0 +1,24 @@ + + + + + + diff --git a/app/src/main/res/drawable/bg_rounded.xml b/app/src/main/res/drawable/bg_rounded.xml new file mode 100644 index 000000000..302e6703c --- /dev/null +++ b/app/src/main/res/drawable/bg_rounded.xml @@ -0,0 +1,25 @@ + + + + + + + diff --git a/app/src/main/res/drawable/bg_rounded_outlined.xml b/app/src/main/res/drawable/bg_rounded_outlined.xml new file mode 100644 index 000000000..8f5073d55 --- /dev/null +++ b/app/src/main/res/drawable/bg_rounded_outlined.xml @@ -0,0 +1,27 @@ + + + + + + + diff --git a/app/src/main/res/drawable/bg_search.xml b/app/src/main/res/drawable/bg_search.xml new file mode 100644 index 000000000..68150025f --- /dev/null +++ b/app/src/main/res/drawable/bg_search.xml @@ -0,0 +1,27 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_sharp.xml b/app/src/main/res/drawable/bg_sharp.xml new file mode 100644 index 000000000..2705eb864 --- /dev/null +++ b/app/src/main/res/drawable/bg_sharp.xml @@ -0,0 +1,24 @@ + + + + + + diff --git a/app/src/main/res/drawable/bg_sheet.xml b/app/src/main/res/drawable/bg_sheet.xml new file mode 100644 index 000000000..b4fdb28e0 --- /dev/null +++ b/app/src/main/res/drawable/bg_sheet.xml @@ -0,0 +1,26 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/custom_ripple_circular_solid.xml b/app/src/main/res/drawable/custom_ripple_circular_solid.xml new file mode 100644 index 000000000..5a8f1c391 --- /dev/null +++ b/app/src/main/res/drawable/custom_ripple_circular_solid.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/custom_ripple_rounded_solid.xml b/app/src/main/res/drawable/custom_ripple_rounded_solid.xml new file mode 100644 index 000000000..538796651 --- /dev/null +++ b/app/src/main/res/drawable/custom_ripple_rounded_solid.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/divider.xml b/app/src/main/res/drawable/divider.xml new file mode 100644 index 000000000..084d1feba --- /dev/null +++ b/app/src/main/res/drawable/divider.xml @@ -0,0 +1,26 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/divider_alt.xml b/app/src/main/res/drawable/divider_alt.xml new file mode 100644 index 000000000..386cb0ec6 --- /dev/null +++ b/app/src/main/res/drawable/divider_alt.xml @@ -0,0 +1,26 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/divider_line.xml b/app/src/main/res/drawable/divider_line.xml new file mode 100644 index 000000000..ef81d3268 --- /dev/null +++ b/app/src/main/res/drawable/divider_line.xml @@ -0,0 +1,25 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_account_manager.xml b/app/src/main/res/drawable/ic_account_manager.xml new file mode 100644 index 000000000..30d46e6d9 --- /dev/null +++ b/app/src/main/res/drawable/ic_account_manager.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_anonymous.xml b/app/src/main/res/drawable/ic_anonymous.xml new file mode 100644 index 000000000..778348291 --- /dev/null +++ b/app/src/main/res/drawable/ic_anonymous.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_apps.xml b/app/src/main/res/drawable/ic_apps.xml new file mode 100644 index 000000000..2a94e7494 --- /dev/null +++ b/app/src/main/res/drawable/ic_apps.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_arrow_back.xml b/app/src/main/res/drawable/ic_arrow_back.xml new file mode 100644 index 000000000..735fcbe1d --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_back.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_arrow_download.xml b/app/src/main/res/drawable/ic_arrow_download.xml new file mode 100644 index 000000000..61f170a41 --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_download.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_arrow_left.xml b/app/src/main/res/drawable/ic_arrow_left.xml new file mode 100644 index 000000000..424dcfaf0 --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_left.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_arrow_right.xml b/app/src/main/res/drawable/ic_arrow_right.xml new file mode 100644 index 000000000..5d168b129 --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_right.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_bhim.xml b/app/src/main/res/drawable/ic_bhim.xml new file mode 100644 index 000000000..d0d87acf1 --- /dev/null +++ b/app/src/main/res/drawable/ic_bhim.xml @@ -0,0 +1,34 @@ + + + + + + + diff --git a/app/src/main/res/drawable/ic_bitcoin_bch.xml b/app/src/main/res/drawable/ic_bitcoin_bch.xml new file mode 100644 index 000000000..c7a393477 --- /dev/null +++ b/app/src/main/res/drawable/ic_bitcoin_bch.xml @@ -0,0 +1,33 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_bitcoin_btc.xml b/app/src/main/res/drawable/ic_bitcoin_btc.xml new file mode 100644 index 000000000..a66301a7d --- /dev/null +++ b/app/src/main/res/drawable/ic_bitcoin_btc.xml @@ -0,0 +1,33 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_blacklist.xml b/app/src/main/res/drawable/ic_blacklist.xml new file mode 100644 index 000000000..fc72c8639 --- /dev/null +++ b/app/src/main/res/drawable/ic_blacklist.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_cancel.xml b/app/src/main/res/drawable/ic_cancel.xml new file mode 100644 index 000000000..fc5191f65 --- /dev/null +++ b/app/src/main/res/drawable/ic_cancel.xml @@ -0,0 +1,30 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_check.xml b/app/src/main/res/drawable/ic_check.xml new file mode 100644 index 000000000..9f64100ec --- /dev/null +++ b/app/src/main/res/drawable/ic_check.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_child.xml b/app/src/main/res/drawable/ic_child.xml new file mode 100644 index 000000000..7ae653fb0 --- /dev/null +++ b/app/src/main/res/drawable/ic_child.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_code.xml b/app/src/main/res/drawable/ic_code.xml new file mode 100644 index 000000000..cfa80ff90 --- /dev/null +++ b/app/src/main/res/drawable/ic_code.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_dashboard_black_24dp.xml b/app/src/main/res/drawable/ic_dashboard_black_24dp.xml new file mode 100644 index 000000000..b3768f763 --- /dev/null +++ b/app/src/main/res/drawable/ic_dashboard_black_24dp.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_disclaimer.xml b/app/src/main/res/drawable/ic_disclaimer.xml new file mode 100644 index 000000000..0a69e4999 --- /dev/null +++ b/app/src/main/res/drawable/ic_disclaimer.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_disk.xml b/app/src/main/res/drawable/ic_disk.xml new file mode 100644 index 000000000..e511eb188 --- /dev/null +++ b/app/src/main/res/drawable/ic_disk.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_download.xml b/app/src/main/res/drawable/ic_download.xml new file mode 100644 index 000000000..3bd89f1cd --- /dev/null +++ b/app/src/main/res/drawable/ic_download.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_download_cancel.png b/app/src/main/res/drawable/ic_download_cancel.png new file mode 100644 index 0000000000000000000000000000000000000000..c7e27e4588b7d3c0754698fe234579152a8cf01a GIT binary patch literal 142 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_+iP)`@f5R21KC+!quP~c$M&be#D z{~j$J4`$~3fqK&f%F=&4`0Qb^EtOY&F6;6M;zv)2{L$I+XvLeEt3Mq{*J$bY5_s<= rF`adzYGFp<+D{LkT%R!i{p=OC`c4Ne%q-Y|7BG0a`njxgN@xNANNh8d literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/ic_download_fail.png b/app/src/main/res/drawable/ic_download_fail.png new file mode 100644 index 0000000000000000000000000000000000000000..799de758ce382579f6ad4c83786d12ec78e1bf5f GIT binary patch literal 251 zcmVST5JjIX(ZL`v1`{Na$PiF?4w5lKCUS!UQ;>@4rvw{>1X1!`-TA)mpZyR1jfg7^ z9d8C+JSd2^$}jXNI31_;V#5s~9F~-rm=udch#Ipb+z}&XaAhbN5p`I8MvUaEy>mw7 z3ybIx)hr`4iz-AnAHP62PmE76I7Bfeo(Sv7@CjfM%@nqXY?F{7+9@0m$tEE|3||nI zl0pQMmP3aKhTi?GyFYHXH4d2XfW3Dw(4iIm1wONtf49Su?D7Br002ovPDHLkV1f>3 BUVZ=o literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/ic_download_manager.xml b/app/src/main/res/drawable/ic_download_manager.xml new file mode 100644 index 000000000..1ee8c3bfb --- /dev/null +++ b/app/src/main/res/drawable/ic_download_manager.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_download_pause.png b/app/src/main/res/drawable/ic_download_pause.png new file mode 100644 index 0000000000000000000000000000000000000000..c15c5ef14550700103295a25ff3ffb8e8b6dfbf0 GIT binary patch literal 83 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM0wlfaz7_*1DNh&25R22v2@)S4*o!=R)So4B e)u6(pM*t`_A%9}*+``L1RScf4elF{r5}E)D@)m>u literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable/ic_ethereum_eth.xml b/app/src/main/res/drawable/ic_ethereum_eth.xml new file mode 100644 index 000000000..c3c463484 --- /dev/null +++ b/app/src/main/res/drawable/ic_ethereum_eth.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_expand.xml b/app/src/main/res/drawable/ic_expand.xml new file mode 100644 index 000000000..ebdd652f4 --- /dev/null +++ b/app/src/main/res/drawable/ic_expand.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_faq.xml b/app/src/main/res/drawable/ic_faq.xml new file mode 100644 index 000000000..586c8aa1d --- /dev/null +++ b/app/src/main/res/drawable/ic_faq.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_fdroid.xml b/app/src/main/res/drawable/ic_fdroid.xml new file mode 100644 index 000000000..e2e6b4cb7 --- /dev/null +++ b/app/src/main/res/drawable/ic_fdroid.xml @@ -0,0 +1,134 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_filter.xml b/app/src/main/res/drawable/ic_filter.xml new file mode 100644 index 000000000..f9b7f4e0c --- /dev/null +++ b/app/src/main/res/drawable/ic_filter.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_games.xml b/app/src/main/res/drawable/ic_games.xml new file mode 100644 index 000000000..10158d457 --- /dev/null +++ b/app/src/main/res/drawable/ic_games.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_gitlab.xml b/app/src/main/res/drawable/ic_gitlab.xml new file mode 100644 index 000000000..8ece91164 --- /dev/null +++ b/app/src/main/res/drawable/ic_gitlab.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_google.xml b/app/src/main/res/drawable/ic_google.xml new file mode 100644 index 000000000..25a6468f0 --- /dev/null +++ b/app/src/main/res/drawable/ic_google.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_home_black_24dp.xml b/app/src/main/res/drawable/ic_home_black_24dp.xml new file mode 100644 index 000000000..76f62b81a --- /dev/null +++ b/app/src/main/res/drawable/ic_home_black_24dp.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_installation.xml b/app/src/main/res/drawable/ic_installation.xml new file mode 100644 index 000000000..6dee6b603 --- /dev/null +++ b/app/src/main/res/drawable/ic_installation.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 000000000..200823d5f --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,188 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 000000000..97be87666 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_libera_pay.xml b/app/src/main/res/drawable/ic_libera_pay.xml new file mode 100644 index 000000000..f3f32d693 --- /dev/null +++ b/app/src/main/res/drawable/ic_libera_pay.xml @@ -0,0 +1,34 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_license.xml b/app/src/main/res/drawable/ic_license.xml new file mode 100644 index 000000000..2b7b0028c --- /dev/null +++ b/app/src/main/res/drawable/ic_license.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_logo.xml b/app/src/main/res/drawable/ic_logo.xml new file mode 100644 index 000000000..edc14a840 --- /dev/null +++ b/app/src/main/res/drawable/ic_logo.xml @@ -0,0 +1,156 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_logout.xml b/app/src/main/res/drawable/ic_logout.xml new file mode 100644 index 000000000..4066409bc --- /dev/null +++ b/app/src/main/res/drawable/ic_logout.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_mail.xml b/app/src/main/res/drawable/ic_mail.xml new file mode 100644 index 000000000..68ebd56ab --- /dev/null +++ b/app/src/main/res/drawable/ic_mail.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_map.xml b/app/src/main/res/drawable/ic_map.xml new file mode 100644 index 000000000..e6b08e1af --- /dev/null +++ b/app/src/main/res/drawable/ic_map.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_map_marker.xml b/app/src/main/res/drawable/ic_map_marker.xml new file mode 100644 index 000000000..dfb9ed5cc --- /dev/null +++ b/app/src/main/res/drawable/ic_map_marker.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_menu_about.xml b/app/src/main/res/drawable/ic_menu_about.xml new file mode 100644 index 000000000..27c90d4b7 --- /dev/null +++ b/app/src/main/res/drawable/ic_menu_about.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_menu_settings.xml b/app/src/main/res/drawable/ic_menu_settings.xml new file mode 100644 index 000000000..93cdcf041 --- /dev/null +++ b/app/src/main/res/drawable/ic_menu_settings.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_menu_unfold.xml b/app/src/main/res/drawable/ic_menu_unfold.xml new file mode 100644 index 000000000..b0ebfab76 --- /dev/null +++ b/app/src/main/res/drawable/ic_menu_unfold.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_network.xml b/app/src/main/res/drawable/ic_network.xml new file mode 100644 index 000000000..10503ce41 --- /dev/null +++ b/app/src/main/res/drawable/ic_network.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_notification_outlined.xml b/app/src/main/res/drawable/ic_notification_outlined.xml new file mode 100644 index 000000000..d0d20dd56 --- /dev/null +++ b/app/src/main/res/drawable/ic_notification_outlined.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_notifications_black_24dp.xml b/app/src/main/res/drawable/ic_notifications_black_24dp.xml new file mode 100644 index 000000000..bf3240f40 --- /dev/null +++ b/app/src/main/res/drawable/ic_notifications_black_24dp.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_paypal.xml b/app/src/main/res/drawable/ic_paypal.xml new file mode 100644 index 000000000..590ecc0f2 --- /dev/null +++ b/app/src/main/res/drawable/ic_paypal.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_privacy.xml b/app/src/main/res/drawable/ic_privacy.xml new file mode 100644 index 000000000..fb7c6dd99 --- /dev/null +++ b/app/src/main/res/drawable/ic_privacy.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_round_search.xml b/app/src/main/res/drawable/ic_round_search.xml new file mode 100644 index 000000000..1e1f8fb8d --- /dev/null +++ b/app/src/main/res/drawable/ic_round_search.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_sale.xml b/app/src/main/res/drawable/ic_sale.xml new file mode 100644 index 000000000..b20cefa30 --- /dev/null +++ b/app/src/main/res/drawable/ic_sale.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_search_append.xml b/app/src/main/res/drawable/ic_search_append.xml new file mode 100644 index 000000000..8d6dcdbb9 --- /dev/null +++ b/app/src/main/res/drawable/ic_search_append.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_search_suggestion.xml b/app/src/main/res/drawable/ic_search_suggestion.xml new file mode 100644 index 000000000..f6aa97dd4 --- /dev/null +++ b/app/src/main/res/drawable/ic_search_suggestion.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_send.xml b/app/src/main/res/drawable/ic_send.xml new file mode 100644 index 000000000..aa9d0e168 --- /dev/null +++ b/app/src/main/res/drawable/ic_send.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_share.xml b/app/src/main/res/drawable/ic_share.xml new file mode 100644 index 000000000..33b3d3dc9 --- /dev/null +++ b/app/src/main/res/drawable/ic_share.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_shield.xml b/app/src/main/res/drawable/ic_shield.xml new file mode 100644 index 000000000..bc8ccccbc --- /dev/null +++ b/app/src/main/res/drawable/ic_shield.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_size.xml b/app/src/main/res/drawable/ic_size.xml new file mode 100644 index 000000000..1ebe7df2b --- /dev/null +++ b/app/src/main/res/drawable/ic_size.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_star.xml b/app/src/main/res/drawable/ic_star.xml new file mode 100644 index 000000000..09f84fe62 --- /dev/null +++ b/app/src/main/res/drawable/ic_star.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_telegram.xml b/app/src/main/res/drawable/ic_telegram.xml new file mode 100644 index 000000000..2fa18d9e2 --- /dev/null +++ b/app/src/main/res/drawable/ic_telegram.xml @@ -0,0 +1,37 @@ + + + + + + + + diff --git a/app/src/main/res/drawable/ic_ui.xml b/app/src/main/res/drawable/ic_ui.xml new file mode 100644 index 000000000..306275a54 --- /dev/null +++ b/app/src/main/res/drawable/ic_ui.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_updates.xml b/app/src/main/res/drawable/ic_updates.xml new file mode 100644 index 000000000..a2b6eafa3 --- /dev/null +++ b/app/src/main/res/drawable/ic_updates.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_xda.xml b/app/src/main/res/drawable/ic_xda.xml new file mode 100644 index 000000000..cbf6ad525 --- /dev/null +++ b/app/src/main/res/drawable/ic_xda.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/messages.xml b/app/src/main/res/drawable/messages.xml new file mode 100644 index 000000000..db7cc45d8 --- /dev/null +++ b/app/src/main/res/drawable/messages.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/progressbar_bg.xml b/app/src/main/res/drawable/progressbar_bg.xml new file mode 100644 index 000000000..ef4508896 --- /dev/null +++ b/app/src/main/res/drawable/progressbar_bg.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/sync.xml b/app/src/main/res/drawable/sync.xml new file mode 100644 index 000000000..88c1d803b --- /dev/null +++ b/app/src/main/res/drawable/sync.xml @@ -0,0 +1,28 @@ + + + + + diff --git a/app/src/main/res/drawable/tab_default_onboarding.xml b/app/src/main/res/drawable/tab_default_onboarding.xml new file mode 100644 index 000000000..0dfeea04f --- /dev/null +++ b/app/src/main/res/drawable/tab_default_onboarding.xml @@ -0,0 +1,30 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/tab_indicator.xml b/app/src/main/res/drawable/tab_indicator.xml new file mode 100644 index 000000000..b38314d05 --- /dev/null +++ b/app/src/main/res/drawable/tab_indicator.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/tab_selected_onboarding.xml b/app/src/main/res/drawable/tab_selected_onboarding.xml new file mode 100644 index 000000000..241c10e29 --- /dev/null +++ b/app/src/main/res/drawable/tab_selected_onboarding.xml @@ -0,0 +1,30 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/tab_selector_onboarding.xml b/app/src/main/res/drawable/tab_selector_onboarding.xml new file mode 100644 index 000000000..e4e61187f --- /dev/null +++ b/app/src/main/res/drawable/tab_selector_onboarding.xml @@ -0,0 +1,23 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/font/gilroy_extra_bold.otf b/app/src/main/res/font/gilroy_extra_bold.otf new file mode 100644 index 0000000000000000000000000000000000000000..578826317e3503b3201daf07bd4dca490c076068 GIT binary patch literal 54776 zcmdSAcU)9Q_c%QF?%sQsSzNJP#9i<1f}n`9A|UoIiX979P z6ck0o-iJOxrL+M#$wF^pB29kI5SMt&2ajK~XrEoDdW5B3$kWh0Bma zlA(f+<6NMB8I-+~({slarrJ^wVq6i@6{g3G%VPb)p$VOa#An2$CoGAlXA#295z>60 zm6?jx0yOxe$V-9Msg>$gawc>*=)Qo)Jdf93Bxynm&Q*C_MQ zAXu^Tk9mWl*g}N^Wrqk8}*`e?v9dzw+u({-2T#wboQc=8qFQ56109 zH5m`o8d4zR@#iH-4*+iF%>R4`aAUSD%pn{4LSpXyE4@KAHNnuf;=djE!9SK z)wX$nzc-ABq^S*X62Kp)Zm2f*!OTPzZps*d9ud%{;QMc>H_Y#UNScGtClkgy zlbOi|+|J|*c?feE_SwG@YlS}3QB^gqs?4Dba|rk!Qyt(Vi@1L$I0xvzCFUm3=%d8O z!oKbIcVb%teg8_z8SMsp5Y8WS9rA9__6YLV zur7Dlqp&Af59sTm?o-&uuz!`k3j12q82VL#c7WNYFyG7257wZ}@x%U5<_CL0*|+K* z&O`x_2WTdu0Ol3!Blw<*N1+J=j1$~u%FjgI(40lK}^lJ}!`wzLYCtJhbX$|*ZYc|lCn2}Ic(yxI3uLQUh zd`d!p08bUUSqG9g&@dTjwj1iAfIkYIV*&1rLLfQ$=gj}w#|Y*bsHQ4v?hEsCrs|M? zoSgl9;SBgfe_xF^BtMwH@-3J%$OJRH8QRidpLK^eH{`4R63T^;W zfd7hqquKNR6++rC-*@;kS{tuwJfOk+^$)Zl4I84dA`a=;SwK64dN^}tIM5FE8R%Sp zbw;f40R$kT$DrP*J+e|(WBs!^y06*87_0~_RQfP)Kp#P%X~9uN9!$Lt-vxcF5@ryO znpeUse57q9tU)H!wG!4MHxye5bEq}Su7r80-&_gnKxy1x3A=m*>rqwE1{DntAy*aF zzy+%;CJWt!c_}c1tW1wem_-IAwG!6AJ(pJrYoSj`CCs65%(+UK|EOMv>VVvG;&k~4 z*28{@tE_kZ2zEoR?9|HoYN$HuqpW_n7tbwEl+ zYUUW5wL@B(Rqc~w%}&Tk$R3jrZ|jA6`QXmRIO;89xvCwKo{ZI^w zhra31FB{4OAkRRlP%{SEpxgna!H@MLJUMD90p*|ss2u~pe0K><0Rp3Ox`|*nYbG_;*QqD3Ywr1t2s-x^+wahazw7DR{{Gj$k zf91~7|Lr&P1G9QFgUlADoQY-Pn0O|ENo10kWG00f z$)qwHneEI2<}P!e*~+|!@4KQZs5){-79g58ss{^ftZu7js0}Qt8|n@W?T-cn0b|we zm(J{F)-!upS7rxW7mY#_&_py1eTu$DbI?4r0Ifu;&}y_E6@rA>iFTts=m7d2okEw; zWpo|gKzEs)EYIv>euhC}#wDbCG434J<-OSq;mwTDA`B3AcwHb!L8L9y5=ahs-7B z1@n@5&OBpcn5POgnP@Z~ZD&fE&CC<#H|9L^6Vx4I4l~8j$Ri9}whP+99ANe{$C%^H z3Fahohp9lfQ7W^7Ima9U&F~5Og>_*HnN!U7pkHZGZNwrYs)Opoz0m+Q1bN&JaCJaK z&`=bK7P3~>m-S&K){CvrT3Bzk9&1KgPWj$ESS=5?Ph&I*4t6oDa3>=$H5e09m#NP* zWSTI|n6^w9(~B7fd`x8~F>_fHdz`(k(Q0%WS50+|uV$WRgXW;tRa;dnYK_{)+NRo; z+CJJy?Fj8R<{9SK)|ytM)ok^$*0Tm!TUt9?i>>=S-95cLZJxtD7kjZ@oL5z^>Rv`K zvsXi}Ft2E@1fNrs{qs+lopRK^Lq)*NGPH-mj5}jy>M?$djR|2|FddlgOgJ+fc2)+= z%7Z<|Ueh2AufZ^@dX=+cv<9u4)?I7T253XHt+ai$!?knFQ_QcdHLM=iI##PXs}}#5 z)iBRFm9wh$(X8TOR*X6;nArd7U)zs+{mH+7UGYQ3j*9P`wGPl25OTc!xDP`2w_v_xTdGGDL2lsB>>x$6b-|jxT3#0B{zI*!a!MiK&PC@8a5OimNi=|9& zphPsBwhWdBT6nRISQ{I_hOo`qj%+wPkWFJJu`q&N!LDW3v0K=k>Mak$m49qGdzyte z7xoJK9s~uiv1vMMx@f{Q{WJrhJV2vp7yt236XvW^`uz1z(-Znb`v13lfU^l_n>oW= zVg;6AJy{k`=o2{OK5*(EvvrskEMf{-3!K_j%u41vrW8&Q3nvk)azzi4r4GoJ+Hfix zR-VclAZa?NCo>eaXKtg;a59ziISfR47&;19U^a;Ne2_tTARnfH1o}*sLCZi2DKh92 zkV2&(hqizW+REHP+d&TfphzOnh<1T=5a5E)z;&vFOVa?Sxf+@Zn$u)B+tZPVW*`ZD z1=`sc$c$zq4>S{bqHj=L^eys2bCD04j~b!npz*IjexL;^r!Ek!fvc$i1);TYIjw`M zYy)ZuXR!s^2$xzhs0L+lEpJm~6|7Y%R7XYhbIeB3lhClUIxf`i*g8sxs9Fa}0MGQ)zV{VRHko}uUHCHfuN z!IGiKfhy2@^e2NDhS4xuhG#gCbvj1RxPok{2GXS#V`V%UFOYrpK>9Ui0zm!+GC@o* z(-b65Gmtqgnbu4zrVYr%4opX;J=2xx39``_PMH-jGcMSyk!VO83*$R^M-lH$Z%uaQtl5$)+;x+ z^6$^>z?J{YMnZ7suk4< zZ&{t`4#Qys8DOKBU}H3dOW1h@_XhS2gR6HGY|F`T(S8M6aV2but-$ns=p<~v>%hF9 zfY35}4+O0aM63hk^HZ;lP*p$=12teIGnyICOl7`czF`)@fhhnrVLP)24%Kny0&@*i zg2$jnfP4jlR0TGB0n=@4OSV1Rh3&^iu(50ko5_x5r?Q{1U$aZum23f9%x+^3!c}yZ zy~JK;AFzI2~7&bLS+^n`_7gaxJ*_ zTvx6)7r{kxaok956gQ5W%zesz#m(cEa;v!w+!k&Jx1al-JI!6CCKgKn#CkM4WjS=~k5b=^JPFS?gHyNkxf;8N42 zj*G8L6PMO5on3mm3~(9d67Q1glI=3lWxC53F7sWMxfHk*yKHya>+-$JS(hJO?zlX5 zdFJxk#eq5Oifdpa_Qbxp32u(t;V!rr9)O48g?K&Q(jhiGVN61Y^q9Ep%#041Ntqc5 zshwitM&~AUii?4&PVt$!F>!GT8M&R+a%bollN+kSp&!~po#R8D4|9x;|9g2B!DHFk~1hNJ7!G6kiT1os1-xi0Yg8~ zB1WZJj1zN=6LXx}Cr+JC+=rg=YB^rzM1l$@d}vE>j!$roPf~FveYCu!4?UA%p((bs z%(%1x{d%N0hov}&rG7LZO`SrT`kAzk>NC{(47EPvqxyu5q|A>=mFdKst#;2=yJvsY zJx8t2sboaXM=iN9zg)GylMz8qMsytgG4|0;?4zCY9<4B9tcr2$-{c6Lo-d zcwpsRoL>m2q*<`K@j7rU*b@}=&*BHttGo;f`DbhMe->N( zv)JLE#eeRDpf3Mt4gTjeg8%uU;D3H7_@5sN?(|Po|NL%n=*O)=EmU#^b!d^5lAn;B zY0FU_>M|14Ja??KoSU4T;H*i^9G$H;C8msV*5ss&Q!8@dX3bEc1m*Ekg)&kq5viDS zRlK>03Rd{7;>=A{F+!%`%S}|U!EY5;ZlV)YZla1OH&MkBpOKla%umS`Olq#;QXmzZ z3Mlv#K*6ZyDozD*VpSj&uL7u;Ge)O_4v=6|as{)RtC$r?#jFAfW(80%tGS97V{T$fMoMm8N=9x1TqtSKA%1jRZq8^>e{$gwIXfmk zB`zjSIUNBVn`XhSmJ*W|pOTmulbxM8c6639l_o8ksU`IV+gZ_Et%%PYo8c_CP|Ilv ziMh^lh+2-#%uQB-(OK~sX_@(O{U#*j_Gt?a#Pac6aMb&JRnU}@et&^QG%TJOHg2QRp*Gy`}@&8 z(l;)$!T*bg`@f&#KfKGMtBM_k!Swf0js31*;4gqD$s#cCl?TXsV5qiXK4CtEM}tCm z%KRQIpZ8$zv|_W_LUt>Af&HD8HB~hpn!1{XnkJgwnqgqEWN5~NwQ^JQK=TCb61(QT zRs)Z15URSIu(QVTm)*aXVpgRZF!)@II-A}qdbbq?=E+&_{U_Au7gt)YH>F5&S zlI)TWw!?InZ(SC<6uRtmIpT8K*hA!>>e7iS%># zl=NkrHkFm7Y>JIdNr{c!lu~BVi;+SWmS_4)TSpFE9cH4nT492yKx&=cyLxM}MIR~D zmGq*o6pIaE5=Dfg^=M!#{1&>j(^IB6|GOgN`n7BK{ZO1XWR01aZg8&#H#tlN6Rkq| zHq=CozYlqxVR@6WEkD|rH7>nf>*&(&v(1;W-iFWYTeD@IIZ|IA49L=7LP>p-UbIPE z%hIa}FH8iz*h>Tp=|yKH95w2Rjp!Z|KZ~3}g99bALEd7O^aB0Pq4UAZm(DLJUYR#) zv6<97&s~h^RMOH^xA6eVSZD__t8jpEY~GZl5t*yDPBK&X4*IhOB<<3x}iN?~VG=z?zQ6!LtkSsEoG$8I|B#^nGC^inJ9i!u+1+9Y!Zeon(?F7> z7k8DG?%EY!8Wj~E9~D&^zYCCdqO01FRfnWDbX6y^iYt{!uw&+T(oM4RCSCa*l;oM5 zfffjz6H*5Zi!VHBCR?eWe)n!}>(-*ugT||2`$O6eiV4p)|JI)?T)1@kTGNV!Q}f49 z89!!>h5FF?oL;;`M2;GGT9pprCXbn(ooQ@#BK+51PaL~@ZSdh%^Uc|`mpCeKabe-| zMFj=RCuL;hO&n!0h^Fr~Yr2lXk`}QrxspQ&yP+j(FwKc1Atr zo7^GQdP@34Od3#31X^Dog=uTPFE%7eiIPPx6wO@q>15-i^cmgfnCl9p_0*%=P8s(k zeU~-YtnY=%YKI#~TuT1X5nBg+S9{Z2t`DElgquBn{Fmb=NR&C=jM>m3f=WjRsQf@tq{BCw}CM&e%T9AL}{N ziQi0Ejx6H!RGay0p~kQdQxaF*@x-I`TX}=79QEUedeb-&BQFsi;kTWYw9bJnpD=4kuny4 z`|;{8&pKRQDh1GG;+c4Pu>5(qtsH$%tBC2C{NeaqTn2?=qdh9?+%?#w(~vi6$=>&%0(Ufv!e>E$wUZ&}&iy|HCOhsMSZ9aH?TaF;5~$ebR^wB||CXG{WL2*HTwK=`6dwlxSHi z>V1>0ij@dYti&4xf^iClm76De;o(wyN13P}6dgTiPH{k75q*Y6u&Nwl_^7ROY-P}0*V+KdL$SbaZitKhp!9P#1xVbuHVB&`3#qXpK}-8&m#+9@s%*Oemm zkA-P7KlyydmwH$cx=BpLm+H587FqPE*su^UEh;QB>#3h(rvB6s5=7d`c^(E6ZL}7H zuq&TQHDW_Al+>hcN9&f1v%y z58PLh+>j^T=ss>aWMm)bXh?zu!w7kuJMBv55)j9iq+se6Ox!L>qywEryVAKFY0p0) z9#5zTXArv$$w|zz=;x7_@`51AP!d0Qa8kUU9OV0i`$*yr>98c17Xv{y3*ni`Lx$xP z?K6|zAk_BGkcL|HJgsrFHLO>>f0t;9F7yI5nWxfE#pqG%N-eu?2-{ZVq-TP<1tJC{ z+A%7Di~*^c>lmFiG%l;-+}iIXQfvCllP5_-GF+bKVHiC&d3>x%PlMxtIR5lF)d6c@ z#9+r{ZeVWec(^e^D7jDnZMQ5$J{SRzM-`5Ozq z*=#CXH+C>cZtB4k7RUyPBMMCVaeAxNNMDQ%qcFW!akRVCYOi$jj-KR^gQDzh6(&i7 z-0%mi7f9f=OJ_-dQ6DAlqONU1CBsonGV`Tt5{N)wfpWB*)BD!%(9Ylg$96AZ&zax- zycz#Y%@%r48$$f<@q0^hfUG$~Ee7S_{?g&WNhxH)X*J3%Nu6noIh((iVv-LR5C!fr+a?R?BXjfz&3o^)c8$!r)Vrcv9uc zg*R{7U!YYidbxZD*4x8HJ$2>vB~l;!_AMTQ4K$e7AI5UIo_2>Jb>w&3Bu5H8D^PuI zSZb8HvwoWdb60r~rY9}!?uW6m9*#!yw5_9@Yp%bJ6Qv}O_;OJStl}2lM)q)9B>kOJ zu*Lj9fNmk5lg8uzLmm-7z4-gZYfpaZc(s20j_v$ym)ioNISNE_l#^h6#@JPbg)5ab zCL?{^*mR3NuK389mzZ*Uo(>|lO!^~f+XgJx^CU;^c}b!ut>23B z8J3><^Ae3DA)rV^>JPgUKYPYL5LMMkHK2r`C&6nlvB4Cfg8I>F@Z$|lKz~0X!w4D) znn(zZ)UTF+YvRqX;A)`lp}*^SeFmPYPhY=l{7dXz{xlM(OM_QQ`X$)F(N4U+*>QgQ z7c-_CC--Rv@=Bnt8|ubn(I#-Xrs-*qZl!Q)+%|D~{&Sd(>rGJ1Y?QsJ7otDSn!CqH zykKg6#6j9p2T%#lMu)SMhr3;lCRRr@ZzxQTj?PHdlUHIhXupg(J@Ju29PK7ny)ZBv z?ow}kx#TF<%QHd1cnh$%4pMgz0D4k?51DNwOJD=gVm)!dsVB9q@AR}KiPqCBvJq%v z4+p*pdIlVRV307MAymvVGZ|FEK%fJv%P_MTaO*H%A~0US-N<~!KtKj~93dbLZDSyO z4LpwEy+QRE=35q`3?OI@e1)i-Wgu#77Xr5qYRE845I7N;r7XBFk&R(iXdns$+=*z9 zhFQ&k`v?Lu!2byTCGa*vkQ%d=1t%l39)Z&kOd@bJG8-X)3!y_AX0s9@%am$R3kHHX zm~sS8L}mv9=OeQd{u%)>yAU`a!NaJ837%xZn}kj?5D0=gF%XIZ-cbg=6^bAnggMB7 zn-ctv%wdGiGt3bLen{phgSs-zF&5%Xn3D)%O~4_FF0srh1fEH7U!xuja|VH5lR3+R zj}-N0mGT=M~|0Ouz!QTmCC#WC8fB`rFF*gwSV<8jDGk8_O@eICR2$ulQvEnRDMqq=24;r;c;AIBa zE109;dPXh`xRk-q3Z6djy@I0{VoJdA3?6839)oupoX6nk0~;11ZoumYzHD|G0|O>UR@nUpi01*fHF&4NVGTZPaC$?04}|%E z>k(Yy;1LIBHaH^LGw?Scimm-A?6Za7pe}(Ocnp~O61ELB7hcKFh z;LJY@fi^dppCAAMC;Zpo#oG*)_Bs9*|CE2uzv15t zI-xpv@_dBGLQA2G5GM3h{dig6$D1K67S;+|gr|Zmyw_o{xI5^2=%Tt7r6rzJ}gY-$>s~-$@^?-=#n1TGQ3t)#}>VwYh6&*Z!_Cu5qqe zu9I9>xfZ+r=K6b;s#O|R@vqXjN|P!rt8}i?tID7%W2#K6GN;OtD%-2AGRQ=P9anrcfbaQv}a5K4ix%s<&?)HUSx!ZoX^KRGNUbwxl z=2opjyvXf-x64l*t=ZZa86;iiG6 z;ig1Wx+&X~XPRu9VVY%{W144LZ@O1Utm9cHtj?@D%j&!b!??55Q|c!Tm7=9&DMQMY z@}()#r_yX`fwV$eD-}zn(oSi=bW}PeU6gJ}52Pp3OX;2DFtcWzxwhHQ9Bl4oPBBj~ ze`78*Up7B9|7y`#3>G(wr=_K(pJk3^f#sZ)v$|VNR$priYZvQi>niJZ>nZCWp7lMO zdUo{e;~C{S(lf_%g6Aimb37M%uJl~*xxus8bE{WvuccnU*Y&B}wr2 zUg%xoz0dolPZggUKJ9#l_+R-T(95lzLH4e?v@#GW6D zNsEenrSPx^CNUM$6!7x~_(H`vfaH$$F9%B`Q|&HK`a`n^FRAzf8tgt)Bj!ngcr%#J z`+3ziT!eLwy0Q>0DPID+m`=8L;g)|lf89po3YA+W8y|q!6-~6Ix-z}`Y^%aUlGm~OFt-fb}VIO?=zBP>01#D zw`fokw(Y?2*`KCuidhgWECg^7WEJUi9uLT@+fIi_9r8o(ouX{^m5RFtyYornK zfE)NLS!`{*NYSCHm zXzO^^T~Zri>g1h+fyULCRJXU5?P8w?G&q=MDZl$r-EqeZ5=_1!S@autu9|V2r&&!z zn)R4u@dmp4xIH`%)7|^NmZ-6h0ufUW99J<-d<%!(kP9bWSyZdxYE!=b%M9FvMk4)Ku#ko1Jhv`+$%sM5FR|GGQy6Lg8deil zu^c}7tKE25Icte<))L{YO@^&!5$2_)%tFog>|?_)-LrBmrlyzV}dIc@UW|rb@(v<}0rp4mWxCXLx-@&F)eN zHY^~qihLMevDQy1q^sf~&oM=8AdQs_3*?g|O00ajG1r$iHM!b@=*HIuDX~Q$x>eV& z5Z-7A!Mo|aS$GsSq{!>k!@2{G3Op-#!}G!_I9aP;<=(JZqhLrO{d-GtUmE$DWSA$- zldh8M6Pv(q*pm?zOBCob2wcA*VxUy{+HRIJ1jAL5-vBqN_!K(GLG~|U^VC!Nn(Wim zy)#ECbW)BRY@KRKr>-RuywK0#(~=t4#t~*#(eCmeLiT1iZ zA{tlh*EW>ylX31OnkVDz;nyYF*s+i{mKR=^403BS!o8Cux0VNdEjelfebvGSnC6us zVqZ)mKnzD==qLASfE^tx#;G0bIzfh#vaaBW9YRn zO8VS!q#2MP1nluIz>D+)tif^K2Y(_@l6jE^laW7c;9kIE%z(7AOVVYZHc4E4fq0zQ z--V;qW;Uu%s~D+2(FYPE{OBJM|HI?1iS1iXl&+hYy}~kY(!9|* z#_lnRy+LNQmqX+sVixce9_xm>Po;@_@rFy-5v6?l5b(aH*ylL)>qD*n#A@TgyH$Q% zptl_%KA7|+{j3-`5YYm|Hq%=%Nsn2Q}zjWEW-KQ^D7A;)3Xu*=p zl7}y*GeHiW6^BSqh_{U|$A)m=@-BHV>=tp5H_(r@Zxpvk=``^>=}arAfW`xGZHfcD zF+79*ikj@|R{V=Vt9t{pK-YWOXtzI;RK zH%c0!G|;o#myd{` z?6}kE{LYF{WyEmlbVc3gcv10+jb^3SDj_p2Zz~>$=i+$slFSx!F=@-wE6N>8&z_V3 z$pN^xJ>k?UZ&xLlMx2EAR+=~qz6ub2KLMblWYIo6B!wf(;6Yj=07UBw`!;d#p5sTZ zDv)8QG+mk}r`m_Mz|;G~oA5l6Egyv~JAn>XOX^E8tx>VIyQIM->a1iRk4G|-s1r5z{OM%N}s@|R5q+!xhg)DO=#|dbV zMfvQHQbjU=Z{(wj&~sc=`uoUd6roqqU+FVR?kH~A*?V((5?2s6bnzgQ;sy$rIM2s8aaeq>IP=NauB9Q$ zw>fY@af%D-{@K!#WtLuL=i+}c{rY32j8LAnqf8@)EIB*|0aq+xybo;(F_l@^EUZZYQYSx@qH#@!pugcfLo`Tg|OO|J4nFmB<4(dr* zTR&sUxKYjKnGOA5;=N&u%D+n9ps%f!h7F*uho`0Bc~^*Q`#rGT1Ib={h6oBCh!S{- zr~R9V{iIjK8+;sDQkL{7o+l3}fg&17pT+?o8$h^wtBv>UT|{c* zTaAGWfG0iNpMHg**klZTri_rg+gmBmnXaD#yjB(dz7oiO*wSTR z9pJgQhd;%W)z-;s&(Fvm`*5)cd`e0u=yMbLba@IEo?;X(0GZhnbho#p9F)L;aDA=j zN!@1;HcEaEXk7~h=Nt>3I8fk9#(^ti94K)Eab1w?;oC6S5#?k$Z>TGW{ZXq0Xy_l; zk%RUVB6X$HO6!`fH8&1%Rju`=@@uNPb1Gb8p_k2^aMvv zfO3CApgqY6rH-W0XLJmeIRfxNa*Cs;0Lv+X45X(x!$i3+c`xqRRIuli5ia%{)I#0c zcW)YPCQ@szeEG&LCrwA!jEas<9o5s)g?c^W&lYD!MWtqjwNEZPlxLyl)|_wItuW$k z0>7m87O8b5=}367MKKoKH%L!OFZ&l~FYrPtq^f+=2w$7D&I<#O*B! zGFE8aZ|>Xuda)Tib#)gH*?rRp){dtLZr#@t_eHicwi*&yccR&lk59-!pg$ZE#wR6B zk2Vd6D?V)@V}Q`J!7Wd;Es16O*11bc&2;8zX(yKZp2va#Zp?U?JNTxqN)_Su6Kik* zw&y499F=pTPI9Bg4^>SLrs-4BCb`83<|v56|bqGU*;xW(oJyY%nhlf&qJw ze9kXZ6noXO_Y2*Bdz6Kg5(e@-YO(co2=vT1vl@BC2-)>XB(VlujC_qS}r6E}glP&p~r|mZoA8;LYHIXRN40=ba z)}q!K8$ra!Hl0X8tvS+gVb}z4Uz=%zh0>`9@IvQ2WU}!*<{i=QZ)D9IhlVqN>JRK6 z2YNib&k#`i38?=HOu-lcnOAPVqU^mPv^p`;r~>m7>Sq&Z)F@gV%$ybRAjrm;6&YZ& z2NK(E44drtZ>0kKM~!clN!#p0;ce&d`6lD(WZy)QGc6 z6h10`yY1wbm3OJzMURZmBPLk~>S%E5v=G|VNNq16)TE_(7BPvvhiyM`^3di3r*=j6 z?KdK(m&Fh+w1PgvJd7dJPn)e@>t`6^&GU( ziFRAbH}f}{sUNJOME(lc{0Ln37qDEL8c$=g!d?|dJE#?EtrxI8fm77UTQbwbjp0VYENnQG4P!cTMN2mq95$709y4+! z96eaNg&#RKc}%QnKvF@`1PiUviEC4SZ}2M<#Mp3eAdDu6a&KrOi$Sr`HaC(Wz+lTf zu#R%@aZuOurLlOZJQ9dLlc!a>Hwo&sxyQ*g3w5C_USm)+xs5==dORI3YsGu@L-^D& z%QqIST2W*%^v6PmR3uH4+sFZ8S^0{sJB>us>@?L;b7NoXYo?zF)aR)m?CXY)?vr{? z=BVigJuG_LxQfD~WHM?*Hi&ri$ zHWe(LnrG=pXKbatK$5-~3L%?Ux85o>gN0j$49E|e%(?56vjUfw=jxMPEP(byH^S5MW^`ml40 z>~;3x8F<@zbIkf3Imb+AcNA<|ZHaNJt{y!xOIko2mBmd!ITLL?OFAyeZpyRCSbSgZ3^x=+j3}3s-IsUt zQj8Z3M80Vpdbptm+NZz;NCwkp@K_h6Mr!%e!A&=}KQ_SJ3Zj2Do{`$Di@5a6*i-V0 z=+q|5tke-(E8+XYC0Fhn4NvXneYgV7;ce=;FA>Le-f)DR%f_@Fi71h7OOExYrP%M> zWeB^WlX=ixT(}>8*m>!$k$6(=ZCcIGe`wv|5P930+p%JE(O%Q;_30yG(_>>}EOasj zvmr+o*Z`033O7Q;kxT+R{UK#byO zPaa}|u5+W}Ck%}+QlB?Iq%pC*{T=%K~#2eJVyoZ9aK&*XBb9cPI7k9ho%H zVrVuWh;0Kkk!Cr{!SWgR0)HV7kr}b9s9+bU(sk-yq9PURcA|B97DSb$n!^)Pdv`I? znm;!uEl7)>??4D}>WT2JW^hRj6L+r9h>y?A7(P6+u(Wi2;ZBRAy|#bs#)AjTHXb=r zmNsx;Y+8Sqw!B475{Zx3@3b+sdHO);Yn@+wiDilzM8+tN^yJ~8`Z`xox@7MmBXzi+ z*j;k)(5~cOy`qvM21b^gFdJS=v|}d>8lZtppCZxhRWw*^hiN{UcC|HrgBP@w=w#CQ zQETZHCYqKKEh5v?r8m+(Py9Pnw30}9HoP-Iw31Z-hF773s7>DTt+c^io(o)`%M<1W zclX-IeL8OWFXx{fJki z->7ZAc@%vsc8S`2_Ux|B=g;j<>eekXsjJ1XQf2q=0{uc|_ktH#P6lr8+E}<-;Wp(j zf$+6ZUg39PEJzT*d&sA{9=M)@rze*f0 zRUC43B@TN}1&3Yl#9<$z;IL<^I9@6^?12gnduA)}k@sF`Hk3Q+TrsZcg2Ln z-0K*1xJ#3HT#)d1-heKbqqg7M9&<fb);1ap0}&SYJF2>-fV1BB~8fB=WVmAA^21 zfJBP7&z!q^yW5!-&AN7L(d=B;yB4MWMubG4X!qm&66sB>6C)%t-MzE&3x@5!3A&pS zybWQEk)Rqz3XdHN0N)d7m(&M>oYTPy0!<{0L<;H9$FN(zDI3M*;1Kq@f*W7KRz45> z(C_$(tvF8=e0vrDd=x*Y{7T!6lOi71huBk__T9^%=g82P}&z z$u=jA%8wav2PQEW-f6slmFVpE9%MH0t}n~*N;j9RR)Q90Q|}iJxjraNvtbT%NpO8Q z%&=T*9LzizjDC0*zd)?!eNrg3#wqHfkp^!A?U!0%5^8^FhE8f{7z9J1Gc-ddwKEKY zq2Oz1d7O+D?TC0+Al`6BDagxU?aO#W2ese^1vhv)A&2G9zQJ%p`hA0;GyqD{V<-Sk zlbvk}pjI22DIukWxYaThOg zqc-oHu-CMA=aPaF%Z1L{wuPSO)|V{Xxz9*byNa=+)|HhNC@Cv8c2pLGMURr#DqhEW z6c-0?feS_`oVs%CqGb5H9*_8kopv={pnDV=EO<_B5JugN)cAVngJkp9pU;`~_2;0E z3d6t6SU+aZW$aPX@NQqieWDA#@K7N7#rJE z;yYo(ge4QQjbo;aPMHRlxEx2a#E4NDIm1l7<4Z4(w~+pKX(a#k7hivETuCDD(!RpE zWm`**nRc!pGjy$m)V{_&8`|oCkBR#E`Ov@&OP3qcIe|7Scb`>ezgb8Vh_2u zd_cS@jiXwRder+WA4!X*_U=C|f&Bd9@82$@62F zE{(>r$WQx(_Q&Zwh5ox8D!P(I0E)DvN z&OvwIN#_=AEIDA>vLQWwG1yhtxCaSsPWYOrS0Ipil%?wp=`4_W&0{ZkqIv*Z>r(HyRr29y$cTk z#mPA!_+%h>*mD}D68y>?sr?6nLj*C6o{Hsb_Hg*~4MfMn-=mE!kVsdB@@_$-aU5+7 zzbnb!_#th)jlgd>pcCb}a&yHc8BDE;H3?e2VpGEJTi7shA1Qt)4V5z?bRU!}4t{vT z`8I&vss8*15d1*xZ_lBHhsRw~EJq!{bUFOw;#ZUh;6bc$~~b9^r2VU zN(kmKt;JN%)9mGqu?t`n|{a-YN517E!jmMS3Ew`#95M%gWUYU&iQYEt)SbJ+W!ptyIEB z-`=o=)NQifBXMl*&7lD$JZ*L&K5TAf)v_*;x=ZYeFuIOqyFEuC){Gpr0MqT#e z-N(4pL#M`_Go8cF;~(`nV{t5}--z(G4)zqOw{!cHvEX^W;%Lal?aDcE7NX4IVjUi{ zU}d6(T#*}c+oIO=4l{yZO$R1>J-8XB$mivCV)uRt(Zem}!~1RRZnCxvg}4j$R_MFa zN4J*mv_$VbnsD0m?$!k*_N3(ntA*a6qs6RoE7z`Fv2xv-)#Ec(TQYKU$LE;HMfaTC zrG>ec!quynt}(4!F*ZAU+}JEdYSCydaZOhx)K7Aehf_4^Zb|`;A`ex3E!V-lL?hwQ z*=Lfh3W)yy!$Y~BKHC75GCX(DA9>J#&Vzcqmjl(y+cA>DUl{eJXK6Hj2nwH;EOjT| zJQ&tx9BstYqzWN`G~ws~>iw2C?2=Cw!W%n~dAKlTO~I$@O#i31F9D3AO7pMRM|F2~ z(j;^`U_z(EAp}G&fkZ$71w;fy1Vlua8$|9)5JfzO!5a?{1OX9o1jL9A3Lb-qeZ`vUw&-cro{9oo`qT2 zhqpo^58F#H$2?-x;#uPoPH~`eedUU0?)>_WHwLV7Ji*5%jaqqCvD3J73%m+yeRP)H z#BOKD>>PWkJrnc$O`Z0Mjn$vVMo-%Gn|EJ)=C#Ugx6a#~=&)$qr56-O>-~cX%3pj{Pu_d zia45lGS>WC=aBQE-$qHuTaCPI2g*V_j^7Fg8=AwOZ_h`5Q@SN(NINjVnD0lsq#PTv z{h@sbrI-#nQQfV&8=HbsZ7DsH+;DvTaVzEwGwpHCIDhqxINRg=xOTQWTWuVE8)1n{ zE-;{}ab@h`Q5+m2`oN}P}$a^2!O<{$jc5i2PVo`N@?7Hh8-}>~r z4O=#^n{(~t>t|0%ME-WX`Oerme|>7_`roX48FQl_-Zp0OgqyBA!--++Jmu-xPi!xG zdeg?|Fpn_uQ9QcC+s&goz0w>FY1h%Qu30hZ(J6@wuO8c@Ymw9N`)+o#_upLq^vfl0 z_w{4w-yzm(>b6}^uK3jx>l63?0*gFL{EO$!Suzuhop-zhkEz<=z15a2@$PEwU(4)B zq8jGy_=?~CV}+dF4n*cASJ^Md{&McG&wr|<&!&NoZZCR#_3AB^8)uJMS7PVw_y2k9 zx!cQ&TeKYJgvKntZq2O3glTg|4=HMS=$a3vl)OLn_Y21t&008r^ysN;e>1@G>1UqV^z@FcYZokBp0E-B*2GJm=~L{e_U%hcKWlF*%o?DS<0E5l#ow?J z|LC>Kvd>92h#l&^t7i#bi#F}Z(I?H9&h*<2%6FKc=d(2zR=D$ zBNB7rk@)^N7?v+u;n`WMcj8d5`F3ZhkTJ=k<0E3uOs6H@0Kdw)3NIFS!n5?~w=cfG zKe7CwB@Znso?za#Wa%%KBnA#WZ(><-lb&1l4oxh%|Bhu37TH4r%a<)%UXt8v%GtgO z>w!Btcz8I&fm#|8xMj(bTS~w#w=7u(lnx0zc+awB_m}K_?LW4BTKwsMTsQc&#Ij%9 zv*fm-NOH>YKP9c*@oJ21`^4>=oH4Q2u;N9qVzvwsTO}daN>$hBz0lV>_H- z&&d;{$W5z~OIO(gSN+#2%t@{al&mkW$qC(+6HXo~NFIo7i5efsTQ(8AVl){q2EBvV zn!dz4HX&@YSb)tE6WI9h9Bhf$6WbsT#rC83s0Y+4>}|0XZ{56&H*WqzeX4%Irns`@ zY$xpDx6rfF^EmeHd(rcT=OFg%`^xhJcI(T>eiv=M-Mz!S*LrXB9`JtX{lxp<-oN<# z*mtizHf`I9Fhoy`}#9{mcE2VsE`l|89Sk-!@I`qt}$XTy!-od%c+-|;w zeJ(yUKQjMleqnxN;&CY6ziA!l5a<@@9T*t6A}|vB=*sAZ^Qr~*6b^$iUOT^1S{n#g_hmWJ*RJrY_M z+8BB&^mOQ1?4`FWv?ui2&^w_6+!y0;=nL$PaWwRO^Eg*Bm0-1FnH76t?dm7D6@55x z=eg$&yt@0O)zhDtUov6N{E1_Vdb~d3+izcg`Ge=@KQm!NN%QYwkF8xici#NPb7w7F zvHr2uD>f#axxN_-A9;Mk>PI$hT7Bc3*$Z!k+ZCH@b;86{yLMLWg80Rl>}rm;&11s@ z&riPU@qQTpG?j-hPRZZ!GH{c9FFpSJ*2L9;UZW=VzIfV`+n{O-diu}Zd3fC4it$2g zg9CQAL~?q-S?45Uy{_E#-g~de9uFh?^tp0mFVxiM%3U9P@QOr_?9t=Ok$s>XFEl$? zm@H^DDvk*@t9nsm>+blFs~`Q%{KQh*7khQpvrq0Ve(}Y5m#t1jj?c#?7_sZ)EdiTb zv28lv4)`a`Tvad?-vV&cyk^$GmDOYypE z9u`mM^{~Qo%zK~r!{U1IsOkpiZj2esUp>5N{(_rtnl@wp$}Ni%Ee5;$WBlRYpZ_d8 z9h(`3SJ-dd)Cx6U9zPGoH|7Gw#qR-CrP7%O164)?nX2*FkdFFZ4E(wYReO7I^V)#=q%H{yL$%t<=+%yZ+B z18-h8bTd{>eC+>vpyPYTi55E(^$PLupg49tfXDho;J~w6_Pn%x+JF-0S-e;3J^0Dy z_ywOhUdMYu7rgy{#=$POH(^J76XtFG?Qhp_t*l%>dC;JFlP4r1f$>{kp0mH$ZeSa~ zmt9W+PJ_hM>cTUvnC*Xi&yfUP-=22k4b!LJxCSfT8=9@(9%B1T!qekeosReP?W^&= z?KPGoI~_o;{kk*`eQTFO3l;`;SXW}&3$N{C#^C1a3;kI3+tGe35IMfHH4^tQfrRD$ z>*C$*gTJ&QvFf~lBkx8#IasHYhg||b-feZ7XKgF|Y`1k5N}n`j(w@hx@XN_A$)(Nk zpu2jJQ=DAHEisA_&P^`*0(%2wt$iCD4}@3Q-ygxctDg~q@3;V!7uddc zzJ3!+k3RP6cTa$`^~o-NI}c-qHuma(v%VTb$-LwotdB_EYS+Y`-MV4t8%1_**+)*K z#My*#!Y zd4aG0d*j=;l-TD7`uDxA@AY`yy&uNt$@aEc-TnYph=a$&_gL66;NR>^utmbz)qiYj zT^s-0x87QZ_r7IYiQ-Q0H!lfK24>KKj^VX&>?iP#K?CB?S^jTK&W_{NW82z*#-ZfT zDx7>EymNAClQ?|%y@TxG@W&bjF8<|&`*swClSe_v*Q~M5OD|jDn=HI((8TVGX`{%L zxsPw&{Mh50w>&m`%CxyNuSk@ID4l4m&tkhv#BlTo}vvj}Q7~JdDRG7yr#J z!veG^P4QguV&~$fm`Iu8O!=ErW?u~D98k9X>lOOIirihm5+ z7vGsYR_Kh!l8&pmEr{U^0Q(QXB+FIKSI&6H0ZJ|V8tiUzjX$#7dhVfRPtOzFJ!6^J zX5iRTEBusYC&O591wZ3ayf!rB?^CShmhJhlI~LvLbnotX5|KlJT~EQMer(oNkCfPn zkNsbbuh`QB9-$FLfU+$E??gTE@kH9dQ+-3RsVvX|RfksZN{WWRQ} zksEJYzWVOO`tXz1lkt*y z@vzV7BZ~y^%mK>+aqfd(Gd#fVi=UkPfSp^}GXt2~n&}Te9lzU|u?bAR-b#eewrA{s zn*Z1e9~kyp*Yk!9?FHH1jmc-cRzK(FN7k-=_>m3R9srw){8zF+D7PNV+ScC_$8+i# z&gOmbcj0>Z2VrBCRzLXcHZj>RAhr4~9P9kqr{+^@=g*uufBw`&gxj%TpAn2DUz#7s znvPcC%;qtlTYBr5EkJ0O^Lsj_XmOiBc$;-Byg6PPMxU{&y~5hIXWI~eg!GT37}cHmiFUZhUdw?`26?7rSM(=o49&E87hCmtz}` zrLk}?NW`n#v8-*}_Tj_FVIPw1<6ccbmHa!5+IHxIHnA`)vKf12_4ZA#7r*er^@CR@ z!a~m5t?=*U!AfVWm>C(iZ-_xMsvsHmqc_3YK^p|_h)3qdgP2c%Sxhz?k6T!4g;B~M z@zmk>V|uszeLK73=cG`fP{_tE5cpwF2U*ehJhnc#A&wn6WY3EU{;*ThHT))?{kczZ z!sbSpU(=lRy%iP!mk<%*7Z~`Zof*Bh4C^}XWRFC;0}|L zrPagC$kDh{9mYEag<;tzDD3|VgvM?j*jove--ZplR$g}b3cS=<;xr4#9=5`d%7!aB zQ^U!3{PKoU;&b>RH`*Ty7>*vEkE~PdzhFxTKeh!>hI}VS|KWRVxW@JZ*j(T%Y!7fm zjm9?M`#d>@!oKOnvONIPTgD&pO(k!yKK=3ap|J~QO);8H89iqjHcG%|0@!Q3hAG4N zI)`N_?1fs2kdBq_b~JFq9&AI<5Z~N<)aY{I;31_(pG$gb}uM$!+z{rfZYw4#>6i6jofemUkZu4;h=%9 zQn_J#Q%N=;V0sH1n3o!j$Ih5N!+48uKjA^bj|e{{{G70g@H@gJ;g159@4E4`rYVcC zfUtzHjIaY?H^M%Ig9t|ujw75lb@Z(3)O^Clgi8sR5w0X$PlzvIB4s7vi-fxg-*V}s zsr9cy`O1rjl>Gk@k}n|m(eD4)(^GTadA zsQX9k)Qvppb*lc8-jp8H{;O5j33qF!J;@b%xA(;8>~c;*k%H_T;C2_U*|D|5+2y6~ z)L^3@{pa$ROSyDx-JYgyoN#vYLxM82Jt0v#LQ-e9N5ZF|qs#e{H9JSTsM7{AIZhTj zUDizc)1^AqJ)QgisSMB)xU;CMAZ3(d(T|9AaeD|D6724UUXT<9G?ZNLNBsNl9UU*& zrA89IU<%<6(S33jS%R!n3PmHjngw^#)RvH2hsZqjq?F|T6+q9X|J=Sx>vl2UUg}ru zIHdpFa-=7k%FuB}30knLW{uoqtmrNNA)Xv0t;D_kNVQA1T-qsd|Lm`J8t5dnie>7O z4~ter>>=v(!^EDng7}cL#)s6x79a&e#J#IO#2(9CXgeKZE=aJxmb3^<%TXhay(m*+ zaF&#rgQc)-x*X&H)=-}xxiwY3phb|}LbWQkg?E@2oPayh3b9v0@inRTqsOpwsq;heCv*2GaCuv|2PyLt4n@N)@CliBG#L|8tF>EOr0mgFr*fvwpgcARLx1;!Ur;|OE7F?&6I;Qf(xrGM(pECZ zVM-pPN-&L7!ufmY#R#8>BxycARPzAJ74JbPE7n+YVn;TFq|eAhs68O)7l{Y2!K-rb zgHI3#A4$%&{>0Nt-IZETV%a1;%Yg@l5K;lAH9+dFoHKuJnM{k+(r5Njr^wWj;^0h9 zHw9&e8u}+-aO;$_Ca>QQ@w4n32f<^j3SIOfx(Y916y>uX7a9Dw32G7e!W zT{MT**&?kHmBdPJK#^qWfoo-?EvY|9mfJUp$>10@>9TP*6Jm)PN19@3 z+@wlzDJk*HkF%7J9yO%u)#p^2+OfFuH2+b?Uv2Mf`w7OCGLIox5dILor0FNnt>c9J zh#(%tQZkLSi$CN^wKY2zdrtnz#G-t`p46FzBe~B$(Hd!rGtQ+h?w+!SOxe1O?CVrm z*QGfz9al?IQ?YWE-l2uHd6Kj_Sq)5`Tfs>#)kWw;hyPdrI$ z()Uk88L3tNK+jWXa(zDoQ}|BBxczV`n~F`H-Me*Ymb#mL&h(3I*SHjZt&^A6o@Vz7 zPqx|Dwln6_WL9ib%^dMJfq&sDv5TjqQ+BNhb}LjG$SLkB%qf4teqo+(QIxNywy%u=PKnLh)qWe0oSDc zh!gw->8ne7@pWjeP(w=Vo}wnCgcpOHOOdux+6gHBl3-8li;OD7-e|AA(xo{m4eVe( zr%szsa*0%F2}L>V3EO%~%A-E*+e1P$6#@6kJPKMP{gn2kXya+pk=oK~w-j@+-Izmr zWuza{Iq;z+2RH-J;V;Ftns-luos)FtCr6PMu6HGwGCc5;i};BNfnVWYv5?YZ@vP(! z%26iSdvXG?Ah?mAU;r~(>5z;zsq=qgweX}d51IaBe`O>HIze}qpbgmXSK5_MA|rCt z$!l$mv&X%9mQBwu*3m%e5>6Y?B}?B@$#f`losM_hJO9*kOyx+OQ&beJN#8PMXNAev z(heOTSKj=Y(Em=`-jX@c>;MxRgRU5}&F`mkUW2-}r<&1oucUetQ~Q zr)ozg9_isPfdX1O^vGGfA{pnUVnqY}0auw>2x*zfkY!p)hbOHs1)UbI0iKB`>@Qlu@mv;{v(1#)Svp>=!~M`qeC z{gBopk2~Vel%498E-foQ)AKs5(q%EFJZCtXDKoo9sT0RZ<|;r7x#J$08c!c$`?GKg znx;7J@QJl(Y-iG(SavG6zLQOz6fcl#GPIHBCHP}L>9<&6S9h^(w7N)uxk9GXsa;Kv zwB#Q4+6|>JuW0o3EHiUX6_o04>KZ9@4|F@T zXZhR`GAVT^RkC}PrDf};OkE>~r)Zh@0cVXk>mlgdn6yjiMJU#eT#`y(`z#!YA6g?& z=c=8e?iFz>&%xv`OZGD9P@g>Q(wfNi4W$jNiI~Ww%?@RAPIkP6QLFf7g0-wVQ6~8~ z)mNQM_XstLMM>*wmLmPs@~cBx@+ke+`A-hXnHp2+Tp2z|^qb;cPIAc#S z6Lb0=WwqwiLBDV?>LJ%leUdbVE4Dym(#4%i=hPM3fL6%tptM$?;1JiRt}^?M_OhuV z_G8>du7k&}&#n$booSl`dW)V&k4R7(ui0wp3AhMQumo=(@HBoO?n&RoQ-Q8WnG(u+ zj>5j6B{+-q6s!)i$iAKv?o!CD~xT>?lpolro^Z(RTkSQeOe>;rN209UB#I<#H2<>GT-aUp!5|v zCB5*S*i4zt5-ydek3w7VW$REFC6g;O)GZ)>vtm(3hI5c4g`1P%Ez^hp?U0Kp;g*co zCEP1|O-2gBH%r;;2M{M17d}bhgrjPdQ6Tr2M@rJOdfC37j4t(fHLa<1?*(@_@RL#* z77h@6WMUEu4soZ#*1lCsKXEiTsCnoLQk!{sd(lW9(q zdUZO1lFrSQj!ng6QW>5o*Q>Oe)M_{Ek<3$S-p9NcdQmCZI7zOo0!|E`I=%E03%bP8uvO?ZAi_O)si}QscBe>(17V& zE+QYKX)e@uan01iY2pc<;H{wb&}jM(d8olt*C#t?Qlm7Txrn;#N*cVm02YzSz9S;G0y$gk_h%6mRHhd)9b{t zT^mF>My|Jk0a@#$+pC`)3C>ebBUu2-6dmFAfOVXb_aLX3R}su2t5`*1g-FFSq zeo~2APHb@|Mq0ob0nW|{C2=hWB?6fqwi&4s-140bWhW57cL@qrt`pu<_v;cK1)9(e%LRm8?jL`AZt5($Wc670 zp8Te3lzS3#VQso)8kG*QCb15pl}H(B6LFcgL5T6>+$wla7%g7Lctg0n46(9CNac2s zJCLn1e6p=CJ~{S~@v8A>yhhjqpX$0DpPpN$9#OwCOf|NqN{z!YzUHu+SaVEWi}3p> zMXBF`l3_Ilagt^{&J%Dza(Pn?lO)2#!Li)u~mMZrL%UJe*D?o9t8NKHokK7^sk)bFI7HC3LbXlpR5_A0k*2W_^{X5H5yYLvK3)N&6pB7T|p+w3aF%15nxk&O zd9iyeQFr52=sox*<{q_HZB(0iRN`uff^Mu?ih{z;qwT` z5MD?)jPP*}%JKA+ylRMqmF0N}a5G~H8FOhCJcse!nC4CoKBTGcWDGbG;RsW9;N5nN zDU&qD-Hh)nH5!KmswV08IJJwF_gDI4!i-t@Nk^3$vUt?-#Zz{3>@?D70iw}y5Q(x zyy89I+v_JC{k(&`bq?QT?=T!AQ)llO@1%cvO!Ln1W*x|T!|4w1V%(KuiTCbW#{=G# z-nHJ1Cp(aO&}oi2a?iWjTUqPa;eFX#_gLcHgF4d(@@@PH$6H8K_t=lPy2nApWgj2m z`kx-&PraYlJ&>>JCmi1)P4z(^3 z$@gVx5UqWvYmAjlFFIS{3&f(iE@k@O4EJET3By^MNNXrh5#zfOMhU5#YH1&>p{QkC zjYIt+UzSCU_ew5#2MzE+mXM`)6m^+=cNTZw5ZzQue|;+PDqC~iLj6U32b&}59rl5%NYP{iB6V-L_ci&U*!PoI%ZG|$LAxCrLa-4@E z_h8tuBgQmXv?_eD_o(qV#C?wnW-Fia8#k+f3K+LwD~=F)nWJ)y+f+SOVBD?>RRiNr zSh!;2ZhQ!LlyR^63SSz&PgSWZd>mOBdHfje?@;pr^}aePpTku(9_8_Pyq=&ZbC$NVE)SJs7h61<9cAY%vhk#Qk{&2s$6w7mZ}a1N7NN+xbdhOp+*?%u+7Ld#(LBN&sB{<8^-~UO084t z;591Pou+_=#$SxT8ebdV7~dLyR|>Y@t4!E_ONCV~Jb;*LsLsHrg5#WLBHUeXZnR}_~huJ0jljoJ^L{Z3Jc}1PE4TkGB*eExQ87I#jm@KwhwqJJh7I4W+Nc4#1-JE7 zeHB%uZZAP$(6_ZZTeX2-)d^It09|{li`8YI<`8TYa|KFofO0KxNG^P7%P2LP;yusi z_;h$FX2Uw6#&UeQyDfad%kkOjq1b+=FTOuL0pHf1gfB{830b}clC%V05}yg4eh_gF z<4f6#@J;bY@yY1*kha_Lf$A;BUB=VK4&y%KCF6B`arzDXHsHhI2k<@ZgT^6zH2NdF zC;L2FQw|vnLuzyIQ)p8@;_BlmX9%2Bg7^e}KD4bAF^$nyKV-BSK5s28Hu1J#8~Chk z@tN}gK5X6zAJOiNl~N`?*4-QF`{3s_F2c`3FZyB(a4~)%^lbp@90)njK`$>ytwW6~ z0EeTu_3-`e36P?R_~lb;)HkL={-e|e3fka1l=(OO0;GpYiUf?~26iw4WvWsBKkzf* zKmUk&Yw+`cTEI87htJ4Ye&`UBI>e+7F{wjL(6S!n8(&yP`h1lS{swK4vH-fJ9&}3~ zVj4ihn5q~WCPWPrqJ{}k!%yaeGcBD6r6^E)A|TU@e4vP6d)YKF9<9r z5Ed>76SMt^+5W`r`M_*P+!dVm2TuDS%|*atf8sGrJoY~Y9wS+J>`px96OTQ>Hw#hU zjra`(H{FZyefY)6O{JKfcnI+;@agrT_{#Y@v~E2#*-&V*r$MQ0#tTUQB7T?Sv*SAv zzYD)oaM`Pf`3-ojg|QdEq2RZ-!OQP}<60T-gX3Bo|A}84@ZBHLt3QGJF5+9-aq4;n zt@RE1^(}rr(zriqe1Y+I{Ca}YN!&e-pK1I6O7|zF!=Uqz2n&tFE{!3Tpq5v8p{s=6 z-AQlJftJx9Ixq~%iOuK_ZI}yMMpOjh4DG|DeSoy@Puiakof!l72nX~h2UyUd4H0vO zI>R^*_NEBRKzJeyz1j#`3%V646VR{cK{qruN}&hOG%i>8+Ou&6?Let&sahHrLnpQ} z`a)-%jk|4B8)K+ytJ)exs-0?&m=3A~@}8qQBHRgfqZAsnv(ZxYigA`YSDlNPE~<;s zMsZAG~*G1|gSgyY6V$|4A^)nKx zzv_?h05!l6&3vhGHmuMfq`wTds1%xdFj@;8X0(D98;X>})G%W(t<&Yu;Uf`$wYnPh zij5jd*}#kyw0So*7V|yjuvO!XOKATGs0oRP-X+(k`RlZ_7Q zI_Uh)v_Dad9yc2o!U}ISn!x@%V+>N2utH7Lv+7x+8EsK>^=tKOq!EkMN9|NQjZ4)_ z>LnwlUQw?Yy`k@S8^!81wby8>-cWBK?{C%H#+m9JSnwvaEbU-f_8aZh?_gifp;yuo zR_1^)1U|z1MpxR3rj+wAEk;vHdw=x}Y}QcmB8vyQbVjUpIS#jGQ}qlEghr&P}CfXI>Q5ry&Sq!bg!4X*GHYh%^^cjw6)a0!pffT3SZo1)#G?rKq^txe6G8HRl$@@}9kSa%KK({7IX0l4R@QSzXlJm{mHh>{b%i75FoC(AZyt02B&V{m}jg9hZv5V^7e zwSOUbvjKTCMBZ#b-VBj9L*&gcd9wj|vjKUt0X4ft4h>PK7m`mS)aixP;}&@~L=E1M z8oZGF+km>eA+>fPdD%x^_EE=0spAU4ZQVi38(@1E;^!enG4{gGgwJ^!Jg(dEQ}jwb zq_LMYHbLX%@Nrgvz9y*q7`(>~@IXytBYpw+6;B{6o<$J!-hpynz|RA!??hNU3zPJ= zNbfM|?E}4k3%}}3{Bn)A;AP~H_9kf`A?@pt_7Q5BJaE8&L%;nQBQX=$F9mE&8pwFe z1MIg(3_Ny7YFk)cGmZUo5bg-u8=}39WYM4=?XAi2o5}H;NqU&1M3~l9DA5lkT%s<) zGYqk=AzD{c4a68PNUDTM6_ZpkNfke-66AQ#BvnGd#{$ZFG3C3A5-j_4w86MRe8Muy zXc?t5PWe-mJj}VY`75TJl~KaVC_{0|P#LAC5oHBl6#S?%N{K}&DOch( zl~7hJC0Ybqrb z%jq@wDO=^FXdY$DB3;{3!a|g=Y`O;MHMJ*Q^GH{VUQ+{7w;ib)qSs`S#^sc}cJ!J` zHKi$kW%Qal(`#x-ujyQRO`Yg9olUQ)E4`*R^qN}GYwAL;sX4tS551-e+MpcTogDg0 zxwJd==r2{!`V`ROw5G*rOpB94ixZ*6$)ShRfnLcu^hb(lgBnvql+hA3riPF?gzsR{ z#0zRgFQ_HGpfl*LbfmX(7QG?5AQsP4?5z9(p<3 zA>Pgfl!=u6lQC8vZC(zoT@Ixr&$VqLC3%#RJW7d=Hq1{O=BM>CX}toJo*=DPm@<@0 z8H&<+m69=R z9;GCYc1Vw70+bjZB_=P+-uP*0{8^SJkJ6J%iOHpWXm2N%(vV9D$fX3-BX>v0-MQrJ zDEYcR^;ML-93d~~l7FM*Q|n z2iW5P`7B623zEYu;-HLNRZdKlk)O)QPvzvNa&l5RwQ&V8R7P!FL9Qw%w#ulDE67{z z$y?>r!yU+54P0DGZ#!}v9cG`Ka0K0$dN@EF2gqR>7a`)JlwOa;QL~3Wx8NcdV;q?o z32=1mBVK~;Y>147gT#*Zzx7x+z_D;gV#-Upl~JEpP@hLg!!qjghScY0QJ+_kU(2b_ z>rtOqkj}E%az~?r)GnhwFQ7h;k@98a<_6T~XHuV6P@m^fpI1CpVQ*lQ*O$??6c?rw*^64(~u6o=4dzC*K9A)y}34FQ#rw zP`8~=-PSrwhnG;d#j|vH5p{SDb$Dm$@F+(R=TehLsma??leeQL?@Ucz$dPX|>hl)V z=PjwvqtxfEsL!L+i}0v&N?;Q`a4zHl*|g$d6HSqD2{$8EP+VfS#9=85%{7v&d`tAi1P%JyJ4{b~qw>f#Wp~@vKK`KH^!=a|elA zJJ8qrHSgE}Ngu5LztZbrRPLcLK+u0E6eoJ)R=lAoKApA+QgX5{B6wS$VN_m(%AxLe|5Ry9vTo8me5Wv{Y1D^1M_L#{*_zcQ~hw>kwOyr&*6M2*g zO~GuLh-S$|gfiiw7Ko67A<`~~RLl1EgQQD7>0*%zVd6X|O9JG1hmU&5OTJO8$-~s* zXZl>dCNp`pHCn2#e)d*+XrRyXwvo*4rJf&dMF}zkylyCLFM=79XbtG*LLtwF>K#!bX)j2Dm(urda-s3y4~z*Ypv z4PM$0-4;LFlEb#-vMqY<$wzL`b5FT!i_A%WhgOTPAEKQIk`FBMfue1Y6>JKJrS^r| z)!JvZg8QuAPM(l`OLLJ*c2sSGr%64GOQ1zYLn~a5nT31MQ@!_UE3BB3l=5_{A@oWc zJP+uPImgIJ4+S-LpJ z@aGs^F-p127zsad7WCm#JloSL0vOkn7%kz+b;F$8VB>0xQfA|c-yQH7bqaZ+l)!VA zPSCi0;d5SNTx-lRZZ_`3?2Aqj!liK1>yOGy$Jge4k8>zIFfJ- z;UvOo6K0Jb>zhNkh;S+41B7b`HxX76?jqbvxS#M4nmpQfnD9%&uL=K7_=7;dL1+@@ z5Y{JbNLVsu`q(M{W`wN>+Yxpq>_&J2VPC?5)8 zyq)k)!e!HEjhp6QLAZu+1L3a-w-G*1xQlQ%;cp4|5gwRxM-q-9oJ2T{a1P-O zgo_E65Z+Dr0O88nv)i>d*Ai|d+)P+WxP$Oz!aam<5$-2ENchq0xie;)pAvpfSVj09 zVUqAifdL<(MVLoeNZ4o&@Dpf4SVq`}up?m?!XAWu2>TNbCLB(94Ya0M8_}`<+h=)- zAtmEI=bs7nnx}sqdZB&upg+Zy{ojXV<&5tg!d}LhgaJaa zeqz=Bby%Nye-4bYgr5WPeja-9bD;JYehzHNHvAlz!xDZDl-2wSmiS+TMYPR*;3LQ? zm`U&>=3{ngDSVBUm=F4uQHeRA-5d$Y%AF%v1@i-*jfw_Pp2?cGJp17LiALCKe4y_b zpStHiy5~>a^GELakb6$0Kj>Z`aL-t&%>L;0eR^%W^iO8drH|9(D-${?I5i^Y*ycq-dIpJII zd6j1m;md?O2(e-fx%L}TjHaG4Q!PcxLN)l^8JKb4tt(99z>dl2XAVhwS6&Jmxu7CEBqb zqs%cF%ZI zDK}uwS)SDatMCEkYY7|B!d4iqq}B#{JhGN0b5#VK42IwRy74w~$rXhE45N52;1+eO zx(#nS{X#8Kzf?=r9e6LGR!{2mUwIlr3wMAQe?C0XVes>(!RuZEk9n=J8BYm!VXe^~ zJOg~k*l!#Fr+A!b%r~buLPc$1wp1p3-A|i|l@{u(m~EWs3*vX2Qn9C&LRUra%iw zHP#<$UFN~M-(tl2Fftg1UcZHK6w-Gb^BuCbQr1=K6^CsxLn`Yl&vRE;%2xs~Hbjjg zMM{A`0oOA2PKB`rLyOfN`ab-A4C=vkHzWB$aF>N^S!as;d=4b*FW*PYrH%LDz+5`l zmCBrdhjiNThjYxd$K8zO{3S)rKOPsLK0JGB4Wda4J@Eiq$z?0 zuu()%=}jpX5U>Ci#I85GNWL>OcXw|P-}n9fpYQ!W-=91ub7tnunKNf*&YUT^3>h(E z2=YhskpdN$R#fQYhKwDIkYyucPPX?cE3Y`b{pbb6`sfj2AL%o6Naf(x{hf&Qdj&DJ zxIUF5ORnX(rXWS{!w3}>52?&Ck6gRD1R>oLgwW)g=~b<-=DgE^P)aRA0cj0&RkeX5 zgFk}2lK}PxND%h==L0>^prnTB9kV|FC9j-qM^M=^%(Zh)TwUWa%XKm2w@4OK{0?22K*1^w{Djk*nPmu4TDe{R|4@tXcPbrJ)oE*;lojwVmru!0p#Hm zymGnabBA1SSzaG#&;J9!Rl&G83T*+v@~|fW|5s3ta-hEmpr6nPH~x>|uc%330=~Tv z_fMdR2Acl_G(dMF_%~(QZAih5M#iHWqanus4g3moPy!C-7I%<> zLI-UOgRu$Wjes-B((!yUp@D39_qBfl1@5Q+B5)f~)AgVTNA>>+u#Y42-(WEG?f(KO zPNQ55>^fA#T9Dx%VIAbn2Ed^VxXlnB|6c~S`&$-(sf2m^64doium$qgLOa&NdP~CY zFW9vhPz@7+Dgkg<0pP;~z_<(OUI(}ufCm^205sj7L1+f>VRfX8FW`j$EE5X!GGNC- zU1Nbi4hIw7ZvHqN&*YP|@xTY--B*|>ut&+L27@GL4f{QEcyJ;!%~QM~qiKhB{ojU} z|8t&P7L>u`go1&26xv0h6vFWUN+}%5JPPgo2F1Do{fI`m@qY^0XMkTSiesKYA$@|=>z>{BTylQHOz3D#zvDci?MZI;|h_1xeHBX9)do_p$EGElJLQb z@6g2S;XmqY+^4?apIisrO7K5k0lP67U=57j76?ayZ;a>KMgWZeFM(n#_zVE-Jk-dJ zL&krE?T~*ArLyu6aPB=wxBrh|=>Ld*3CNTM`xFduP|re$|0`Gz`CEbRbD+ne447Ed zD8WE>AQ}L8AOOR}c5j3*9zX>!0iYVvE(u?pm91i3I^d0HXt2@`t6RVb5{ z0NYwO{*U2&n3v}e_pjha>(UL=Ho!c(5y;lF8h-{;!yL_o_4hx41Zem5 z@FK|bzW|Cy(F6?KIp`Y=*xCOUEJ03|g*N@Gx%d(?GOJLL8(*9c%`g8^OOR$MN7}T?ha1 z8gl(uf;jjz>CAGJz$@*>_O^`wmKkb$AAxbA+)$z^4@Q%OL+#R04JRD-z)Bq60}--vT~yRBlL{!F~*I5+%bKhDtFA@`?dog}R3U97er8vMxlvFfM(epCT3ekU#qi z)PDxraR%tL07p>*2F4CJ)(hxBVFUo4P)EMvGYGL>0XP=;fDS=tnFJ&~!Wo0!qerYS z32-~zx*{Ry3HJ-u39J)|ux}3spHdI|e5WD|+A$hAI3tQ>&!c$I#SDe1`wc}pqzfnj z`ZWaRavs|k%0l0f_CbG;y?rm}d)Z$5Z~Mq_l*}GPwQLR8GxwfvG4MJ7`@dSy_itJPd3^(X6^2Ku9r z1-*lwEsB-k^9%#IwxTKQ>h5*We?0U@HOpV6Y=@wAeh_pz&ENwI@ssgBsvw)1^yEq z^AgenjF3Vdgld2UfDC|C0GyW0?m(pgj{%eblmQe1^amIYFbJR+pdSFv$8NcUNE74?I? ze?N2**#PY9JrkLTr`9+ zC`kYkd7FE0=g68@DIny zy$?Rq!*@VCU=73P_i%Ox>%W$H9c;}*NK4Lm9jJ=!0Q+z&z#EYEB%G~IL0Vxrgic8N z65uGT-8la!jPq5<&MhXi4Awczf_(>lv%OJ;1Z^_hopycL3urLl^bExkI2*XV`%?wB z0r+ek?o{x(yCM$qu}#>4hAN6s2Dc7nuwzgIz?}g70qz3m0GI_Z6<{{|GT7oVs2%{* zVH%u|^I|YCzkyC@9H)&2XeZ%V@LS~ZF(@DI(X4O}W&(R1!F-G2z+bQu_zU(Sc*T6U=O8T&r6P0ta7q~S8_0QLZ^2N)xTQz67M;e1>! z1fi=3Abb(Z!F}rgSk~+1{GZ4Ncc!G?CYd`>wx<;CKAp&!Vt6_{xNXryuX`=JM7_C4|Mkx%;d(JWYJ%6 zi_NjOF^?diVebcd5gI^4W+IH|0!#&2JbAy z0=xopg4p4&=?74krGu)xB~2Zql9xP6fKZ&9u=Y`5-vdcNeTBs zA?T8X`^sxQGF$~~%kxsYzYGsx^l<+|c?N=w__MU7 zb#_~0eM5)dY&4nlBO2=TgIbz9+Uwfdsyg&T+ghg9)pY2cogED=ZSDHBhK`Qb_WYcj z`o@li&T2zV%k-T7olT8(9aSTns&c#~)4k~My86zhs&S;=^>%1 zv8JxMy{=Z@*<4%KrtbhPRrVXGAJSUaOo<0lf-JpTZj-@e0Bw59Vm4JXsv4WBs+;Qc zGeLj)Dt(1>n7*nbUoVN%Uengt+R<)kZ)`HOwAJSfsTlarIj9u1pjHGg8elZn!+h*O zdU!!%L?-yrqY;o&2jL)~YKFXah_%5_6`>mnSPPm8X*DG7M4b>eKw2A6>QNfu*8%+7 zQ9jCnpL(bTQ*^>SGC-*oG#yg=L%a!c>L9NQjRd?3Qg2je`VCWuL&q^-?p2IQMS zJ|jRL{1ia@tI#yyiD{-lD%L|a$Zr7u$pHSC6>7)@{6?}}FMm%x>LpD!1MeBo1Jj_F zasT4}puI5@;U5EDj7enDG zN=Dhxu0GJZk)W{#7#W?QwL8#S^c;E~y@Gb4J?JbthrUD?(D%Y*p^s1{{3VQ5b}$Q> z=b81)E6f|r2h4HiH1iqr6Z0GM2g|Y?8^oGfD{Etm*a2)MJB@8(Z)V%rxxz%@cVUtM z-W{xn(I_6YdplZ)4lxfh%bCZSXPC9jYGxzzCbO5>!Mw|S!F0T4Okub%SlBBZ71jvv35SFZp;H(rEJa}`7Og~USd%bKm@3Q` z<_Nb4bA=tkmux5cN+=Xo3p0e7?0NPx;ZLEjaH}v&I1c`T8D7vizz-OW#-f|hB-r2F z4J~{G?L`OB5$1E{&C1!z8Omm5yKvNltjqs`S8 zX!~g^wKr)SwJq9Pw0CPC)-KjQsa>x9L$A^Y>m&5BdaXV|pQ1PFE&68vS$n_4=3eoAs~hx9Q)C4~vhCPl!*6&x|+3kBFZbzchhO5E6nCLK7kqViU9p znF)OpCMVP-KbyQI`Hkeal6NQXOFo$T&2tM~Y!(Ctw_W z&-}@1*$mbwX<{q8likaHtUw9@G!X)tNK~W{P1rn|KwK~v$*DObXXgsIYHljm#@)~D z<%9WHK8ZJIeYF1CaBZAcuT9csXboDsHeXw!9jG0rov3Zn&eG1;KBQelG_g{z1WknM zqxErmJ<)_&?~pXnp`Q<$Sgl_Nn%JPZarhbP<6B(e1%J`e&9|2A9potLBM2trh zwaF`!Hz&WIyd!y6@?Oxy*UvrRV!FGPONEDtHc+>LhaCs_-{7bB{MWs`yR*AUo(8QA z;*=axbSwT;bSZv=IjD!(qmtA7f5*Sv`h^ykzBCB{c6{(V<vO-*XfXDIQTQSFBWgp}3&<7Uw|fw~8MXKP#?rK3p(O z`TK8<;}W)e~%Hf|Sh<=62W`OW+eekZ?+-^(8c>-j!d&XZs@Kju&K zU-Q57T|%G`F2o8+LJH;!;fL`1Q-IHe3xBT((qS(LzmE$a3#Wxoh0okH!fA?;@|S=@ z0x1>ffa4d!1)PrmVH|${|JvRQOTpS=YrPWeu_*imR{CeI1+4B~p;h=rIRk9@yF!ML zshkeh`bVL!vQ20an)mwwBu5R9HtIA$E5q zS}1Hs%h3^HeGj3FXdT-RR+k51eOU`D%xY#GtT20FjlnC-x3I>*3d3xIS)zeeCKN1f zjIe{vgjHq)UT4^Lu)Ljc{`V|Wz?+aLW)+;ny#RMtFQFvnWjHV00Oxd@;qGk<>dhR0 zv-GVfpLq}7@g9bAv~8$Aa|-okPQYINBRDhr5YGM1p`mcTUCaCkZwkIgW0)`Dj{66A zWAHh=5559#fqz9)84-Mf>CB&~g}I8F85e40{({pX22PE5bPMZ)=CDduK=WW;KgFiP z*-{8v!Y1QY3$2H9=M8WU{Q{edHo=?LH`p@tI$MffWu0(#Fc|HE^XRwX{_YIi6@0|j zp?z?la2n15PQhKzCu}|XjBQ4j;GFF%^aGrv{RB5BKeF@K{&0SvL&w+&C-l(CYf|`BJ`Gmi3_g?3;N*(vA?wiTU+8^+Js z7W6ec1O3d-N1@Cz6wa)GS42;s0A>liE?Nq&hMt0xu_xg~>|rF3*NBVY1<_)7-T4SA zVD`Yvhkfv>=m@gFt7IGVHk{7yLb=RtxMA9fCNr1eMcOy$VR-rUD62&a*f4Z28-ebF zo3s1bC^!KOM~hiKdK_MxJi#WSC)p%88H`7V*>UJ7+;Y9oPDIDq$>;=Ig-){7=mWUb z`W8-&zhP&>DdKE&g}p_1TzFD=N_aweT4*9Z@O0t@&jNq@Pw=$Y3hRYc-~~S~Y!O}( z)(OuE%Y@CsE5ZxH2H|D?2!B}kL^vUw5)$@`u#X7agxA5}#y|9#f1I+s0Jrz#`)xCNP!fU*5?D-% zVH-3Uwn5`zBRCb^36WekjsDP&5Se#}s2G&7N@W15&YW)^cNII~NcmCSR@24*v}6&%__%n@*EPs8GK znfVzuO$bbSAR7*@eg7MXD74uY%|-*&Smdp?_(ckpJrFF>)DO$7IquE zi`~Z_Wlyo6vR|^_vOlq1Y&UERRIn+CQRo$^3ag?(QKIOl7^)bps8%#8S`{-D^AvX} z?pHjaSf*H`*r3>~cvJDVVz1(e;*{bu#aD`76n}CII8Z@c7#G9oxm3;on}|ZLl6|+;70A?j&dit^V}uwJMI_m4<7MKJ`jwd77UjS z%-0aUim&I}_*?n=_{aHY`4{=E{2thte$HRv{}6Z~M9>JCLatB(uEIEBiqHlduLoct zE`uTW8Vs6!;7Xl^A@i$}1G65dOjBBw#ma%oaj*<@D(5Q~DwinNC|^CKEL}izDnOH z-&9|Vufw;u?*QM?zBRthzO#Mr_kF^5mG29_ulv5^d&KvI?|I*gzTf-);m7-h_-Xvo z{c`;B{mT7@`BnK%_q)aK9=}C?%l)4Bd)053-(kOVe&722u3}Yzs#sN$Do0hIa;gTZ z#;c~PI#su;?olmLtx~Vz3rY#f4zdOn z1eFB!2^t(UGH85IP0-Y!nL+b{?haZQv^eOgptV68gSG^13)&U5H|TiKCqbVDeHHY5 z(62$RU^dt{I3zeOI4w9Q*dAOIToPOnJTSO2cwBH*a6@oQaA)w`;5&ow4}LWGiQr|y zYl1ffZw}rXyf^qr@X6pa!CwS_6?`T5*Wf=x_>jPm@DN={N=SByB_uz@8PYdoaLAaD zNg*vEogwo>7KSVdSrhVd$o7zZAtyq<4EZ6%6{-vk3)O{YhB`vaLx+Zr4{ZqT2%R5# zf9TTCHK7|q-w1sx^g!sz(DR|+hW;KVgoTD_!!p9`VI^Th!zP7I3!4#kN7zGQPlc@u z+Z?tt>|of3VV{P55q2f4E1V0D2-k*ZgxkYQ!Uu&{hK~!c32zRc6TTpPQTX!k7s6i& z-yVJ_{FCqt;opY;7Qsh^Md%_jBkU0+5rZQ}L`;sD7BM5@?ubVt9*cN7VpYWEh<76P zMx2Sb5OF0!jASBJkr9!)$n?nENN41L$dQqgA{!&yBX5hmFLF`jGm+~eUys}sc`)*L z(@D4(d1sJN(f?dZeNA4Z>!z7Tym`nTxr7)4A#Oms|qOioNm%z&7&F*PwQF}K9r9rJL^l9*L7 z8)G)bY>(L!b2R37%x5v*$NU^~HC7cH8>@{?jWx#hj;)9t8apnwHge1>d^;C6-dainb z`VsY0>NV<@)H~Jt)yLJJsxPU3RR0mDj0=v7jZ2C%#988U<9f%H#`TFC8aFnsA+9ZM zZrp;nN8%ofTNd|R+@`o~aqq<)i#r?lUEJ>)r18^4Y1EnoO{T`C>8{cB*!U z_BQRE+IzH*z~Rda+E=x2Y4>SA(4Ns=)PAG=S?kjI=t6aBUAit;SD_oJtJSsZ?$AA| zTcg{edq?+y?lavFx@-CXIIPLi7wG%z$LbsOo%(z9kLy>!5zSlrz50{-FZ4g^yW>OS zv$24Y$6hp60#HQ31tbx6Y3IXCOnYv zRKn_nEeX35K1uj0;m<_h#Ms32#Jt2li6awh6WbH-NPIMLMdI^`+Y^r_o=v=z_(S4f zN&ZQ(Nl8h0Nqv$=B~>N0Cf$~FZ_*PK-%_}gu#|)pQ%bLtK`A$-OigJ|xjp5vl(i{aQg);q zOgWqKeac^{{;9F4>8W|CeNsoJ)}(f(-j(`5>Y~)8sVh^TOMNNzwbUJ{@1`D1{UG(@ z)bpvAQm>@`n(9hp(|pr{)1uO}X~}6>X_mD7G-q1hv>|Du(k7(UrcF!hNV_HNj363uOkbS-RQj{&&!@kf{(Aab>F=c$9xyvVO_>Gn>iw$qven%+_QlWoKravmM#R*?qDHXOGMtpIwtZHT&l5S=qN`FUWo{ z`?2h&vsY!W&weHQjqIJ-d$JE_pU6I){dxA+**|3ep8c1BGpG!qh8Tn1kZLf%#ap4F z)X?8B%rM3<$uPw*-OypU#c+?|A;aT_XAG+iFBmo%-ZZ>z*lRdqIB7Uz_`>jw;YWjL zxR%4`_~(S>#OB23q~+w~*mH_<%5nze49^*xGdZU|r#YuHXKv1&Irry0nzJNlMb5gM z7js_C*`D)G&Viiwb3V#Bmvb@ayPRKg{xmX1A7hX)(x@>e88eM$qtiIlILX*-yw$kS z__Xl_<5uH-<449z#@|ewDa@2$GMS1^LrfD*(@k?t515`Ztv9`C+GqOEbkX#a=`XXd zIl>%o&Ne&D<>q1L3Fby~r}=jCgXX2?wdPIcx6B93r_7(5ubBU^@RksZ#*%KaS)7(Z zma&#POOvJDGRHF4a;N2f%OcBC%Sy}hmQ9vzmUk=%EFV}-TfVehw)|rG%PLrdtTEPj zYns(;EwDPReXN76qpTCHb=GOt4(nX&oz@4ei>=RC*I8e-Znf^V9<-jYp0!@IUa|ga zb=g>(uPxXXWz*V{ZCN&pt3{c}@ui*g6$-jq8v_mC}G>M%HL zjzUK-N4cY)V~}IGW2|Gcquw#i(dL-#nD4m9@sQ&&$J35gj`faL9B(*wI`%k@I6iTF z;ke@X&GA=0pYNX^mLHp+n4ghv$}h|>&F`N-BLAlR+Wcwx9r?H9-K1RrFHP&Z2`w9~b@D3-t=_71t}PS7EQgy~g#L(yOi4yj~CWdaBopy534r8SLhHJ#I^G}X;2t!?S3s)0-U4$Rb4YAqv#Wx%ltCz;BPR3am8 z2@u%ItJ~^k)RlYrnJ7UCZE7i%yp+mXN`)*XO*I>>Gi& zQ~w^dKqK-7$fU)DrqbMjr0WKfb`6we4+IHFr53DgprmTFkS3TUfvo1ir1)S`e6XxI zDQd-C0t&E~H`h}pB{U&#h`X_-@-o_sWt4vz?aVS#shNxmqgm3MMe1)l7D~&9z%v*+ z+{&na%B8}Tpp0s(+&HA66NYPB=k%tkPTVF?UHK5%D6r9DHmSdyL)x3F+8ap2sJLVd zn9Y=}m9j0f4kr}DNz;e-Y`W2GqO#hg9+zq;uXJ;D=H^jd*r|@}_R5;N+Qz1)s>&X{ zW-7NED|<4BB+AZCRp!6~8BKW=BVe43AmWe&LIsjg-Po!0c1m1kA$l;;%FCs|KGLJn za>~1cHltk9FXd5QKJsrGHQ7hXG@9!iNyp?!Y$-;%2d1e!H*Yl2z-VtHN7pviwbivZ zwvVoFtC~?a)|*3=pI1K8J60;JV+j$-si0%TLX}aGH;!-~*K_7L%>796r2dAcmYSx) z1IwK5`V3V~9V2fXX>T{0aJJKcF(=_bQ6dXLi44wiFJ5Ao;YJy5cH=OM%G@{@CM&_6 z-UfT~oHUQtQ7TK9)#1#OrBg|P9tJn9+uKgu2FgjwAv%;5_6i6VpQKCnp5nACDUSQo zi^&v2`k3ZWok&XqNy2i0-Iw7uf>TPmhR_7BeyR0$7crHas2a*|q^=TKHER^B~nlnoS z)@v#~_Cy*&Bd_bfa@uyXn7FF|4~n*!D7{=JZaEdV$C4r|<(b*Al1Y4JVnosFn(kSaXzq1Qtf13rEXf?})vIjvqFH~R4|$#znaV5Z zh@p!bDL@)j;V~CRv&GZPKu%>JM=0rXM^W#{@GgH|b`Hv-%Dav_D@Z+VAD6BUCDk;0CV@A~G>j0Gufr5$C>t#j`kB=_6V;%+_c9cbT{ zQ6&(k77}U6GSV)yk@l0B>eM0)20BnmD_T4bZyD8CIh_P$l%R}`q;gY>+~IBUj1AB~ zrLfUrHmNU5TCU$hP<~c2XwBxbHll?#()6~TO$R$ig|tzTO%m&hc2b5Y$Bvr_TL{Ug z&uy3X4(;CVYwyVwmIcbyPTAQVO)bs!?Hw@YI*1g&&}br=AOO{cgN}PUmEB&^=~lea zREBp9KuA?xE(N)r9(9(}E~%iEmP^W|oXRUY6Dfr!>{D+30i+R>*M7 zBwCp1ZRkw5iJeJo^lWbqFor!Z?Vt+VY?4jVD(KL#P*qeo<`AB9dd^FSrI930n(E#; zR1uR(&8XM9I_2IQbuvgLB_>Kvn0U!c=t@gTs22=4wzrhrE55gk(?f?Xu`FFyozo#p z_c$8ZNK?LUEy<{)oa>f0r$<02OmWGSQ%1C7DDGKhz2==}c-@++I+{YQKEY`UZ8cr# z)S^GPf8i~w(?B~-6< z*J-Ilu438^(vsmVYLa!2Nvcn#J*)s&KfLrMw0%=ZN)!F<9X-_4x~ZXWC`Y;NrA43- zZYnUp9vNVW$?CFD`J0HHBILAC3+)_i7iemaKEM{j%^RBuHxBEBryj60vLR}x{W6nK z(z#2~>pIahi)pS`+p|f+Y_A?<6P#xC=t9!blK)Kb3aTbL`$++s?2#8%K-#7`vRtYu zY8OcI9Iqa{cEVf0L0OEL)G(!yq)wszSLGG11|KMNUKa{C? zriO@(jSa<0paBvFLtMf@3JHT&V~pAr@U>)HMoNKgiiAOTNtlNxwkA>ibYqk>k6lh&3WHl9>Vo1F zw;(-i6lI027sarxq8PSR66WbTY?*Kxo#D7aZVLst`ykz23|prfw{4QRdpits_gE#G zi*1sG;d&&G)}4ke5Um^AB}#`aC{4rrh?*%<418U{;4Fybnea5Lp>8im+e|geey|;Kp^~?!fH_ml(&0C87eDJc={pM#4c=W*l&K%H?%`#dJ@VkKeNAJJS%9rA728NMr(z1v(3kvhoaP?o zm;kq1a>XGg^Tr`IP09yji(@kX4o79KIYhzwNFF*y+`Z9rt*F7SI%E?S;&np8+~bF= z7d7q`qGmb}G`oqcc(_tm9wY7weh{^6n6hLEtrQ}4dCzNB=_8+ z-abybzK6imm)PKq08o%h@JWV*c`7cGDuzDA1H47nr&!@;GMWfx@$kVXwKXk0dlYv! z*)e#kkaPv78{TR?^SsP6JjyZK9(xGN0R$pEJ;Q=D!RFy*lf;3!fNP?=3J>B@ky~}7 zwAC|*%^pdtq!w(mS^@B%596M^830!K8oC+5h`S%`4Wi@U`tj6tz)ySPNS`52jHQ1jb{+GvpqA_>(c|A7bNqM8hosvayC&;3h zw=9Z#)E$*vEE4VMXSpe0(&o4&dd9dVMll)H7sgR2nzlh`YyWirfCs7&bE!)`JXpGj*Owu7}LyM$zORUFC78 zNFEtQ#E+t+J!YVng7{b7C~>l6QIEz5yHbx1XjI-)pdn9G?uXIZ<<6LwJ@LpWfqTl! zGOz1PPse+`G%qok(6Xpkl%BmqsSFMee1MBD62%+6FHX zn;&9s^6GJ-fo=%aKg%(0O=xy*iqA<2F&Bi%?*Hiy(NZ zrbrLnktPy9jAr)e$3{=x*STbbmKODlCmQvRMdER#&qY0nc#A{UeRmOQv8U0V z15!FS?NOF^0hCy7$U#uam7#e(I+3)3cm*VLrkkP1KcLBfAH{M{fztHQ+-x@^kJCVi zdU_5t>4rT*dyMiPXc-@YYM54R_RN zC9i!Tz^C01SZF}*gscwotOy=t(L{Vy4kYCK+)7^C8Li|Z4HC(N7Av`Kf*5{*0|B|h zvC?Y{D|yUgwB|WU9(^8VC6D8PlH3YMBe*Y>030bnTkZOoUCRl5`A?C z4_^sSGZoNGj>xTMDv~)5dtu)8nkAuV9p+M6QW8oEBoc`omCYzMTjGkZ1EBS^p+;Jx zky4VUZbqw_)}oV@OW7;R=bZBe;YoH8RXzl_!j+RO@Cjy}1vR#3_ciIOV0f^f0XsbDjaayB|% zZPExO_f?=|S|E=Wka`*plko$sl0L)9pnyjQ@~F9I)aj(Tm3;}ZBdA?z}YRavGUz(&G@qvs~6L>FESXukexX9kfF&bii7u`Jqoct@L`BYCzLPw*8&OIBQbo804mAuge z)^xmEq<~r|i$qB+l!eYAD;3E~C!LkbVx^PZCXrB)$lF+>)k>|NmCi&fmBmJRTB%4@ zTCJ7NHY=^2zB{nmr1sG~8*Q{rVnzkD(Hd+LYg(K zL1PXnMtPRWM!16t>!5`lv~DLYMV^MhTqK2U(hB3GJe;%ud0+x0lnZ&E0w$B{k339) z7_FN;T7eiXN8W9KNukV2Xtm@42n+?gQYfN`bbmeX?NJ{%IM)NliiSH5yuyn+TZ^HkeG2?_3=TSG zlGv1%&NdQlfOk_EX@|dRX~xKu#u+rBaTbm?H_~!-_~V~Af$(c@Y$gnGh&hswu)-K& zP7ursBa}PAfGrLQn~o`zElwa6VFU}>LAZ8MP7ooiJ0v!cjQK(ocy&meKr+g!gL3Mi zd>|Ri*8w4s3L_FXoJ9BlLgmAmgeQbV9GpPg+gaT%v4`0Gm+=u)&1@n^6exS_lC)_YgR!)-YSDNWh4ExF$(|7{mI5P*NOVRB?b1 zNo$*1repbVh~*<8VTdtO2SISlF+w>L3^?PEuxaj`-bNVX6j~R?FlU^FIpdJ&GFRb8$_ z>Z8_n+B2$=eHOPwPK(RD1OtZKOSnpOvQ##1OO z9IxvcBj4hsRZ?PaLMwcXw%J2XV%|*B1>|iU9M=<9(C93yn_dflRYbTo%*O8?30l|e zNv-dMKmVzlj<2yLkvu70);I~~Z#_`Th)9Z;sBRWq{!XuLnTd}MX#@}tH&RpNwJs&rrpC4^N=!MDojm-p0G%JiNd@Y%*1C51^fD!+wUFZgAjFQp*-TDyAfS_s z_}q}^Xa-f@+*#EG7x-0pjAI->WFSaYYinD}EP~ceZ|#^3HGuJMY;NqB-3T)R&b*ok zZzGY#WVQo&ZD&nKdneqT;%;e!YoQuK1XJGz?La(XNtBEV9K#%JrIZ61061|9 z9{aV`EwgBDZd1z~cnDC}(bia1Po=bxGZwSWX(EZWL^2yWTru0I0ke_40mQI(26d3d z!ep}G5en+4t0lsl?9&^Y;c-=a9elgG7T)@}g)&KhU{Ez326{VOC&Q?P67VjX3?T^c z6KN?0P%^VE^xTSOTFD_KTwGUEDkD%43ky^jB?tK4Y?C*exQM_VQgX=wB&H%S#)trT zC_)z5rdUX4;}|BU8BTI|>*Y)yb9>SzU01IK3QA&ORBD}+g8y_>Y8gh|nSe@7uV`p# znO0TZG6VD{HI(i|EcDRFVk&`KpX$1%mYE&`dah=nCt?=5TeHxU9}7JWvd~Svh3?ob zbdPSKdvpukqk}N+zQTfGIc4!&8BRLty0}#qC!Im!o(_}cl3w$~t)&$n^?4Gdp1LkB zufi&@$@6qo&zQI4WE@EET^E-tMlJ>EOc^bXn@GGzC(0-)yg0o0kfw$a zx4}G# z9)sZCl}sIM_uUw5A#2Dj8lJ`QnxhVU3H-(V9w^}`qatw`DT&jz$Wy(IGurWxq$_3Tc|F;k9MEp6w82F}p6MU`x3G@Pb5uHLGqi!Y~KFgQP z3}9|&Rxn$beek8%AJ|0J4xiQ=!A@bHVK>9~v=6agvEQ@5DZ&(SieZY0@D1&QiVqa0 zxlnE(Hx#}SycoV!DZn>92l7q)qx|dqe)t~nPw>e)7Cu^szX_Zu@q8lNPenLbP4!*lE4!*jcQ-t#%&bJRE5HxoWG z_oVNezPtVGez*E9@O#p4hu?lxkSbagud0NP#kH$esMe`=s;>G6`|JI4{QLV4_8;p% z)&G9~rT)A9Kl1;||BC-l0i)r=ZXE%S1iS>FbK4&9cECpgX9KN)(*m0V+u&1e3j!Ys zd_3^iAm5;H_!wJKP-f8lp!<_&U-%qaOlW*)YUm*N99m`Qn9y57?+o1<`b$`D*!Zy7 zVT;37g>4Kw2A?^*5cXr38156E8Ey+79ez{zwD8&C^TY2BUlRUO_@!_${8~g_#F&Vx zh{lNKh}jWKB3_Hw8u3oV{)l4{Cn8QqoR18L&zF_Jhszowni(z)qZUOiflrgY9QA6{_NY@)=b|n}M@09E?h`!>K1X&_bX9ai zbW`+_=y#$IM4yU26a9INDkeB40zN;M8Z#_rRLo5=O)>Lh?uvN;K0LNFW`E2FG3R0~ z$NUr{#$1b4#QMgD!>7mevB|L+vHfF5#5Tew$L@)JAoh{i$7A1&JrsK`_Ul+N_L@3D zU8EkSzDZr9ZcsmTJ-m#^!k zE7uLwRqDp+Ch2DC7V4hSy$GKR+X^2H+Yg@%`%rgY_Z56LOw@JjdHBwIJbc?cQ*Vcl zhYi+`(vR0y>l^j$@U8cG`aAWH=-0xR&3EbF(;w2muRp8*T>rKH7xZ1JG-(WcjBILBd(tgQ^ONpNT9ov3(#oWD@KLfillCMXOgau9 zCi^t$o1`C-t|t2?ha_v06O%KNP09A;qU4g~KFI@DNl;D(zl-LwqN_vVh#g@`LrBBL`l(8w3 zQ>LY~rp!*6mvU#yy%DQcEL*dB=JJmAS+m;Pmd{?T6DuMU{ZtX6SuZlKH27g$3YXDk z6ge^2PYmwNbKY13UU>|PY8sC zI#q%uFhVqlJTNjsDPB~#4DcgJ>;)Gm8gvK6XEjiHgc!{C6Ve=MXCV+HDnXzGzlaU1 zX0%M7)loBBx2B2Ty=Lu8+hTXsZmevccE_Af?K2Jhk`>DzU8zQoV~hn4qe z#I#}=vGaUS8;ki@%;$;slT@t%;&a#`efi+FLhpI<4G!DC8_qwoQ;WNwDt3f$$ zX*xRYx^1@hsb+rh(xneS1tQ1wI_8Sgwq0$H$Q`$7|L%=je$t5!n^4g*w5~v{>Pq+P zGWdxc$T;q5rE6SQCGQl(+pa#_F^2CqeOl*ub=|b3Yj4&~9mP-EuJQ)D`12b& z@#(J5dDX6$UO)VxZn98!-^}*uwbPFcQmZ}@Tqpk+%9lVOeycez4&l3E1y}dJ2vtO; zMzwnRvNdaFEo*O|HM@h(z^?FRl@Z9=gewSt4;{6rCkU3G6=37;7Qq$Q1_N_=HC9>bJe;1Zz{MKVsAhA#>RKmDwD>=yL@srs$H+Y_QS6CZfo4G zUAu?hIKJYUerlJ#cb^=ct3mwnJ6C$_nChy;-lflNs?tucUHf4a4LQ5`73THw;SqK*}dLEjvC z`;$`x-pbJ}a5Y5a)xWuA=eC!Qo!HVaW=vIMKV4u;_4wOHsa=}1%OZR?bB3S04zc{q zCu_Ft(y3g|QjO?T+5N)iW{Q4(fwdzC&F!mp6}!U3U@;#iWf)9K=h+Y1hwT+tYK{)d zc`;t?nkvrFxT;_>W{Qp9eY1Y=5uIxE$kL87wJX)7{Y4xEv-pO^z#e3}YFx9#UNGke zoIbVkjdx%ISTw4k6K@(``No8OI@GKNARCMxBbAEv0||CxGP!fYQ1{kgjgn~2}_nNT)0Fh zhPx_wS8wsQ2v?H0N=y<&X75c_+t6_?MyDDR;c5W!vRsod_=$Toy1!!oy=5BJFuCu2 zFvGSyxMDS)Vf}=qViYenzUg{gXm{NV77MC)v1P*e+NS)0Bf1v&b=L6HUN}1Agj!VY z+5N!><9E2?b%B4}8S(vr?VmoV8zGFmZ}PN(>H(8C?7kmj`3n!+^aHT{M~mgPYbs~N z3O~^yF3`A2bXS*fu4vNXQQuz{qs8d&GhI>QevN9ttjRa^jWvAQN3?u>e$8v!wW_Ww z5f}DuJ^!F?j45AK<8L9NQyjJld2%=A;$__;n3=4n)Ws{Jgd zT;W<3m+W`$#A?li+Tk54FuH|;>Xy~(?%Jq6x^?4jFuom;va`x z30?0iRs1U9wH32kr+3b&Y43b=$xNN9E5GDwzS5QL>}vNb@5<0PuNEm?L$4U1G3@C# zPH)l{zH(-gs8EY}SH2d9>bi=Qfe|*r72sm{g92V|#4)^BxzCj#h<0%Tzy0c@Azcjc za}DEN{f3HQUy59H{7qdg`;;p8D)M#BNBy_yT=uJuh~Ao9hfASTiG^MMV&PSPJ}}~- zASQ_nZxdAceKg|bQrBgri>n-!nK7&joE_J9N>}>lMPmGgqZGUIsS%>`%vn*me6+vqY3)_tQhup2Y=`T1 zS6xKz*i8p_zPRN_SU#!*L;v9^+Q0}`z01@!#!ocCN?#8@CNWd2602Mgy6ScPIOscn z7jk7irMr4g5VM{V{Y502)mJ~}TxM5*$cS0D>blMeuB=;ece~8$z~im`UrABt7yf;X zo(gMpMK3Ve`^Ss>1y!!*yGl*q)zMvt`N4k;J=Jws=o)qPFu(tgp{jigc4<_-(lx3- zS_(9(+_NIs+q}~0mE(0=Ch%J~zqjhJ`pT)%_O-gJWx2d7?UCP-#CWxs*;VETt_SDJ z(>-&wIU=Lt@aJ=NUCI;u!n+>0>&{r0s?Y%)7Wl*NgWu`84wPLzpcFUwwTPME1wdnz zF4koNR|p0YG}?(JE$$=@-8cEkMxv+JiGhZlg+E5g}%~7^pwhTwL9EqP9&#hrs6>zuc^I zy!A!x74^9j&%XylU>+U<)s-Cs)Gl8v&|SK&K|3G!J-KEbDB3R~?5+9d_P=sqv2L7De|PnaN_A!R%9rnkSlHY1 zU)DhGLcg$E=Zk(AHERMlXu{4P2Y>OH(5dRNj?*SG?()^EqW_M=>W?)tJ78M-UoO8|*6DBZ6 z%o#Bun8kp1nB$oh^$cfDAYi~VAPN`=ib^t167c@ii94FBKRY5PY&#Ko-s66? z^O{SC_A^KOcx~&>@4awo!*8BBSH}AF>JiY?R;SlvL5^fKkFyeyZu`y+KgVX$KbH3_ zQ6^-qZFh9BYEJdeK-g|+q2?Y91`qE?X& z4)dz?^!kV_&+GAf;-YN1K57*!fkihRoaq{=pw^K76r!Jv^UOO!l3wQ5VabDo)ej*T zlHD$)vGD7b)2xm}8bO-2u)q^#r36fb;`cpuYsb4NWq*DrZ@an>zBxyn7tLt%}n6n3|r<$ycHk|&{)XHds zHRrezuiAQPL4sLprhTyfDKeL&gin7ab=F+Gii>8}v}7_Ho2>eW$W}5M6oNvP1$p6> z8Iqjcfb7=-^1`d?@{m6Ql$=zyH`SP7_q5WQu0!?e3v8rTkX?}K3EG+5%~n?Cna=bq zrImI|)GQtSO89o$a&VwmU{`+B>7<|xPtAv|u%=SEVD|8f8gY&JDRRYrikuU6MeTD{ zE~WKPjtyp8<5_s3n_3~w63v#faaPu!t>xn!-8!%@qSjYi$@e}ZGWYndHEVS2rb*Qr z9y&eHW>IOiTcW$4c`@R>Cu{WkQ&Ppw%`&wrt88XXY9tNlqg)fz?&@vcahoi-cA}%Z zBZF5nT9R0jQtZBj$=F5}2@bI3YNe~WE~3`HH0it^Hb;qc7Qnt}0qXp#V(x1;*2?IX zqrJcE+_!Jij=;dlK>-0fC-1e%S4RY^qpryB7GGxlF0ihbIaVlDEt_g&U(_pBLKw^H zbi1#uTcP~qSZdYkvVN?=1+4*zp}N8w$($Q*txId5kZ%`FZ07UJ*}fzecCS_BYsx!T z(Ctksdz5N9EhkI7+(OWLYDrp8mP9&2R(DuSiI!J42emv}+f%EknMilm(rT090jrpk zdieTDn--HIS~Bg!M0Etgq*u(GlTEtYm{VUfds#BI0y=v1GEb0RwXhyiwA57563A+^ zm!joRnq-M$4J$m*%6eG>k18>&zzNnYk~nJ)qu%KM4wFyc4(MlJ^^7Z)MwpTpm zM9r0q->YnEB3pA+W*yiH>Nn%#udqTk^#^T#7dA}G5*v;g)~4^!ohJgl{Qdb}2NTBK z@HB@hW?x$5tTYXLoML&Uc43vQfpl~6_#Huef8D-g@9v=Ofi}zCk-5|FDCTsdy^gz& zPLqmDdDhX}u|s6T7V*p$o^h3#JQ%#=wIWNr*h%f#9YsrUjI(M*sp4#r0J*zzS!)r@ z{4PjtZ&KKk6icSMf|x+tD{3ySy$O3%S9FSO7W*DJKQYzw_Kp21hY1{4&<$fNTP&X4 zxi`*hcl-#UM*o2|ZQ3evPp0&{{XJ$r> z{n=~(&l@Aoc$mp}7}Ki7baG>uTU}wCjZJQ;%@DQHFYB@T*Kcn+km038$<`yAw(mLQ zadg6N|NrpUj3iaLh^^rp3R;Gi$+OH>x`A6>WCmuCV_4;ARydkt8N^-t;DOwcQsl zT#+Y8s(fCtycnvmzSQ?70twb3BxHx^5T>uTMjNK>;O_|}pC`zblo#@&TZgYl+Ik-5 zdyecjxw9vYD<_8Ae~zuEVU3mBI(D#?ioAxECR!YoR2N52D@ml5f z3as$8UL@X4ZTvK=&1|h9>|~R9mX_$S2qU!*WB>`Q&4q3?>UQ+g$hm6xO5 zd9r5N&qzxYY?MtFika0_N3gn%5&SJurQ{GoUn$a%UeHZB5o#3wWE6ZzkHpnXf5C2*Y zb$a+7)*eNu0~9%eb(Lh_HjT$O_tZ*i zZm(Fi={CD0ELpd7(>f3B6N|JGK$l5S)+EG#USKuXF;|ir95dWF}!#uhl9(m5o zGIeLfstp?mL3LGjEQpxB#r8B~=>7%``86}9t_<<)pkvc-qG5QRXmC^!8;lv=rr!`< zG{|hI_D2;~-a|e)Zh!X`WVKkoyA?KG;s*(ntNSaK3Dbx3mLvI22Xi?8deNDYO*|U+ z9#U}$;i$ZYWBbd|Nv=-<7n-vgsLP~2>}0%llKHWFmmNJs?G=HfTf#%#?KeX!t5&-# zI6kn_R_4Qg<2Pu(31&7?>!C~Kg%O*>w|FwkNdkKZ*4kE+Ss_18+kG+4HXxSoH=tXH z4`GmMVM)V-)jvYcld*qJSiv^)9R+#TLUu=zKbK);B-y@P|5s$ANM;oeznw3$TIb2E zY1(nCrsNANg@hH2n}w3vnK0uRpCPerifznp8nb2?5YV-69t7|**H_GJks`>?Q`qAa znY5bLNt?lw`8YYb=O+u`s!9gZGk}E zz4#}#w?v}q-Xl5;x5;18Iuu5GANNJX{G9}CzN3P+K&`-ca$LEgk|#Jo`^+DpA1Zi9 zTTvb`#5agshjMMQU&&tUFD&^WX`9&)>!-V|N|TjWSa`qoPUy3(FXnb-O{|Z}vbioaX38iMMr}M;n?tP1U8Z=o6 z{O&`P?KO%kHD%^>^~|$p7yD`kFYSFQ!2;%#aOTf~3lFwK5P*`XBjkV2s_}=zVp{ZUPimPAo zH59}mElE%lCsiX(lI=gR&Js~2n5d;=sh6oTF(ojH%V6KE`HG>SbhF}aR`AQwO-eeu+ED&uz@2$SI@%YG2msUJ;Y_A50*Xm|(9QBJW``-;WdtPm6JGV0h>Zk8u$} z`}gkH@vE0*!p>P+gH{n+6lFmYE1Gm6xPQzYsl}cex=%gN$uGZ?m@QTfme$ydvUufj zZaYFX?3R>C>X)<{_dZ>1k#3aZ*h^gvlq_Cb`#~%8p)M=&{!#3${kEl3mW0go(0nzn ztX8vcUzrV4e%&)~`Zg~%lq{mQS}|s0t?7^cvps9aJhWn38~Wp=71!F3K;E$UF8lam zamX8@@oM`AOSH^dyt+#mu}qCyS_AT-gw7jXrlx1;yd}$KlY4eVqNA9-U<(4vE7%;; z6kR2qGi8QUwPTu`=g=Q4(+a&~eU@~K_*wM1RkccoX0$ygBsP0)>!IDT#*?2`%Mu8s zH09d~nupexI@O!MCTIrMglAcTxhsKaxy8ZQ0k&aX`R?aljADZBb$HC28QU zwjm$Pr&u!N)G&5|raRAX+{#CDmenvR(Q&l{TO^B#3$IMOI7Z(0>*`HAFuQ42__Lx}Ew0=YnEP|`&Q4-ab$RGd@{o2KVp*wRGL%_CSzuyPGV{2Z zFniP~uiXj!ksdyq8j=xKi8eQhOnJ*o4Oa3OoSdp&ArX?I?(mk`ACl!OnXLZ<>wZ^y zKhPczx%#vwyq4Xak&n};H6?;D57w7AJIw41V{~6__fT9j=CCv5l3v3YZBdzHNkHbg z-&5FL(%#ptyD#x9Ogcbf^Kj!}Zj@>>GDkV9mi@UU?<276FD-fVH1z{Hw~DfvMs`a5 za9lB;9LUIH5#q+y$61c3rR7-RUXN2PTlVPJh%|4c@|@%YVPxh{>@+SSSRHbm0HBTy z-UaCJV2VT{YIXvf5kfz`GD3ebdN&e;_q|^EFRa zAhi?i32BpM`v-eZYw>ymYY(wyk8XI1Jg}vj3~o5gCX;-QBum8I?z^7+{T%x##i+)T zDM35H{T$u3-fDO9zsTe!)#z$R%WSp2qL!4Nk2)kDkj|2@Z5xV9#9(zvmdl)@H@j$* z)d${=XCkreh(x)~$UiE1diUvF?kC$R;nLE*M`CXrF8h);WT(5UWh6F?t=4gLo}zb= zM@kWs_`huid78awEJp@nf{~TA7o8)S-;7i;p37=^T|8^H1@pBMwLRseFtwZ{Ul|pw zu8pJ3g2n79E$)2Z{0Jibidb!hpfkef#vvT-PU`+Co6Ko1?;Evq^7Y=a6<5eCS0XWx z#2QISX%@ipwK;nvCra~K4aIzjDTm~E=66{ZEoa*e4e=j(QZZkAJxVu8+y4HN7Pd>n z`&xj_yq0AtC5a4bOX6%cn~=;$QXYg{+DEXhq!gzebVH=88k#rE?2H)`>Ivb+P$^O ziEP6awlqW8annwTQ<7+zNxWcX6Qo2z z^Om^>YE$C_brN}G9ZlJ2wJ9(EE|5cP2O|TFDT?_j3rb)Y5;)m(NQXvIi-OsNU^$sZ zCbG$ioE)zfxU3e4r(f76f)46Z)~iFJn|z;Lq1xn`@gx&XWUV`0k*9?O&I|OE`<`Qm z+lcaJW+r|5Ch}s)F~+&+49IdMx#D7VjFcIm#H!7WGNByWj^9eb3sWaeqTmJD^FMG}V876u%?e9q8{*meWLYh#aBos%E-{UjxX%g`)X?`SuF0oTRo5G` zHh?zhs%w3;KC}r@o%NFmlSjvE(fOFXl;es_ev4`p?$M9+qdn%z+5jE4ez~fBVN2DD ziH@LLU-_NtPWth4W37WPGv&r>Ch|hXrr5M9j%rpGe@2TZ;NDqsGN-6R8G(3`%%b^n z7Asguoua5~iDBiQdG3erB6+wRM7y*5lY`SdnGk>e2C2%XixbtfWbz)StMy4EYtCVb z>aE0!Bu&$3Ksp%u;Xbpb$~48S@qK2yF0+~v73(;G)napL3VkKJZ-|_ub-n*O&q+5` zVpV0Wfi>vh@j1sl<%;p-v1Jr9BFB&*Qe_sauu&wPyp#+=5F~t8B+BP#V}a3KtKH&s zly#6WQ17dgrpjX5D2VUsbcO($XM>TJ6V};9%A_GCKNrv~2Cd`Z<=S5u{9NacP>9L^swYiM>dZQ&>fjEL%aQ*@0Z?x;kDYZR|`_ z!YvMQdzYm8ClS=SsCKkQ3hUW=zPBKMo<4|X=!JTNTzB$knf`NJVU1+Ak4(W>QQJpW zwVb9fS7K~8R*%sZE29_W-POs897dv-7VH(8E{+z_Ls~go6IejV#bh~z6zxYH zXd7uN{i#d$S#v^MwaHrZ3k)I8l^l1Tl}}a+Cb9Z)vZ(RCe1DPHn3r|n{EXOdi({0P zf_G$%??4ReC?=ZYh`o-;9I@=qSWFNdn2nwXm?J{NaijQs-^GyN;Gmene4;WWFO|iE@yRt#L4PJh5MLF4)l=( zGLl#mk_2-UC$l!r8ppcy63nT(vzeSg#QIHGW0^xqU_a;vb?!$V2f!`mDUy~hC&wXh zYr(An_cMpXpsUPr1Xu*R&m5&VZX-vxV{@AgCz!m{1lt25y%D$HDF8L2Vx5 z5DD&xk&EQGXmBUN#Q^0vE>=JVaKD*Q5nMbF1-b$qRGI*~a+|Jk=dN&w0heT=d%C%s z94dpZ&PEk*=^~Z@Rl#Kg)xc$e%MlR^R0mFh?s>;)aL)_|4s^*j&VYMupc}locP>~8 zE*FRc-2+e8z!NsaGN1;q1*pjx3<7F{u7x+qpo`&g4h9#4p&$$eU?^ln9T==U>H@#P zPzwf6;5_I)c3gmg?ijxagO`~uY&TRfq5+3XFi^AcFw}&hwh4`3XaHP>p`i<1Ax}5T z8`^ssM#32Z%pd*YW4d?`88I!RRj1_@Zpi9?{ zm4PlWR&l~@z!wy|Z6pfcfw3NsyD&B};2w<4MI(K&9gIE(tcI}@kO^aF1J(e3zz(1* zXY>=$9md`UWWm@^psU-BLqJ!$8;1($31q`KhNHXNjRC;#FpdN6!x(5rFU~m2h&~)1 zz&ICp2;)2+{b2kFcm!j((YO-ErAFf_tMOMD_rkc(*?1Ag6VAps7|+6Z&S<>E8Q%SR_CQP+qss&RW z(bN>CCNMRFsRc~UVQK?YE0|isIhTkA56nw8Vb{J&NK?9kuZ%mnI^$B z9;QH;Cc-oUrpX3V2u#5+O)Y9#4bv)^)_9ptz;qm@NOw~*Oi3_ZlT3GEx&u=tO!r{Q zvYI}^^Z}+%<#;df3V0j%ir|ZjdcT8i@`4iPj4*)ze3>G8~JVEw}9Ua zek=G0@H@b72mcHBo#1zw`2FDbf&6Ry3Gm0kM{;~L_>-_}k#`fX@J*3H~1VyWq2o{6p{$z~2Y|i05B|e*yj#_&4BRgMSPDEBHUa zzXSgP{Cn^p4ZI4Tfw%JloCFDiGXxnzR|u{UTp*Y^p%8?E5ZsJHVF>OJiWCqkKqwEP zB818iDnY0Mp&Ep$5NdEjbqKXMp)Q0v5b7C(Mi3f8XlxW(Kxhu3B?NB>tst~^5xNK^Op`A1C;mgkcbdLKtokA3zxGDolkC0%4j-m;+%pgt-txAHhyo`%Lv(^DL3DvALv$5IcZgPqMIjb}SQw%QCn^w&LG*%H9HI?kNlq*Q zu{0-^gIE?~d59GuR)APZ6l+7Q1+fl?p};1H^*C$=c7khegbXwRCILYJ=?hn28ejkh z0VbdTP#l;67=cj$sgi;~bBGNLViSmsAvOj21O7l`fPQ!8#O9pX3}Q<_UY!~qZo zLiC3?$VB-x;&2lNgCog265t_{vQPTbC;1kEKd85jY|^}#M+1F#(!08pJhzywa*%YjI=j~9sM@uRu(McokxjAf`b~ zg_tggS)7;&F~=Z2G&m8HJTZx{A-?3qHxU1X_*S5p1}C0#GQmmYoFq6o!$~$d6@pVi zIJp^|+~H(}QxOhCE+PV{m@t5dGar}-%mfw!p}>#80$>iDJd94o;pD|Rl`uJ#gHu^J zmFJu)!>JM0g;GF7mPIcha08aHGZX{;n2%LfN3MpNhIlbxqI#|II za4z6V>3qYr12+}iLU3Ea5kg7>_a27AFjR%11K|?FA26JSAq$2tFqVR`CUhNq62=r5 zKf&Zqnki4akZK`a@)>+d@TEx&fbWzi$M=x91pgfT7YLR-iPjOqAP5UdSmX(X8+nV< z0y$4OdJ4l2lRPL0g=_I0Gyh_sVkhu!D%I&cEafloHF3_0nUZsTocY6;5-q| zQ{j9S&cDOi4#^EtRY(mXO@;I$q-~JSK)MR)31k<@ zAI9K^82oS*Kit9(^iYLWhP4>1bztoX>o!>T!5R%~8mup1{Q`Fd?la-O9PYc|9tZaf z6c$nVM-+a6!gdrXha&AzWGaemLXjvGNkWl3DDn(N94J}{Ma!aSZ4~uG(FrIz2SvA_ zXe^56z{3oWTJY!(j}`FP29HR1B*NnzJbS`31fD;`^8!5IqL?d+l|-?6DCUo1lTmCR zibbPXB8p|BSS}P#C@r9LhcX#TIF$WRVxU}u@(_v!FAKbC!D}PD9I&;9tqW`eU>ghD zY}hu#b{Mwnu)TrJf#MR1_eJsPD83HGPoQ`@ia$j0T$FG}iP9+193{G-#1ND?ixQbA z;Xuh!C|MsRd!poAl#E5mn<$xslJE6pRH_$B4M(Y+D3y*pMk z?mWufL%EkIr=fgNly8Re{wO~a<(HuRew4q2@>!@*5*6B_LO)cPiVBBNAq^FBQL#2E zc0|R^sCX6?e@7)Uk;#XMl^dw?9#xB?YE@M2imDS(bt$TzN7WouD~M|CQOzIKW}w>7sCEw3 z{zP>J)jOj4B2?dp>gQ4YE^0WVMhVoYj2g{Qqbq8RMU4fh@d7n+QL_kYRz%G%sOgWI z!Kk?mHFu-tS=79Rn(t6cLanl><%?PYsI?KbZK&M{wR@rVTGajxwcn#oanu=vIx|sc zFX~)HohPX4jJoAew+-sXpzcG|BeSa;>WxIbPpIDv_4}ay6x3gi`q$8)FdEcEgI;J5 zLOG&n*a{72py7Tryp2W$(Z~~x%A-*uH1a{C<7jjqjmw~Mdo-Sa#v9Q%4UHe7aW0w^ zMw41-(h^PjqDdf{%te#UXmS!wz0kBhn)XA}0cd&}O>@x9fMylY%n!|`p;;7~JwbCb znr}h#J!qbR=I_u#MvF>l(Ht%Mp~VQan2r{!&|)`Q#Gu73wD^ja9%$JHE&HM66trA| zmgms&0$LS7t9oeF3#~?@)qJ$tfL2G*Dg~`Rz`H2C%fY)jyvM>j9Ns(OeHPy7@P2{T zCbTY$)`4gphSs~#`Yu{K(8e8Y8lg=ev{{EX@6pyBZTq9`2(%4C+l6Sm8g0*^?R~T} zpj~OS>w|V9(JmD2Hlke=+FeJxY_wx&UjXe(qkSi|ABOfnqJ1XXzeM}5@Ug(BDty|( zCl)@J;FAKM*XZDa4$aVEB08j@!$WkeijGatu{%1BM#oTe+=h-P(J>1h9q3dXohqVJ z6LcDYPJ_^CDmv{!XCpd$qH`T|?ugFA(0L8I7|^9Qy0k-=spzr|U7o_%3BHx!>kHpt z_%4C(&+y#?-{bI&gKr{yU&F5u{1o^#h2KE8MBZ$bA+bWcS0$LRhUJqn;l8T4q4 z9zD@x7z}xeq-bp;u${nvPz-q1Qw7=Fz(gdM`rnE9m2d zKE2Rq8u}bWpL^(&i@uf6w=4S2MBga%W$4!c{X)@i9{M|>e{1ybiv9!9KM4KjqyHBS zFk`@U3|NN&u^5mAe+&Fe!oLyxyTLyI{xjge3jTZH9|!-t@c)E?(=l)x1|Gq{WDLy0 zzz-Ny2ZKgp&^!#=+3{J-ocMPe8A)_&55r!PVkQ5AgjiC~T zw!lz-3>}N1voLfehVH}Aix^fI!zy7|D-7$6VXH7~2ZkNNus96Mz_521UIxS4VfbVW zUxDGzF`^bmY{H1&G2$IYiWq6b$l4e=6eFi%GwOxTYJe`2DHi4`%iJtmI8#7USq8xvPx;%ZDhfQe@@@j52H#-zfSR11?j zV$wiNnvO|ZFzGHPy~AW1Cbz@nKA1cbljmUaN=)95$(JzsHYVp_vI9X?5!4nzLl86z zLF*B81VOhD^bkQBrtp|j1XC(uN)t@!izyQ^WiO^Y$CNJ!u8ZK72=0pD;Rv3D;CTq% zfZ$&d{2U>L5mF8z%@N{{kRXJFA!IE=jv^!-Qw^9}15^DmbtR^zV(LRoRWZ$sX_YXo zEv7|dS~{jVFx>;w8)5oHOy7m+Z!yCIGx}jh6lVN}87Y|Y5HmhvCXboLFtZkBF2u~W zn0XL0Q!q0fGhbs?0n8eWS#vS#XUsZ=SywSj#q5cgJrA>wVs-*%+cC!tb82Bucg&f9 zIkPZl3Fbs#P9)}B#hg2s^AVwi5Lz6ejS<=hp|cTs0HHS#YRBB>nA-(&hhy$c%#FZY z74zINuMy_?W8Nani@?0In3sup4$Sw!{H~Zk2=gan{zA+@g86qa{|gor!h(ia;D-f4 zSTG+8)?>i|EVzgTnONXJm@~qPA*>U^eneO_!rowEO)PANg&nbQ7#2ogVKf%L#E+%$ zV|D!Kj~|!g$IbXL89zS2k9Pdj2|oqmr>*$u9l`~KmqB<3gpWn|JcO@D_+cz6f<@J^ zC=iRHu;?BZOIX|mi$`GbIxIeq#da(yh$W4&q$`$;#FC$}BpypEV(B<6y@+KzmU&=V zH7sk5W&N;hI+kt1vcp(*8_S+x*(WS7g5{oAUKPumV0lL@AA{w~vHT#GpTY9GSZ>FP zx>(T_E7oDf8LYUkI|f%)!ph!QISniKVC5;Se1KKXSXBY5x?xoaR-MCYC#)`u)nl*J1q$tbc_K&e%{28|q_2 zCv2FC4H4Loi;ZsB*bp1LVB-pG+=h*ZvGFW6-onN=*yMvvGq7nHHr>SLX4u>Zo2O&* zdTf4*%^$GE8CyzXOCxL^= zW9w^dldvre+qPj_EVkXlHaj9b5z!hEJrS`T5l0a55!-uU`*3Xk4co6{`vYwMf*oe; z=#Cvp*jWZU!?5!dekqGz>f)DB{1Sm*9N1MHyGml0H+F?#*A46zu)73yx5Dl{*nJgy zW?;`O?Ae4phq31d_WXgp#jv+A_AbHRDC~WRUkl*ZF8I|Szs|w0>DcFteKzbHiGB02 zZ!7kl!M-Hy%fY_4*e_v!UF@HP{R^>w6ZW6P{&ehrOxp)IP!tF1<3JxAn1%yeao_?D zia6K}2T$N&E)EUGp))v?h{J_&xGfGJ!r_NFQWQse;>bK4Er_EPakMgyR>jeNI653h z&*Er0jy}XO6OI+Zu^Kow0LLcb*eV=*hGV%n?u6q-aC`xdN8tE%9Jk{{7o0eQ6E_iQ zL1Ya?`XRDEB7+gR7?B$gc^Hwm5&0fbKOm|sqM9M9E24HIYA;UOaIzszMj+Y=(H@9a z5ZxNlhY@`S(W!`jgBSrZH4)PoFfk%(Q0 z*tLlL6{o7;RBN0Xh*OJjYA;SDz>U&hN&B zrnnG<3tMpE7%rsYB8Q7LaIrfs?#IQ`xI{Y?rE$q0mjZEV87`&a(ig-xK)fH~HzWQS z;%_287ncj+av5B%hs)h?c@8e0#pQFjoQ})yk>HAiQb=ftgh5CMLBa|o>_);hBz(Y? z(zr4eSJH9ShO5(X^%SmNMWP!L=OU3IsSc7RA?Xh!cSG_NB(FfS9oGusS~*;6hHHIr zZ2_+Rh-(qJb`;m1;+={`iFSzZE+dXi5J8s9|_E%)o zL`F+w_#vYkGKL^y7&4|H;{r0WaHju@ImcMjuDEbb)W z4pn=MJFjs^!(Aipy5Mea+zrCr)3}?0yAN6Yk$UKY8%gDTe%xq+SK&FN) zBeDu1t0=NcBC8^@>LIH+vbrE^7_!D8D;Qbxk+lR_Ymv1LS%;7ngRCpaN<&sQvYsL9 z1F~|FEh4)hvTew&i0lT)_C|ImWcNh&U}TR$_GDzwMD}82|BUQi$UcJX7-XMEb~3VW zAv*`z3^^r`tGHR7oiPut?@Og#M=PY>hic|1+W(+7Cg0MAzA z*-<=8z_S;4F5-D@JRgkb6YzW!a zW+%?<&Y7!n<`JAZoHK9X%ttx%v4F)l)r&W|TP%HXlR`al%TI{GLn|=J;u?D56^`{K5Zyk7(-?PXZJr-~fMFQ$^ zfH|S;)aUw%3ROC!_wseV zW6`6+^})RPR=FSL{r#`O(A(+_)@~B&rP!O zi~rSOcEujvLSj?@>hRn9ZY<>f)B8k_9rlRg3iHd2(1)qh;v3cTy7|gJ&|UA|$N#4F zw;6714{t89!+*8RCPuoc6=N)!?8E1xd`n$uzlDb%5hgHapJ$u;K|ZD@RrzCL#Ff}a#C?bsa>pj zGxmr=#N5~_3K6s9u+Pj}V*9_e&|81|KZ@;ZSs~@$71(o$(#={bmfP&K9u%EZhkos% zu;;m{dMi(dDrzm2B$zsx0>Vxw{wn4DmjdYY2ll2q|L@Oj|Et7`ElyyY64ecfZq?stLm#xw(Eq2_pYKz$>wpd^Vt_`hTt=&+qfX)0ajBQo8IO}RS?30vP^2vyT{qO8no^4oiAXNe9J`0GR! zFUy=zJu@mUbke^Y3#HcbhL+!dwW_}0nBhNqmscbGB@Lg=eQBgh5w)b%xx4>kkZhRR zSJFf9))FBE_aI3-Dx~KYw9>L0DlU}F_Pct5Nq%`T(=we5xgEM%wWGPdL6Ukrx8QE+ z%PO`;iqT`4Yf2X=W-q_8TF?Gd86~b;UR4$$=?x0hpk9&I)(3VlFa3bkxX*U{&rdJV zR#{mog=$kIzxiAB|D&5)9c>ncN85)kj`vMe%c>T|67~7L`mu~+Ih5=9^jmi=fj`p7 zw4vX+VmYKHEL^6TcPV-dqLKL~wv{LpId23;b*nx-T5~;hX_pdrGQSGbMYh%`*ZZF} zJ$0gJh4q?e=O}RprMQ#C+-y9tMJUUAgRRnq^#h4bXXd{@L1i>=ZT0uqUBzBPmy#cu zlhoSL+WPMZS#^G*qk&*PtJCeE9+pb|Wy*cyUoFrf8(~`GvI@1MCh79>qgH7WtF7pf z)tQI&zO%HWMs?IUiE^>LB^vU@=eo=k(${^H^k2bFer{s5e9q5g{!05Rmd~I};QUtd ziNY@F0#qB9Pn3K2^@b0T{4}zyy7<)AP7;(_->N@-L80>WP+h4D zPc1!ny#6qg9neQC)&4F!%jlRe?}L&Xy~!5GQOy76 ze0fV#s8(jziL6&CBZ?+@jV=;ldCQ{NVp1F5Pm!{`%|A!V@~Tws>bBBPVM**zJezx! zQ>Rm}Z>Xi0D}O!HWu*S}zRpGGHT!a9Bc*|zNo0Ex^T+eVUeKBsC!J1FwqFt{aC#3N zR#07H&m{E&6Rhn>6?o@8i8-l1tDFZb`sKR)qeRaXr6OPb8*EN);r}kGoF_xh{;NnA z9ijJGZAysv`$O75N8g$feLr8c%z4QwQFo>{-#s{d&DQLO&WczrsebDR8C;7jqb z<>J{>%B!Su`%bpiS~ADGo;JXj4X7&6XbOI$(e#R7-jqupkV*hZ?dS8KF=d^ z7tnL6#=3mwt*r!&4oXZG-ekY2&nHtNRBm{F=}}fy+Vyu6gnPE$Uz5IuU6QWo3)k{Y zb5rL@dCkAg-bD~JB8Q4|ENPF`PLkyd9RW$Hk(M;A&|kG)y1jo{H=R-qSS5YxEzd^e z6{*DtX5fCKbjve+{M#hUyCYvH;hy9;^|7oyoZ_2`1&OfJOH3u?iFO#*)`XCgv~oU+jk|>+Vr*cK#b56bjIHyAwAfot9)Y8>Q8= zvTk%NNzne)$IhJfvir8k^%X7A+gL%xoZrrKX+hMT^gB^h{|g&$Urq=2WYaMSU-FJY zuw<9aU9PvBpDV!WnF7o&Y>T88)_beZis_4-|KDjkXSCN`t!#~>u~2$o?>e1&|9WA# z*T6n}hp5z%cReZjERB`7snh7-1Tz^vMm5*{A?5z7mft1b{EV&3mr_;oSnmL#;lB&1 zHY<7Gs1{N!*54&|Ov~b_@b^+RrNG6B_JWkLq>I9GidhW^OY}=rZ%|f*eR!H>Dm}AA z+biex-14m^8=m)-zc|WD3B_h=M4dp<=xSw!tx($$?6+j79n~Y&vw~V)((dMtv*u+E zQtAOE4~lyIZy#kAU5cyiC88@;5w%HLh_@^vuU~A~YrShM$6;cFr9fSO{p)Qi z`so{cgzK3WUWw|E5LWEnAnJ`e>qD&+>dl|_ad!7W$)V-CfA7g_y(j8|FNLi$6txhw zt0RQ(4eK9#oNFgIF!@_oWui}yL@PTdvdT;t9i>S%c^1(^%Mi;oKQQ>(8D6`h_utGO z)AAy6rl?sdMtg+a*LptxNEG&%_*ySuOYgH~|0<(%Qral27cF>7@ii!Bd&jWE5yAF? zdTNSKn%XAKa@sDiBG&u^%mmBKyfe(i?`N2`6xee24m#J+aXJ5Ae3K^9Vd zQhmk$v-0SH?MHogCH*-1xUIg(<>)!-%SMV?Uzek~i%cYda{q zX50Ri?)M)@gy=aL`S0(ilt-45XOMphWvUL-KIOx-FZzKqA43VI(cyeFy2~WzujUUf z{l2cg>k&F*b%_6SbU9x$ceg(7^z1XydSL@fb7S?Wa4WMZweri-?w^gO!LhBM>1+ro zcNdX`My%IfL9CxHY0H>Z|CCzG9l(Q*K1T zw&!$-Y?tjttdYG62+qC?<@o751q}BrdXXAQT*zicb`Mj}E z^Vw>39eP^MvhmtlItY$Fl1(2utA8Z>Tklg?FR~-)V6B#v8PbmwxsUWGxAbgB{h>xGS{tK{Q@xbjw11YU^**~&4(DGbbOajV+Ga_VyrYlMFOI!U z*YjB^FIGR_Q9pT4>&;4OWm%uuHp^Mx;Zp|=_wb7xah*=5vdo^&)=Lw%9=UM!^n(Z+ zt9Xdc3b8OB4>~Eq>;K0At90^4Sh9LFBtDtCY%gw?!==BT=-`yrVcxv65X~)|MtAXF zBvmIV>P-9cMM~S;wfYF-X+i9v(bJlE9m=%Nu5@VKSA8kGre)DCcis0oXS4Nz7H7{C z=9O-F9l?HQE$Nj(!CE;w1;p|tLVsltopbj(;va9tMSp8}D@&ui|Nm@SUA!hwqI9C9 z;U7q={4<;JtEdGL9ZS>6xP$48rqV3PW;Z7axw1Q@_|idKbj(E)I*?0ZoouX_Y{$KYQBPH^Al3F>7pfoj*jpZ z>F63fW);qNGd6U67jRM_ZL&=VP9y zj1qlyAo6!jGDa!mu4I%xFkZLQ*hTVm(MLY!i}B5pUb2WSu>Nu2QU)`4&^WxbjWj+= zICy$5`h#S=dq443GmM$CCp6P_GJx>5w`B^0UBk=~IaT`8d^ zr&3gkAYJJwAiXApl0;exE#wHD6hK-aARx2H-s$_DeV$7K;_v@|-}fUQbI};Q% znVqp!*IUW*pXv2I?mn9Pb+C&CGE(p?Kh}2KZ=NU#ch;)Bi@N|k?^IiDjas0efaUf! z6Ul27sEPiXER&o4ZKNI}s_B8CU{qZC83==pQ%l=5{q*6(^j6a8ai1lG{&Li^qkWCl zwL@g%S|7e=my1M0g19KSak~!k?Ft`m-QNYi;SO}VH}Ju??~bkgy{1G)l@>VD?*=Q1 z(wM)jnp-0X;uh&*u|Xky0ux7WEbe5WqHs0^f>%xBBYv`HZ6EDh4ADK1=zYVRqAii1 zFjMZ$NXusG3ujm42fo(HxkYVger?SvO>hdGn%r&$T{=oV!;dDjC#g1Pt+8UDC~N!9 z4>y}umVN$v+g4j$IYhRG9|ZV}gwIiTg*5TLKUKJ#LGK;=J#u7Bs2n0Ae;Q!@4nV!{FSL6C$N0&9ScLCWU+M{e!4>fT z-tZ|}P7Vm2=P7K%8|Z=$soX@nIO-?MOp<=ZWVw)lUSOyS;pROX$KnEwKPlxXEiqEW zNp%-z_Y~-oN^Rf|7yL~MRXPgiqf7kF!YrTR;^)*fn$gZC8f$6n*8%{M`WeO_2!L5} z)=6|e2oL2wh5W617yFH=3SUT*SmL+0JAN#VvzQ12>07>0r9kPBhs&BNw=!?niytO4 zMg?;VMB~ckImx5ji>B)N8Y5rv#PDKit-&3gC^FSnQwul&-rVGf_!D`~Xq9U+MG8bT zQhUM=?QHI1{^6P^;g52nh`J4*d?t~$8Vrbu>pmnzK519LgQUa%UxLYksLTdDC26y9 zH?EgOVenO^NRpp`!2o(l)f9hWqQXs4U89%8Pyj$bFnJ7>yF}xuSl_uf9z5zA*W8Za z8R$OypKPMj6868Bn;$C@c*7Hc^8l32K(P$eL@khVI^akV15_C%s{0I;VYujVzwL2h zV|yNFALc4V;b(DzD%@^?P>~FTapE0jCE&xRFe=Yrv~dQ7%)k1i9RmMwm>aP*p0b#I zh>HjNursM41>3Sszzbl=v$L#@TU6j<jW*L0>uo{4u3r5QIH z!R1ZhY*#buNLK+g1@5<2yC2Jy;xYTLONN(+Gx!6INM>QHtSL(4poUFo>zp3&P&XP2O<|yn^jFc#Xw#MBx~8M%Tac?fv|v@LS6F8 zZs)t`QrAnqE^ClnL4VUsy1~wE2FM-olE1@J)sJS$TXMN{TQdD<3?^9`xsC z=v)F^ZRToAS+IaFF-{%x$J^y4!F>)>TLnod!{-_46zO!SJ~Nt zv?{yW8J72)RyW7f-rS{1S>lgv7p+a-!~GWy$+;vaT;_RE@a^ar{t zX!SDu9*^I(JQf=@A)iOo!*1OnN;G`&g0`l^HFbgF)Fd>(@RlO}G3vOHem3JK3)r^O z$=o{NT4X$8$1EGa+Rk-(>$NVS;LE&Y;GW;*fIe6k6E&RIaC`HLE{^elOzVuV!MKQX z4zA(wPKz&AU&FDXh*BExeL6glLe{F>?gO&6t{@A(uOTXgRx*EqK1$iWPbERujg7@g zbGK6&Od&m2;ceEDLC195#jQ`{mFw1j!f+RmVUxAWU34a|DNl{9Odfx|r=$V~vBo`r z49oprC+vDoo#uo%U_>xv1H*1HRbxa=VGDWfxy)%UfI6>4DXK% zhG|w$ZstaAB*X>z8~HTMsVe|9j2<#4-^IWMi2*QO!zOhN+~YBITG!_e$Rm;I>zGR$ z_e_6Y&vOg}EC%2%UlBuAba`Q3(eq>{MJN>(y)>n+Da>Px`z!`HR;g;re`r(!5sw;U zZV%mz+Ye6K)oPtmbmw)X*~y{aPVS0Bv`(MVUHd@Wy+I7HQLz6r9V~ z{yt;P5s#8%*)U@#Cmx1q!$`mHYRPln>1mE!Layr~9X9oitk*1Da1Kv8Ps?2O>K;#_ z-rC=bpk8$iW6dVjKTKh)+}!u{iBfJxd+fQ&&G2WHo6+LLw60pf&r=0%?+-*e6^5fO z>#g$GT^nlwhhb!(j)X{0flAsY4eP21ynFDA z7JAJv19Yxuk8M%R;~#Ep_&xUB5r-_OAR-<$#D&!!)2>aGU|HagTGVK2=-AreY$L;z zGCNH4))>KCdMKy>9-KmSlPjS`I(vb79ilVWJ6>hz9ZwsZEZO+bi8{3YcNXyF9E&@H#;W7o^5@K zX0fhq3gNhp z$j<{Rm#1xg4(eycb8;(Y$5|(f)v}Roz=##wqkc^!>rMNJBbFW=D-A9k^6%L&5!D

wC#j)Frk^yXn!-@dM`$BJf-aVxK9 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 + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout-land/activity_account.xml b/app/src/main/res/layout-land/activity_account.xml new file mode 100644 index 000000000..46c7f0c15 --- /dev/null +++ b/app/src/main/res/layout-land/activity_account.xml @@ -0,0 +1,201 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-v21/activity_main.xml b/app/src/main/res/layout-v21/activity_main.xml new file mode 100644 index 000000000..643235d9e --- /dev/null +++ b/app/src/main/res/layout-v21/activity_main.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-v21/item_preference.xml b/app/src/main/res/layout-v21/item_preference.xml new file mode 100644 index 000000000..74ea3d3b3 --- /dev/null +++ b/app/src/main/res/layout-v21/item_preference.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-v21/layout_details_install.xml b/app/src/main/res/layout-v21/layout_details_install.xml new file mode 100644 index 000000000..0aacb6fa9 --- /dev/null +++ b/app/src/main/res/layout-v21/layout_details_install.xml @@ -0,0 +1,160 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-v21/sheet_base.xml b/app/src/main/res/layout-v21/sheet_base.xml new file mode 100644 index 000000000..79cbf1e37 --- /dev/null +++ b/app/src/main/res/layout-v21/sheet_base.xml @@ -0,0 +1,31 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-v21/view_app.xml b/app/src/main/res/layout-v21/view_app.xml new file mode 100644 index 000000000..b0024a6cd --- /dev/null +++ b/app/src/main/res/layout-v21/view_app.xml @@ -0,0 +1,58 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-v21/view_dash.xml b/app/src/main/res/layout-v21/view_dash.xml new file mode 100644 index 000000000..e23b19c4f --- /dev/null +++ b/app/src/main/res/layout-v21/view_dash.xml @@ -0,0 +1,51 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-v21/view_dev_info.xml b/app/src/main/res/layout-v21/view_dev_info.xml new file mode 100644 index 000000000..88e0401b1 --- /dev/null +++ b/app/src/main/res/layout-v21/view_dev_info.xml @@ -0,0 +1,60 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_about.xml b/app/src/main/res/layout/activity_about.xml new file mode 100644 index 000000000..27030b04c --- /dev/null +++ b/app/src/main/res/layout/activity_about.xml @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_account.xml b/app/src/main/res/layout/activity_account.xml new file mode 100644 index 000000000..118dd56ea --- /dev/null +++ b/app/src/main/res/layout/activity_account.xml @@ -0,0 +1,194 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_details.xml b/app/src/main/res/layout/activity_details.xml new file mode 100644 index 000000000..bcc07f6ca --- /dev/null +++ b/app/src/main/res/layout/activity_details.xml @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_details_more.xml b/app/src/main/res/layout/activity_details_more.xml new file mode 100644 index 000000000..ceffb75dc --- /dev/null +++ b/app/src/main/res/layout/activity_details_more.xml @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_details_review.xml b/app/src/main/res/layout/activity_details_review.xml new file mode 100644 index 000000000..3b5d60278 --- /dev/null +++ b/app/src/main/res/layout/activity_details_review.xml @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_download.xml b/app/src/main/res/layout/activity_download.xml new file mode 100644 index 000000000..3fda42dde --- /dev/null +++ b/app/src/main/res/layout/activity_download.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_generic_pager.xml b/app/src/main/res/layout/activity_generic_pager.xml new file mode 100644 index 000000000..bcdc3c1e6 --- /dev/null +++ b/app/src/main/res/layout/activity_generic_pager.xml @@ -0,0 +1,51 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_generic_recycler.xml b/app/src/main/res/layout/activity_generic_recycler.xml new file mode 100644 index 000000000..50b15d762 --- /dev/null +++ b/app/src/main/res/layout/activity_generic_recycler.xml @@ -0,0 +1,40 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_google.xml b/app/src/main/res/layout/activity_google.xml new file mode 100644 index 000000000..25126cee3 --- /dev/null +++ b/app/src/main/res/layout/activity_google.xml @@ -0,0 +1,28 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 000000000..40f5026ac --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,83 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_onboarding.xml b/app/src/main/res/layout/activity_onboarding.xml new file mode 100644 index 000000000..5772baad1 --- /dev/null +++ b/app/src/main/res/layout/activity_onboarding.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_screenshot.xml b/app/src/main/res/layout/activity_screenshot.xml new file mode 100644 index 000000000..b9a73edf6 --- /dev/null +++ b/app/src/main/res/layout/activity_screenshot.xml @@ -0,0 +1,31 @@ + + + + + + diff --git a/app/src/main/res/layout/activity_search_result.xml b/app/src/main/res/layout/activity_search_result.xml new file mode 100644 index 000000000..87c06a35b --- /dev/null +++ b/app/src/main/res/layout/activity_search_result.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_search_suggestion.xml b/app/src/main/res/layout/activity_search_suggestion.xml new file mode 100644 index 000000000..7f56e2126 --- /dev/null +++ b/app/src/main/res/layout/activity_search_suggestion.xml @@ -0,0 +1,40 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_setting.xml b/app/src/main/res/layout/activity_setting.xml new file mode 100644 index 000000000..4e8e84859 --- /dev/null +++ b/app/src/main/res/layout/activity_setting.xml @@ -0,0 +1,35 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_splash.xml b/app/src/main/res/layout/activity_splash.xml new file mode 100644 index 000000000..3752f37e6 --- /dev/null +++ b/app/src/main/res/layout/activity_splash.xml @@ -0,0 +1,121 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_apps_games.xml b/app/src/main/res/layout/fragment_apps_games.xml new file mode 100644 index 000000000..f33859480 --- /dev/null +++ b/app/src/main/res/layout/fragment_apps_games.xml @@ -0,0 +1,47 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_details_screenshots.xml b/app/src/main/res/layout/fragment_details_screenshots.xml new file mode 100644 index 000000000..465f0618f --- /dev/null +++ b/app/src/main/res/layout/fragment_details_screenshots.xml @@ -0,0 +1,35 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_for_you.xml b/app/src/main/res/layout/fragment_for_you.xml new file mode 100644 index 000000000..25ea52ad8 --- /dev/null +++ b/app/src/main/res/layout/fragment_for_you.xml @@ -0,0 +1,34 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_generic_recycler.xml b/app/src/main/res/layout/fragment_generic_recycler.xml new file mode 100644 index 000000000..995b0324c --- /dev/null +++ b/app/src/main/res/layout/fragment_generic_recycler.xml @@ -0,0 +1,34 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_onboarding_accent.xml b/app/src/main/res/layout/fragment_onboarding_accent.xml new file mode 100644 index 000000000..7687bd122 --- /dev/null +++ b/app/src/main/res/layout/fragment_onboarding_accent.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_onboarding_installer.xml b/app/src/main/res/layout/fragment_onboarding_installer.xml new file mode 100644 index 000000000..2ab7c8eb4 --- /dev/null +++ b/app/src/main/res/layout/fragment_onboarding_installer.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_onboarding_theme.xml b/app/src/main/res/layout/fragment_onboarding_theme.xml new file mode 100644 index 000000000..e9cc530cc --- /dev/null +++ b/app/src/main/res/layout/fragment_onboarding_theme.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_onboarding_welcome.xml b/app/src/main/res/layout/fragment_onboarding_welcome.xml new file mode 100644 index 000000000..db1cb2e25 --- /dev/null +++ b/app/src/main/res/layout/fragment_onboarding_welcome.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_top_chart.xml b/app/src/main/res/layout/fragment_top_chart.xml new file mode 100644 index 000000000..39f5aa492 --- /dev/null +++ b/app/src/main/res/layout/fragment_top_chart.xml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_top_container.xml b/app/src/main/res/layout/fragment_top_container.xml new file mode 100644 index 000000000..2a10291cb --- /dev/null +++ b/app/src/main/res/layout/fragment_top_container.xml @@ -0,0 +1,35 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_updates.xml b/app/src/main/res/layout/fragment_updates.xml new file mode 100644 index 000000000..720464a79 --- /dev/null +++ b/app/src/main/res/layout/fragment_updates.xml @@ -0,0 +1,43 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_preference.xml b/app/src/main/res/layout/item_preference.xml new file mode 100644 index 000000000..83267fa75 --- /dev/null +++ b/app/src/main/res/layout/item_preference.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_details_app.xml b/app/src/main/res/layout/layout_details_app.xml new file mode 100644 index 000000000..b502b2ee7 --- /dev/null +++ b/app/src/main/res/layout/layout_details_app.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_details_description.xml b/app/src/main/res/layout/layout_details_description.xml new file mode 100644 index 000000000..0e7d783ba --- /dev/null +++ b/app/src/main/res/layout/layout_details_description.xml @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_details_dev.xml b/app/src/main/res/layout/layout_details_dev.xml new file mode 100644 index 000000000..11dedc1aa --- /dev/null +++ b/app/src/main/res/layout/layout_details_dev.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_details_info.xml b/app/src/main/res/layout/layout_details_info.xml new file mode 100644 index 000000000..52a5cbf4c --- /dev/null +++ b/app/src/main/res/layout/layout_details_info.xml @@ -0,0 +1,158 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_details_install.xml b/app/src/main/res/layout/layout_details_install.xml new file mode 100644 index 000000000..19c52600d --- /dev/null +++ b/app/src/main/res/layout/layout_details_install.xml @@ -0,0 +1,159 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_details_privacy.xml b/app/src/main/res/layout/layout_details_privacy.xml new file mode 100644 index 000000000..97179775a --- /dev/null +++ b/app/src/main/res/layout/layout_details_privacy.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_details_review.xml b/app/src/main/res/layout/layout_details_review.xml new file mode 100644 index 000000000..38913d1f0 --- /dev/null +++ b/app/src/main/res/layout/layout_details_review.xml @@ -0,0 +1,150 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_nav_header.xml b/app/src/main/res/layout/layout_nav_header.xml new file mode 100644 index 000000000..9e1ea3ba1 --- /dev/null +++ b/app/src/main/res/layout/layout_nav_header.xml @@ -0,0 +1,54 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/model_carousel_group.xml b/app/src/main/res/layout/model_carousel_group.xml new file mode 100644 index 000000000..45dc62e1a --- /dev/null +++ b/app/src/main/res/layout/model_carousel_group.xml @@ -0,0 +1,35 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/model_editorchoice_group.xml b/app/src/main/res/layout/model_editorchoice_group.xml new file mode 100644 index 000000000..8f258ad49 --- /dev/null +++ b/app/src/main/res/layout/model_editorchoice_group.xml @@ -0,0 +1,34 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/multi_recycler.xml b/app/src/main/res/layout/multi_recycler.xml new file mode 100644 index 000000000..d76c87d1d --- /dev/null +++ b/app/src/main/res/layout/multi_recycler.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/settings_activity.xml b/app/src/main/res/layout/settings_activity.xml new file mode 100644 index 000000000..15898ab7e --- /dev/null +++ b/app/src/main/res/layout/settings_activity.xml @@ -0,0 +1,32 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/sheet_app_menu.xml b/app/src/main/res/layout/sheet_app_menu.xml new file mode 100644 index 000000000..516057fe3 --- /dev/null +++ b/app/src/main/res/layout/sheet_app_menu.xml @@ -0,0 +1,35 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/sheet_app_peek.xml b/app/src/main/res/layout/sheet_app_peek.xml new file mode 100644 index 000000000..7055a8d64 --- /dev/null +++ b/app/src/main/res/layout/sheet_app_peek.xml @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/sheet_base.xml b/app/src/main/res/layout/sheet_base.xml new file mode 100644 index 000000000..2b86208ca --- /dev/null +++ b/app/src/main/res/layout/sheet_base.xml @@ -0,0 +1,31 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/sheet_download_menu.xml b/app/src/main/res/layout/sheet_download_menu.xml new file mode 100644 index 000000000..b16cfb016 --- /dev/null +++ b/app/src/main/res/layout/sheet_download_menu.xml @@ -0,0 +1,34 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/sheet_network.xml b/app/src/main/res/layout/sheet_network.xml new file mode 100644 index 000000000..146ae90e0 --- /dev/null +++ b/app/src/main/res/layout/sheet_network.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_accent.xml b/app/src/main/res/layout/view_accent.xml new file mode 100644 index 000000000..40813724e --- /dev/null +++ b/app/src/main/res/layout/view_accent.xml @@ -0,0 +1,40 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_action_button.xml b/app/src/main/res/layout/view_action_button.xml new file mode 100644 index 000000000..b4369a3c8 --- /dev/null +++ b/app/src/main/res/layout/view_action_button.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/layout/view_action_header.xml b/app/src/main/res/layout/view_action_header.xml new file mode 100644 index 000000000..b5cabb350 --- /dev/null +++ b/app/src/main/res/layout/view_action_header.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_app.xml b/app/src/main/res/layout/view_app.xml new file mode 100644 index 000000000..51279a73d --- /dev/null +++ b/app/src/main/res/layout/view_app.xml @@ -0,0 +1,59 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_app_card_shimmer.xml b/app/src/main/res/layout/view_app_card_shimmer.xml new file mode 100644 index 000000000..54af8ed3b --- /dev/null +++ b/app/src/main/res/layout/view_app_card_shimmer.xml @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_app_dependent.xml b/app/src/main/res/layout/view_app_dependent.xml new file mode 100644 index 000000000..413d0069d --- /dev/null +++ b/app/src/main/res/layout/view_app_dependent.xml @@ -0,0 +1,47 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_app_list.xml b/app/src/main/res/layout/view_app_list.xml new file mode 100644 index 000000000..006e656c6 --- /dev/null +++ b/app/src/main/res/layout/view_app_list.xml @@ -0,0 +1,68 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_app_list_shimmer.xml b/app/src/main/res/layout/view_app_list_shimmer.xml new file mode 100644 index 000000000..8ff188064 --- /dev/null +++ b/app/src/main/res/layout/view_app_list_shimmer.xml @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_app_progress.xml b/app/src/main/res/layout/view_app_progress.xml new file mode 100644 index 000000000..52f4f5281 --- /dev/null +++ b/app/src/main/res/layout/view_app_progress.xml @@ -0,0 +1,30 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_app_shimmer.xml b/app/src/main/res/layout/view_app_shimmer.xml new file mode 100644 index 000000000..dcafeacbe --- /dev/null +++ b/app/src/main/res/layout/view_app_shimmer.xml @@ -0,0 +1,66 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_app_update.xml b/app/src/main/res/layout/view_app_update.xml new file mode 100644 index 000000000..0bba47a7c --- /dev/null +++ b/app/src/main/res/layout/view_app_update.xml @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_badge.xml b/app/src/main/res/layout/view_badge.xml new file mode 100644 index 000000000..a3497b78b --- /dev/null +++ b/app/src/main/res/layout/view_badge.xml @@ -0,0 +1,45 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_black.xml b/app/src/main/res/layout/view_black.xml new file mode 100644 index 000000000..0c4c46734 --- /dev/null +++ b/app/src/main/res/layout/view_black.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_category.xml b/app/src/main/res/layout/view_category.xml new file mode 100644 index 000000000..9a6295f27 --- /dev/null +++ b/app/src/main/res/layout/view_category.xml @@ -0,0 +1,47 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_dash.xml b/app/src/main/res/layout/view_dash.xml new file mode 100644 index 000000000..0d008c09a --- /dev/null +++ b/app/src/main/res/layout/view_dash.xml @@ -0,0 +1,51 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_dev_info.xml b/app/src/main/res/layout/view_dev_info.xml new file mode 100644 index 000000000..88e0401b1 --- /dev/null +++ b/app/src/main/res/layout/view_dev_info.xml @@ -0,0 +1,60 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_device.xml b/app/src/main/res/layout/view_device.xml new file mode 100644 index 000000000..d709498ce --- /dev/null +++ b/app/src/main/res/layout/view_device.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_download.xml b/app/src/main/res/layout/view_download.xml new file mode 100644 index 000000000..0264b6fa6 --- /dev/null +++ b/app/src/main/res/layout/view_download.xml @@ -0,0 +1,140 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_editor_head.xml b/app/src/main/res/layout/view_editor_head.xml new file mode 100644 index 000000000..442c31f3c --- /dev/null +++ b/app/src/main/res/layout/view_editor_head.xml @@ -0,0 +1,31 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_editor_image.xml b/app/src/main/res/layout/view_editor_image.xml new file mode 100644 index 000000000..ba9d86d2c --- /dev/null +++ b/app/src/main/res/layout/view_editor_image.xml @@ -0,0 +1,28 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_exodus.xml b/app/src/main/res/layout/view_exodus.xml new file mode 100644 index 000000000..8e8a7c795 --- /dev/null +++ b/app/src/main/res/layout/view_exodus.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/view_file.xml b/app/src/main/res/layout/view_file.xml new file mode 100644 index 000000000..a5cb2a2a5 --- /dev/null +++ b/app/src/main/res/layout/view_file.xml @@ -0,0 +1,47 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_header.xml b/app/src/main/res/layout/view_header.xml new file mode 100644 index 000000000..437a5e720 --- /dev/null +++ b/app/src/main/res/layout/view_header.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_header_shimmer.xml b/app/src/main/res/layout/view_header_shimmer.xml new file mode 100644 index 000000000..98a039d49 --- /dev/null +++ b/app/src/main/res/layout/view_header_shimmer.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_header_update.xml b/app/src/main/res/layout/view_header_update.xml new file mode 100644 index 000000000..f94beb8bc --- /dev/null +++ b/app/src/main/res/layout/view_header_update.xml @@ -0,0 +1,63 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_installer.xml b/app/src/main/res/layout/view_installer.xml new file mode 100644 index 000000000..53ad1e8f0 --- /dev/null +++ b/app/src/main/res/layout/view_installer.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_link.xml b/app/src/main/res/layout/view_link.xml new file mode 100644 index 000000000..dca873f40 --- /dev/null +++ b/app/src/main/res/layout/view_link.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_locale.xml b/app/src/main/res/layout/view_locale.xml new file mode 100644 index 000000000..4f30ea990 --- /dev/null +++ b/app/src/main/res/layout/view_locale.xml @@ -0,0 +1,51 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_more_badge.xml b/app/src/main/res/layout/view_more_badge.xml new file mode 100644 index 000000000..5fb17d4da --- /dev/null +++ b/app/src/main/res/layout/view_more_badge.xml @@ -0,0 +1,57 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_no_app.xml b/app/src/main/res/layout/view_no_app.xml new file mode 100644 index 000000000..7cc47a1ed --- /dev/null +++ b/app/src/main/res/layout/view_no_app.xml @@ -0,0 +1,42 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_no_app_alt.xml b/app/src/main/res/layout/view_no_app_alt.xml new file mode 100644 index 000000000..d0d347892 --- /dev/null +++ b/app/src/main/res/layout/view_no_app_alt.xml @@ -0,0 +1,33 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_rating.xml b/app/src/main/res/layout/view_rating.xml new file mode 100644 index 000000000..68071cc5c --- /dev/null +++ b/app/src/main/res/layout/view_rating.xml @@ -0,0 +1,45 @@ + + + + + + + + diff --git a/app/src/main/res/layout/view_review.xml b/app/src/main/res/layout/view_review.xml new file mode 100644 index 000000000..8d4a4dd5c --- /dev/null +++ b/app/src/main/res/layout/view_review.xml @@ -0,0 +1,84 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_screenshot.xml b/app/src/main/res/layout/view_screenshot.xml new file mode 100644 index 000000000..5a6f384f8 --- /dev/null +++ b/app/src/main/res/layout/view_screenshot.xml @@ -0,0 +1,29 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_screenshot_large.xml b/app/src/main/res/layout/view_screenshot_large.xml new file mode 100644 index 000000000..0012b2816 --- /dev/null +++ b/app/src/main/res/layout/view_screenshot_large.xml @@ -0,0 +1,30 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_search_suggestion.xml b/app/src/main/res/layout/view_search_suggestion.xml new file mode 100644 index 000000000..4a3cf2b26 --- /dev/null +++ b/app/src/main/res/layout/view_search_suggestion.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_state_button.xml b/app/src/main/res/layout/view_state_button.xml new file mode 100644 index 000000000..225249260 --- /dev/null +++ b/app/src/main/res/layout/view_state_button.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/layout/view_theme.xml b/app/src/main/res/layout/view_theme.xml new file mode 100644 index 000000000..84baf03a6 --- /dev/null +++ b/app/src/main/res/layout/view_theme.xml @@ -0,0 +1,50 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_toolbar_action.xml b/app/src/main/res/layout/view_toolbar_action.xml new file mode 100644 index 000000000..d02942fd0 --- /dev/null +++ b/app/src/main/res/layout/view_toolbar_action.xml @@ -0,0 +1,51 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_toolbar_main.xml b/app/src/main/res/layout/view_toolbar_main.xml new file mode 100644 index 000000000..8bf17ed61 --- /dev/null +++ b/app/src/main/res/layout/view_toolbar_main.xml @@ -0,0 +1,64 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_toolbar_native.xml b/app/src/main/res/layout/view_toolbar_native.xml new file mode 100644 index 000000000..6ae162a55 --- /dev/null +++ b/app/src/main/res/layout/view_toolbar_native.xml @@ -0,0 +1,33 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_toolbar_search.xml b/app/src/main/res/layout/view_toolbar_search.xml new file mode 100644 index 000000000..a6b7a0df2 --- /dev/null +++ b/app/src/main/res/layout/view_toolbar_search.xml @@ -0,0 +1,63 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/view_two_column.xml b/app/src/main/res/layout/view_two_column.xml new file mode 100644 index 000000000..83eea5d55 --- /dev/null +++ b/app/src/main/res/layout/view_two_column.xml @@ -0,0 +1,49 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_app.xml b/app/src/main/res/menu/menu_app.xml new file mode 100644 index 000000000..eb64de0b8 --- /dev/null +++ b/app/src/main/res/menu/menu_app.xml @@ -0,0 +1,37 @@ + + +

+ + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_bottom_nav.xml b/app/src/main/res/menu/menu_bottom_nav.xml new file mode 100644 index 000000000..796c0bc45 --- /dev/null +++ b/app/src/main/res/menu/menu_bottom_nav.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_details.xml b/app/src/main/res/menu/menu_details.xml new file mode 100644 index 000000000..ed4b61de8 --- /dev/null +++ b/app/src/main/res/menu/menu_details.xml @@ -0,0 +1,34 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_download_main.xml b/app/src/main/res/menu/menu_download_main.xml new file mode 100644 index 000000000..00682a59d --- /dev/null +++ b/app/src/main/res/menu/menu_download_main.xml @@ -0,0 +1,36 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_download_single.xml b/app/src/main/res/menu/menu_download_single.xml new file mode 100644 index 000000000..87b4484b4 --- /dev/null +++ b/app/src/main/res/menu/menu_download_single.xml @@ -0,0 +1,36 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_drawer.xml b/app/src/main/res/menu/menu_drawer.xml new file mode 100644 index 000000000..864c17dd4 --- /dev/null +++ b/app/src/main/res/menu/menu_drawer.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_splash.xml b/app/src/main/res/menu/menu_splash.xml new file mode 100644 index 000000000..76a7583d6 --- /dev/null +++ b/app/src/main/res/menu/menu_splash.xml @@ -0,0 +1,30 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 000000000..8eb609edd --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,23 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 000000000..8eb609edd --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,23 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..6852989938e8fcfd1f4b134bf48e8f7f5b655b30 GIT binary patch literal 4093 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..6852989938e8fcfd1f4b134bf48e8f7f5b655b30 GIT binary patch literal 4093 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..5769c31fc0c36b9d5755267a7a63c01a6eea48a2 GIT binary patch literal 2546 zcmVkt$f_qCr6n zZuE?xx*jWVVFG?ZfO3R9e4Y|-xhUW6L&lOh!%l>{-g%V? zm}mm9K*+ObQg{{!xs2~Z=UC?=`rT+e<;unoer$1WzfR#INyuXw9sq3-a?OlyLdQCj zA-af8drsIqJVg27oR~rmelO%&L{7jNAv?m_K`cUWyIL1V$&>2v*S(_<= zSlkYcMgyr-igN`wks;6yxH{QF$p}tFTZ+q2vNIy?2@rBk4NkSW-v-l@b@*S|ZK&01 ztBA#7;)(k>@?{&A#hV7aPtZl>$EX?u1J8_m$P0ExNpad}r`7L=0TYtrdXPk9{~j?^ zDwTEh>u=tLsPY!%ayeS=w&TMr*?>cddV%BuV#CC|2fWZ*nv&!61wO|@d z%5A`{;6)`kRdd-9I+q&iv9Y8GYd^_DX?i|Ht4~7w$#JyqK7l(s@=>+s1S(dYw#vB` zg~(25_R$j-3k{?2B{u|)l4|z=KSsUJTiqwg1YzbKykA;`bp$k(wM2H67o$xgfl{f& z-TQ6WR&KkWRaS%(>%V|v!%=i?I_6fsTm21IaOA8f%wF0IGh)J;bLj5u++O%6$8?vu zBajkrCb6u*&TSIx&bxvGmn$8TQ(19f9l*+3nY7{QX&{*7@pBgj{30I|A!6JBO7(brmIGbW0#HrmJJYoCA{_ z1eOL@PZe-Yo$d&v@^xqyEBXaOr|p75oDYNIQvc@Zboe?~M(V!V0|B)%=&i4(IS7Qj zQ}U800(1v0JS#;{Pmj+)G&c^xTsjg=`O`2+PWU`eq0r*vj}#u8J4)c~H*%&s2n5ah z=4WIR>u_I%%-FkIPHKF&Z$P9Eh2(Bz`2nd!Htj{@Vt?kaq4)7X98Usn0A~26TB5n^uJ&z zu7j~Ln|NU~m_k1o8#Y1Tu>y@j}thVUUU3&c9ltz{(Vz`==JP zm59?LIIwU9#oVWrnD^&{uL$CdU7nh`)frmSSt?x`SE@9)aJCU&9j`}Tem#y|k>kJO z9;*~o=R$XO4453P>#^m!4)50J@WE9Z31`_Z zs>bcJvmhypB2C}JxzyRILTaMgT@y);m9Wr5)1vwLG<-(%Iab<_XV3rWcRx*tQZ{-z zQf94k-&QEJgos9ub~n=5*$RzX=C*yH=Uz`o%Gj`$rU}pdW|GT;J+walKZ7 z>vf%jl51Bxap>PNq$L_X#(q>tLk+?9g01;H_N1q$a-;t=b9-=7jMhNi<}Q@dNJ6hl z?H;tOT&8ndd6GFMGp#%cQJsdFuWbmW*!cYFk-j`yd?x7aGn;75rS2P^hq^AyrX?x1 z8-a84Hd9PqA1(Hlrcmml=R<-j57WMcC}M_}gcUqv+q8;vDHa>nVn>Q>DA}-%NdV8Hj=di%3E~+y!t_5!%&L+BlMQzw@J%Ya> zur-*5)P#V5AHOnp_Wm{Df4x=`A1PDO6}0W(jNLmAh^5bi;DkuIDtvKm3DrS$QJp^3 z`JwOzYh(z2$=ZV3lrX${o>@{x2Vu-`WwB+j(LuqGjiDf z?BU8Iv{HnIem{!M6T5fl{~vzH%2Vubw4RTmCn;NX8GM}oA3(4`;JN8BlK=n!07*qo IM6N<$f;L?7!~g&Q literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..5769c31fc0c36b9d5755267a7a63c01a6eea48a2 GIT binary patch literal 2546 zcmVkt$f_qCr6n zZuE?xx*jWVVFG?ZfO3R9e4Y|-xhUW6L&lOh!%l>{-g%V? zm}mm9K*+ObQg{{!xs2~Z=UC?=`rT+e<;unoer$1WzfR#INyuXw9sq3-a?OlyLdQCj zA-af8drsIqJVg27oR~rmelO%&L{7jNAv?m_K`cUWyIL1V$&>2v*S(_<= zSlkYcMgyr-igN`wks;6yxH{QF$p}tFTZ+q2vNIy?2@rBk4NkSW-v-l@b@*S|ZK&01 ztBA#7;)(k>@?{&A#hV7aPtZl>$EX?u1J8_m$P0ExNpad}r`7L=0TYtrdXPk9{~j?^ zDwTEh>u=tLsPY!%ayeS=w&TMr*?>cddV%BuV#CC|2fWZ*nv&!61wO|@d z%5A`{;6)`kRdd-9I+q&iv9Y8GYd^_DX?i|Ht4~7w$#JyqK7l(s@=>+s1S(dYw#vB` zg~(25_R$j-3k{?2B{u|)l4|z=KSsUJTiqwg1YzbKykA;`bp$k(wM2H67o$xgfl{f& z-TQ6WR&KkWRaS%(>%V|v!%=i?I_6fsTm21IaOA8f%wF0IGh)J;bLj5u++O%6$8?vu zBajkrCb6u*&TSIx&bxvGmn$8TQ(19f9l*+3nY7{QX&{*7@pBgj{30I|A!6JBO7(brmIGbW0#HrmJJYoCA{_ z1eOL@PZe-Yo$d&v@^xqyEBXaOr|p75oDYNIQvc@Zboe?~M(V!V0|B)%=&i4(IS7Qj zQ}U800(1v0JS#;{Pmj+)G&c^xTsjg=`O`2+PWU`eq0r*vj}#u8J4)c~H*%&s2n5ah z=4WIR>u_I%%-FkIPHKF&Z$P9Eh2(Bz`2nd!Htj{@Vt?kaq4)7X98Usn0A~26TB5n^uJ&z zu7j~Ln|NU~m_k1o8#Y1Tu>y@j}thVUUU3&c9ltz{(Vz`==JP zm59?LIIwU9#oVWrnD^&{uL$CdU7nh`)frmSSt?x`SE@9)aJCU&9j`}Tem#y|k>kJO z9;*~o=R$XO4453P>#^m!4)50J@WE9Z31`_Z zs>bcJvmhypB2C}JxzyRILTaMgT@y);m9Wr5)1vwLG<-(%Iab<_XV3rWcRx*tQZ{-z zQf94k-&QEJgos9ub~n=5*$RzX=C*yH=Uz`o%Gj`$rU}pdW|GT;J+walKZ7 z>vf%jl51Bxap>PNq$L_X#(q>tLk+?9g01;H_N1q$a-;t=b9-=7jMhNi<}Q@dNJ6hl z?H;tOT&8ndd6GFMGp#%cQJsdFuWbmW*!cYFk-j`yd?x7aGn;75rS2P^hq^AyrX?x1 z8-a84Hd9PqA1(Hlrcmml=R<-j57WMcC}M_}gcUqv+q8;vDHa>nVn>Q>DA}-%NdV8Hj=di%3E~+y!t_5!%&L+BlMQzw@J%Ya> zur-*5)P#V5AHOnp_Wm{Df4x=`A1PDO6}0W(jNLmAh^5bi;DkuIDtvKm3DrS$QJp^3 z`JwOzYh(z2$=ZV3lrX${o>@{x2Vu-`WwB+j(LuqGjiDf z?BU8Iv{HnIem{!M6T5fl{~vzH%2Vubw4RTmCn;NX8GM}oA3(4`;JN8BlK=n!07*qo IM6N<$f;L?7!~g&Q literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..fff4e420e49f89cb0685bc44672b53f1db75b7bb GIT binary patch literal 5806 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..fff4e420e49f89cb0685bc44672b53f1db75b7bb GIT binary patch literal 5806 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..65ace096d96cdbdb33ebe78faf48d18d07e8f465 GIT binary patch literal 9047 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 JPDHLkV1m+dsF(l% literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..65ace096d96cdbdb33ebe78faf48d18d07e8f465 GIT binary patch literal 9047 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 JPDHLkV1m+dsF(l% literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000000000000000000000000000000000..6b2ab3c26be8e38b5374463186124a6b305b4224 GIT binary patch literal 12890 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 Eg2b=eKmY&$ literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000000000000000000000000000000000..6b2ab3c26be8e38b5374463186124a6b305b4224 GIT binary patch literal 12890 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 Eg2b=eKmY&$ literal 0 HcmV?d00001 diff --git a/app/src/main/res/navigation/mobile_navigation.xml b/app/src/main/res/navigation/mobile_navigation.xml new file mode 100644 index 000000000..ea01bdbc6 --- /dev/null +++ b/app/src/main/res/navigation/mobile_navigation.xml @@ -0,0 +1,43 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/navigation/navigation_onboarding.xml b/app/src/main/res/navigation/navigation_onboarding.xml new file mode 100644 index 000000000..7a2eb9691 --- /dev/null +++ b/app/src/main/res/navigation/navigation_onboarding.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-v21/styles_text.xml b/app/src/main/res/values-v21/styles_text.xml new file mode 100644 index 000000000..bf52210e0 --- /dev/null +++ b/app/src/main/res/values-v21/styles_text.xml @@ -0,0 +1,133 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-v26/styles_text.xml b/app/src/main/res/values-v26/styles_text.xml new file mode 100644 index 000000000..91662e959 --- /dev/null +++ b/app/src/main/res/values-v26/styles_text.xml @@ -0,0 +1,70 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml new file mode 100644 index 000000000..4342215f2 --- /dev/null +++ b/app/src/main/res/values/arrays.xml @@ -0,0 +1,160 @@ + + + + + 12sp + 13sp + 14sp + 15sp + 16sp + 17sp + 18sp + 19sp + 20sp + + + 24sp + 25sp + 26sp + 27sp + 28sp + 29sp + 30sp + 31sp + 32sp + 33sp + 34sp + 35sp + 36sp + 37sp + 38sp + 39sp + 40sp + 41sp + 42sp + 43sp + 44sp + 45sp + 46sp + 47sp + 48sp + + + + @string/pref_ui_theme_system + @string/pref_ui_theme_light + @string/pref_ui_theme_dark + @string/pref_ui_theme_black + @string/pref_ui_theme_dark_x + @string/pref_ui_theme_darkord + + + + 0 + 1 + 2 + 3 + 4 + 5 + + + + @string/ui_accent_1 + @string/ui_accent_2 + @string/ui_accent_3 + @string/ui_accent_4 + @string/ui_accent_5 + @string/ui_accent_6 + @string/ui_accent_7 + @string/ui_accent_8 + @string/ui_accent_9 + @string/ui_accent_10 + @string/ui_accent_11 + @string/ui_accent_12 + + + + 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 + 11 + 12 + + + + @string/pref_install_mode_session + @string/pref_install_mode_native + @string/pref_install_mode_root + @string/pref_install_mode_services + + + + 0 + 1 + 2 + 3 + + + + "bc1qu7cy9fepjj309y4r2x3rymve7mw4ff39c8cpe0" + "qpqus3qdlz8guf476vwz0fjl8s34fseukcmrl6eknl" + "0x6977446933EC8b5964D921f7377950992337B1C6" + "whyorean@dbs" + "https://paypal.me/AuroraDev" + "https://liberapay.com/on/gitlab/whyorean/ " + "https://gitlab.com/AuroraOSS/AuroraStore" + "https://forum.xda-developers.com/android/apps-games/galaxy-playstore-alternative-t3739733" + "https://t.me/AuroraSupport" + "https://f-droid.org/en/packages/com.aurora.store/" + + + + @string/about_bitcoin_btc + @string/about_bitcoin_bch + @string/about_bitcoin_eth + @string/about_bhim + @string/about_paypal + @string/about_libera + @string/about_gitlab + @string/about_xda + @string/about_telegram + @string/about_fdroid + + + + @string/about_bitcoin_btc_summary + @string/about_bitcoin_bch_summary + @string/about_bitcoin_eth_summary + @string/about_bhim_summary + @string/about_paypal_summary + @string/about_libera_summary + @string/about_gitlab_summary + @string/about_xda_summary + @string/about_telegram_summary + @string/about_fdroid_summary + + diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml new file mode 100644 index 000000000..1fc263af5 --- /dev/null +++ b/app/src/main/res/values/attrs.xml @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 000000000..08c47e678 --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,88 @@ + + + + + @color/colorAccent + #9D9D9D + #ABABAB + #D6D6D6 + + + #202124 + #2d2e31 + #202124 + #373a40 + #373a40 + + + #1C2733 + #222E3C + #1C2733 + #425a75 + #425a75 + + + #23272a + #2c2f33 + #23272a + #99aab5 + #99aab5 + + + #000 + #646464 + #343434 + + + #6C63FF + #FFFFFF + #00000000 + + + @color/colorAccent + @color/colorScrim + #44F2F2F2 + #F2F2F2 + + + #22000000 + #99000000 + #446C63FF + #30000000 + + #4768FD + #E4002B + #ff4c4c + #FF9933 + #49A942 + + #6C63FF + #FF00FF + #E4002B + #F9A826 + #49A942 + #6633cc + #52565e + #ee70a6 + #b5c327 + #f38654 + #61A7E0 + #7289da + \ No newline at end of file diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml new file mode 100644 index 000000000..1d2d42161 --- /dev/null +++ b/app/src/main/res/values/dimens.xml @@ -0,0 +1,62 @@ + + + + 2dp + 4dp + 8dp + 10dp + 12dp + 16dp + 32dp + + 2dp + 4dp + 8dp + 10dp + 12dp + 16dp + 32dp + + 24dp + 36dp + 48dp + 56dp + 64dp + 72dp + 80dp + 108dp + + + 240dp + 135dp + + 64dp + + 128dp + 3dp + + 36dp + 52dp + 56dp + 200dp + 52dp + 64dp + 148dp + \ No newline at end of file diff --git a/app/src/main/res/values/font_certs.xml b/app/src/main/res/values/font_certs.xml new file mode 100644 index 000000000..4a9cfec0b --- /dev/null +++ b/app/src/main/res/values/font_certs.xml @@ -0,0 +1,35 @@ + + + + + @array/com_google_android_gms_fonts_certs_dev + @array/com_google_android_gms_fonts_certs_prod + + + + MIIEqDCCA5CgAwIBAgIJANWFuGx90071MA0GCSqGSIb3DQEBBAUAMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTAeFw0wODA0MTUyMzM2NTZaFw0zNTA5MDEyMzM2NTZaMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbTCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBANbOLggKv+IxTdGNs8/TGFy0PTP6DHThvbbR24kT9ixcOd9W+EaBPWW+wPPKQmsHxajtWjmQwWfna8mZuSeJS48LIgAZlKkpFeVyxW0qMBujb8X8ETrWy550NaFtI6t9+u7hZeTfHwqNvacKhp1RbE6dBRGWynwMVX8XW8N1+UjFaq6GCJukT4qmpN2afb8sCjUigq0GuMwYXrFVee74bQgLHWGJwPmvmLHC69EH6kWr22ijx4OKXlSIx2xT1AsSHee70w5iDBiK4aph27yH3TxkXy9V89TDdexAcKk/cVHYNnDBapcavl7y0RiQ4biu8ymM8Ga/nmzhRKya6G0cGw8CAQOjgfwwgfkwHQYDVR0OBBYEFI0cxb6VTEM8YYY6FbBMvAPyT+CyMIHJBgNVHSMEgcEwgb6AFI0cxb6VTEM8YYY6FbBMvAPyT+CyoYGapIGXMIGUMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEQMA4GA1UEChMHQW5kcm9pZDEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDEiMCAGCSqGSIb3DQEJARYTYW5kcm9pZEBhbmRyb2lkLmNvbYIJANWFuGx90071MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEEBQADggEBABnTDPEF+3iSP0wNfdIjIz1AlnrPzgAIHVvXxunW7SBrDhEglQZBbKJEk5kT0mtKoOD1JMrSu1xuTKEBahWRbqHsXclaXjoBADb0kkjVEJu/Lh5hgYZnOjvlba8Ld7HCKePCVePoTJBdI4fvugnL8TsgK05aIskyY0hKI9L8KfqfGTl1lzOv2KoWD0KWwtAWPoGChZxmQ+nBli+gwYMzM1vAkP+aayLe0a1EQimlOalO762r0GXO0ks+UeXde2Z4e+8S/pf7pITEI/tP+MxJTALw9QUWEv9lKTk+jkbqxbsh8nfBUapfKqYn0eidpwq2AzVp3juYl7//fKnaPhJD9gs= + + + + + MIIEQzCCAyugAwIBAgIJAMLgh0ZkSjCNMA0GCSqGSIb3DQEBBAUAMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDAeFw0wODA4MjEyMzEzMzRaFw0zNjAxMDcyMzEzMzRaMHQxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3MRQwEgYDVQQKEwtHb29nbGUgSW5jLjEQMA4GA1UECxMHQW5kcm9pZDEQMA4GA1UEAxMHQW5kcm9pZDCCASAwDQYJKoZIhvcNAQEBBQADggENADCCAQgCggEBAKtWLgDYO6IIrgqWbxJOKdoR8qtW0I9Y4sypEwPpt1TTcvZApxsdyxMJZ2JORland2qSGT2y5b+3JKkedxiLDmpHpDsz2WCbdxgxRczfey5YZnTJ4VZbH0xqWVW/8lGmPav5xVwnIiJS6HXk+BVKZF+JcWjAsb/GEuq/eFdpuzSqeYTcfi6idkyugwfYwXFU1+5fZKUaRKYCwkkFQVfcAs1fXA5V+++FGfvjJ/CxURaSxaBvGdGDhfXE28LWuT9ozCl5xw4Yq5OGazvV24mZVSoOO0yZ31j7kYvtwYK6NeADwbSxDdJEqO4k//0zOHKrUiGYXtqw/A0LFFtqoZKFjnkCAQOjgdkwgdYwHQYDVR0OBBYEFMd9jMIhF1Ylmn/Tgt9r45jk14alMIGmBgNVHSMEgZ4wgZuAFMd9jMIhF1Ylmn/Tgt9r45jk14aloXikdjB0MQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEUMBIGA1UEChMLR29vZ2xlIEluYy4xEDAOBgNVBAsTB0FuZHJvaWQxEDAOBgNVBAMTB0FuZHJvaWSCCQDC4IdGZEowjTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBAUAA4IBAQBt0lLO74UwLDYKqs6Tm8/yzKkEu116FmH4rkaymUIE0P9KaMftGlMexFlaYjzmB2OxZyl6euNXEsQH8gjwyxCUKRJNexBiGcCEyj6z+a1fuHHvkiaai+KL8W1EyNmgjmyy8AW7P+LLlkR+ho5zEHatRbM/YAnqGcFh5iZBqpknHf1SKMXFh4dd239FJ1jWYfbMDMy3NS5CTMQ2XFI1MvcyUTdZPErjQfTbQe3aDQsQcafEQPD+nqActifKZ0Np0IS9L9kR/wbNvyz6ENwPiTrjV2KRkEjH78ZMcUQXg0L3BYHJ3lc69Vs5Ddf9uUGGMYldX3WfMBEmh/9iFBDAaTCK + + + diff --git a/app/src/main/res/values/ic_launcher_background.xml b/app/src/main/res/values/ic_launcher_background.xml new file mode 100644 index 000000000..461765417 --- /dev/null +++ b/app/src/main/res/values/ic_launcher_background.xml @@ -0,0 +1,22 @@ + + + + #6C63FF + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 000000000..d5bf5dd94 --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,291 @@ + + + + "BHIM - UPI" + "Donate via UPI" + "Bitcoin Cash" + "Donate via Bitcoin Cash (BCH)" + "Bitcoin" + "Donate via Bitcoin (BTC)" + "Ethereum" + "Donate via Ethereum (ETH)" + "F-Droid" + "Get the Aurora Store via F-Droid." + "GitLab" + "Get the Aurora Store source code from Aurora OSS on GitLab." + "Liberapay" + "Become a patron on Liberapay" + "Paypal" + "Donate via PayPal" + "Telegram" + "Join the Aurora Support group for discussions or suggestions." + "Dev Thread" + "See the Aurora Store XDA thread for discussions or suggestions." + + "Anonymous" + "Google" + "You are logged out" + "Login using" + "Logout from Aurora" + + "Accounts" + "All reviews" + "Ask" + "Back" + "Blacklist" + "Add to Blacklist" + "Buy" + "Cancel" + "Clear" + "Clear all" + "Clear data" + "Copy Link" + "Disable" + "Discard" + "Discover" + "Done" + "Download" + "Enable" + "Export" + "Favourite apps" + "Filters" + "All" + "Apply" + "Apps with ads" + "Downloads" + "GSF dependent" + "Misc" + "Paid" + "Ratings" + "Finish" + "Save App bundle" + "Home" + "Ignore" + Ignore version + "Import" + "Install" + "Installations" + "Installed" + "Installing" + "Later" + "Logging in" + "Logout" + "Manual download" + "More" + "Next" + "Open" + "Pause" + "Post" + "Previous" + "Purchase" + "Resend" + "Restart" + "Resume" + "Retry" + "Save" + "Search" + "Settings" + "Share" + "Sign in" + "Sign up" + "Skip" + "Spoof" + "Submit" + "Uninstall" + "App Info" + "Update" + "Update all" + "Updates" + "Wait" + "Whitelist" + "Add to wishlist" + + "Changelog" + "Changelog not provided" + "Contains Ads" + "Dependencies" + "Description" + "Address" + "Developer contact" + "Email" + "Developer\'s reply" + "Website" + "Files" + "Free" + "Requires GSF" + "In-app purchase" + "More about app" + "No ads" + "No apps available" + "No Dependencies" + "No previews available" + "No reviews available" + "No updates available" + "No apps whitelisted" + "See what others think about this app" + "Paid" + "Privacy" + "Enter star rating first" + "Rate this app" + "Rating and reviews" + "Review submitted" + "Reviews" + "More reviews" + "Screenshots" + "What do you think about this app?" + + "Cancel all" + "Cancelled" + "Clear completed" + "Downloaded" + "Deleted" + "Calculating" + "Download failed" + "Force clear all" + "Getting metadata" + "No downloads" + "Pause" + "Pause all" + "Paused" + "Downloading" + "Queued" + "Resume" + "Resume all" + "Estimating" + + "No reports available" + "Contains no trackers" + "Powered by Exodus" + "Checking for trackers…" + "Exodus report" + "trackers(s) found in" + "Aurora Protect" + "Exodus report" + "View" + "View report" + + "All" + "Critical" + "Five" + "Four" + "One" + "Positive" + "Three" + "Two" + + "Root access available" + "No Root, grant root access or change the installer." + "Installation failed" + "Installation cancelled" + "Installation was blocked" + "Conflicting package exists" + "Incompatible app" + "Invalid or corrupted APK" + "Insufficient memory space" + "Successfully installed" + "Unknown" + "Waiting for user confirmation" + + "Disclaimer" + "License" + "Terms of service" + + "Quick notification" + "General notification" + "Click to install" + + "Select a suitable installer" + "Select what you like" + "Accent" + "Installer" + "Themes" + "Welcome" + "How you doing?" + + "Force clear installation sessions" + "Clear abandon or pending installation sessions" + "Downloads" + "This setting will be applied after you restart the app" + "Maximum number of active downloads" + "Active download" + "Removes F-Droid apps from app lists" + "Filter F-Droid apps" + "Removes all Google Apps from search results and category apps" + "Filter Google apps" + "Apps are installed instantly after download completes" + "Auto install APKs after download" + "APKs will be deleted by default" + "Delete APK post-install" + "Native installer" + "Root installer" + "Aurora Services" + "Session installer" + "Select mode of APK installation" + "Installation method" + "Select the target profile to install apps to. Only works with the root installation method" + "Installation profiles (Experimental)" + "Accent" + "Black" + "Dark" + "Dark X" + "Disskord" + "Light" + "Follow System" + "Theme" + "Customization" + + "Download Failed" + "App not purchased" + "Could not get files" + + "Make sure you re-login to apply the spoof" + + "Top free" + "Top grossing" + "Top paid" + "Trending" + + "About" + "Accounts" + "Apps" + "My apps & games" + "Apps in library" + "Apps on sale" + "Blacklist manager" + "Details" + "Device" + "Downloads" + "Games" + "Installation" + "Installed" + "Language" + "Library" + "Settings" + "Spoof manager" + "Updates" + + "Old installation sessions abandoned" + "Not available on anonymous accounts" + "Blacklisted" + "APK export failed" + "APK exported successfully" + "Whitelisted" + "Not available on anonymous accounts" + \ No newline at end of file diff --git a/app/src/main/res/values/strings_do_not_translate.xml b/app/src/main/res/values/strings_do_not_translate.xml new file mode 100644 index 000000000..e24a27e0b --- /dev/null +++ b/app/src/main/res/values/strings_do_not_translate.xml @@ -0,0 +1,43 @@ + + + + "Aurora Store" + + "%s • %s★ • %s" + %1$dh %2$dm %3$ds left + %1$dm %2$ds left + %1$ds left + %1$d B/s + %1$s KB/s + %1$s MB/s + + Blue + Fuchsia + Red + Golden + Green + Purple + Grey + Pink + Lime + Orange + Light blue + Disskord blue + \ No newline at end of file diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml new file mode 100644 index 000000000..3bd5252ec --- /dev/null +++ b/app/src/main/res/values/styles.xml @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/styles_accent.xml b/app/src/main/res/values/styles_accent.xml new file mode 100644 index 000000000..24dba4461 --- /dev/null +++ b/app/src/main/res/values/styles_accent.xml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/styles_text.xml b/app/src/main/res/values/styles_text.xml new file mode 100644 index 000000000..209ca0b62 --- /dev/null +++ b/app/src/main/res/values/styles_text.xml @@ -0,0 +1,140 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/styles_widget.xml b/app/src/main/res/values/styles_widget.xml new file mode 100644 index 000000000..6067c1ba8 --- /dev/null +++ b/app/src/main/res/values/styles_widget.xml @@ -0,0 +1,75 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/xml/paths.xml b/app/src/main/res/xml/paths.xml new file mode 100644 index 000000000..20f667683 --- /dev/null +++ b/app/src/main/res/xml/paths.xml @@ -0,0 +1,30 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/xml/preferences_download.xml b/app/src/main/res/xml/preferences_download.xml new file mode 100644 index 000000000..1225f2e87 --- /dev/null +++ b/app/src/main/res/xml/preferences_download.xml @@ -0,0 +1,33 @@ + + + + + + diff --git a/app/src/main/res/xml/preferences_filter.xml b/app/src/main/res/xml/preferences_filter.xml new file mode 100644 index 000000000..a399a89f5 --- /dev/null +++ b/app/src/main/res/xml/preferences_filter.xml @@ -0,0 +1,35 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/xml/preferences_installation.xml b/app/src/main/res/xml/preferences_installation.xml new file mode 100644 index 000000000..49fd413d4 --- /dev/null +++ b/app/src/main/res/xml/preferences_installation.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + diff --git a/app/src/main/res/xml/preferences_main.xml b/app/src/main/res/xml/preferences_main.xml new file mode 100644 index 000000000..95701e10d --- /dev/null +++ b/app/src/main/res/xml/preferences_main.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/xml/preferences_ui.xml b/app/src/main/res/xml/preferences_ui.xml new file mode 100644 index 000000000..bb37d8fdf --- /dev/null +++ b/app/src/main/res/xml/preferences_ui.xml @@ -0,0 +1,39 @@ + + + + + + + + diff --git a/app/src/test/java/com/aurora/store/ExampleUnitTest.kt b/app/src/test/java/com/aurora/store/ExampleUnitTest.kt new file mode 100644 index 000000000..b63b40f22 --- /dev/null +++ b/app/src/test/java/com/aurora/store/ExampleUnitTest.kt @@ -0,0 +1,35 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +package com.aurora.store + +import org.junit.Assert.* +import org.junit.Test + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 000000000..9365ca05c --- /dev/null +++ b/build.gradle @@ -0,0 +1,50 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +buildscript { + + repositories { + google() + jcenter() + } + ext { + kotlin_version = '1.4.30' + } + + ext.kotlin = "1.4.30" + ext.navigation= "2.3.3" + + dependencies { + classpath ("com.android.tools.build:gradle:4.1.2") + classpath ("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin") + classpath ("androidx.navigation:navigation-safe-args-gradle-plugin:$navigation") + } +} + +allprojects { + repositories { + google() + jcenter() + maven { url 'https://jitpack.io' } + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} \ No newline at end of file diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 000000000..0263fde23 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,57 @@ +# +# Aurora Store +# Copyright (C) 2021, Rahul Kumar Patel +# +# Aurora Store is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# Aurora Store is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Aurora Store. If not, see . +# +# + +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx2048m +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app"s APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Automatically convert third-party libraries to use AndroidX +android.enableJetifier=true +# Kotlin code style for this project: "official" or "obsolete": +kotlin.code.style=official + +# Enable Kapt Incremental annotation processing requeste +kapt.incremental.apt=true + +# Enable android.databinding.annotationprocessor.ProcessDataBinding (DYNAMIC) +android.databinding.incremental=true +android.lifecycleProcessor.incremental=true + +# Decrease gradle builds time +kapt.use.worker.api=true + +# turn off AP discovery in compile path, and therefore turn on Compile Avoidance +kapt.include.compile.classpath=false + +# Enable In Logcat to determine Kapt +kapt.verbose=true + diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..f6b961fd5a86aa5fbfe90f707c3138408be7c718 GIT binary patch literal 54329 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

<5G2M1WZP5 zzqSuO91lJod(SBDDw<*sX(+F6Uq~YAeYV#2A;XQu_p=N5X+#cmu19Qk>QAnV=k!?wbk5I;tDWgFc}0NkvC*G=V+Yh1cyeJVq~9czZiDXe+S=VfL2g`LWo8om z$Y~FQc6MFjV-t1Y`^D9XMwY*U_re2R?&(O~68T&D4S{X`6JYU-pz=}ew-)V0AOUT1 zVOkHAB-8uBcRjLvz<9HS#a@X*Kc@|W)nyiSgi|u5$Md|P()%2(?olGg@ypoJwp6>m z*dnfjjWC>?_1p;%1brqZyDRR;8EntVA92EJ3ByOxj6a+bhPl z;a?m4rQAV1@QU^#M1HX)0+}A<7TCO`ZR_RzF}X9-M>cRLyN4C+lCk2)kT^3gN^`IT zNP~fAm(wyIoR+l^lQDA(e1Yv}&$I!n?&*p6?lZcQ+vGLLd~fM)qt}wsbf3r=tmVYe zl)ntf#E!P7wlakP9MXS7m0nsAmqxZ*)#j;M&0De`oNmFgi$ov#!`6^4)iQyxg5Iuj zjLAhzQ)r`^hf7`*1`Rh`X;LVBtDSz@0T?kkT1o!ijeyTGt5vc^Cd*tmNgiNo^EaWvaC8$e+nb_{W01j3%=1Y&92YacjCi>eNbwk%-gPQ@H-+4xskQ}f_c=jg^S-# zYFBDf)2?@5cy@^@FHK5$YdAK9cI;!?Jgd}25lOW%xbCJ>By3=HiK@1EM+I46A)Lsd zeT|ZH;KlCml=@;5+hfYf>QNOr^XNH%J-lvev)$Omy8MZ`!{`j>(J5cG&ZXXgv)TaF zg;cz99i$4CX_@3MIb?GL0s*8J=3`#P(jXF(_(6DXZjc@(@h&=M&JG)9&Te1?(^XMW zjjC_70|b=9hB6pKQi`S^Ls7JyJw^@P>Ko^&q8F&?>6i;#CbxUiLz1ZH4lNyd@QACd zu>{!sqjB!2Dg}pbAXD>d!3jW}=5aN0b;rw*W>*PAxm7D)aw(c*RX2@bTGEI|RRp}vw7;NR2wa;rXN{L{Q#=Fa z$x@ms6pqb>!8AuV(prv>|aU8oWV={C&$c zMa=p=CDNOC2tISZcd8~18GN5oTbKY+Vrq;3_obJlfSKRMk;Hdp1`y`&LNSOqeauR_ z^j*Ojl3Ohzb5-a49A8s|UnM*NM8tg}BJXdci5%h&;$afbmRpN0&~9rCnBA`#lG!p zc{(9Y?A0Y9yo?wSYn>iigf~KP$0*@bGZ>*YM4&D;@{<%Gg5^uUJGRrV4 z(aZOGB&{_0f*O=Oi0k{@8vN^BU>s3jJRS&CJOl3o|BE{FAA&a#2YYiX3pZz@|Go-F z|Fly;7eX2OTs>R}<`4RwpHFs9nwh)B28*o5qK1Ge=_^w0m`uJOv!=&!tzt#Save(C zgKU=Bsgql|`ui(e1KVxR`?>Dx>(rD1$iWp&m`v)3A!j5(6vBm*z|aKm*T*)mo(W;R zNGo2`KM!^SS7+*9YxTm6YMm_oSrLceqN*nDOAtagULuZl5Q<7mOnB@Hq&P|#9y{5B z!2x+2s<%Cv2Aa0+u{bjZXS);#IFPk(Ph-K7K?3i|4ro> zRbqJoiOEYo(Im^((r}U4b8nvo_>4<`)ut`24?ILnglT;Pd&U}$lV3U$F9#PD(O=yV zgNNA=GW|(E=&m_1;uaNmipQe?pon4{T=zK!N!2_CJL0E*R^XXIKf*wi!>@l}3_P9Z zF~JyMbW!+n-+>!u=A1ESxzkJy$DRuG+$oioG7(@Et|xVbJ#BCt;J43Nvj@MKvTxzy zMmjNuc#LXBxFAwIGZJk~^!q$*`FME}yKE8d1f5Mp}KHNq(@=Z8YxV}0@;YS~|SpGg$_jG7>_8WWYcVx#4SxpzlV9N4aO>K{c z$P?a_fyDzGX$Of3@ykvedGd<@-R;M^Shlj*SswJLD+j@hi_&_>6WZ}#AYLR0iWMK|A zH_NBeu(tMyG=6VO-=Pb>-Q#$F*or}KmEGg*-n?vWQREURdB#+6AvOj*I%!R-4E_2$ zU5n9m>RWs|Wr;h2DaO&mFBdDb-Z{APGQx$(L`if?C|njd*fC=rTS%{o69U|meRvu?N;Z|Y zbT|ojL>j;q*?xXmnHH#3R4O-59NV1j=uapkK7}6@Wo*^Nd#(;$iuGsb;H315xh3pl zHaJ>h-_$hdNl{+|Zb%DZH%ES;*P*v0#}g|vrKm9;j-9e1M4qX@zkl&5OiwnCz=tb6 zz<6HXD+rGIVpGtkb{Q^LIgExOm zz?I|oO9)!BOLW#krLmWvX5(k!h{i>ots*EhpvAE;06K|u_c~y{#b|UxQ*O@Ks=bca z^_F0a@61j3I(Ziv{xLb8AXQj3;R{f_l6a#H5ukg5rxwF9A$?Qp-Mo54`N-SKc}fWp z0T)-L@V$$&my;l#Ha{O@!fK4-FSA)L&3<${Hcwa7ue`=f&YsXY(NgeDU#sRlT3+9J z6;(^(sjSK@3?oMo$%L-nqy*E;3pb0nZLx6 z;h5)T$y8GXK1DS-F@bGun8|J(v-9o=42&nLJy#}M5D0T^5VWBNn$RpC zZzG6Bt66VY4_?W=PX$DMpKAI!d`INr) zkMB{XPQ<52rvWVQqgI0OL_NWxoe`xxw&X8yVftdODPj5|t}S6*VMqN$-h9)1MBe0N zYq?g0+e8fJCoAksr0af1)FYtz?Me!Cxn`gUx&|T;)695GG6HF7!Kg1zzRf_{VWv^bo81v4$?F6u2g|wxHc6eJQAg&V z#%0DnWm2Rmu71rPJ8#xFUNFC*V{+N_qqFH@gYRLZ6C?GAcVRi>^n3zQxORPG)$-B~ z%_oB?-%Zf7d*Fe;cf%tQwcGv2S?rD$Z&>QC2X^vwYjnr5pa5u#38cHCt4G3|efuci z@3z=#A13`+ztmp;%zjXwPY_aq-;isu*hecWWX_=Z8paSqq7;XYnUjK*T>c4~PR4W7 z#C*%_H&tfGx`Y$w7`dXvVhmovDnT>btmy~SLf>>~84jkoQ%cv=MMb+a{JV&t0+1`I z32g_Y@yDhKe|K^PevP~MiiVl{Ou7^Mt9{lOnXEQ`xY^6L8D$705GON{!1?1&YJEl#fTf5Z)da=yiEQ zGgtC-soFGOEBEB~ZF_{7b(76En>d}mI~XIwNw{e>=Fv)sgcw@qOsykWr?+qAOZSVrQfg}TNI ztKNG)1SRrAt6#Q?(me%)>&A_^DM`pL>J{2xu>xa$3d@90xR61TQDl@fu%_85DuUUA za9tn64?At;{`BAW6oykwntxHeDpXsV#{tmt5RqdN7LtcF4vR~_kZNT|wqyR#z^Xcd zFdymVRZvyLfTpBT>w9<)Ozv@;Yk@dOSVWbbtm^y@@C>?flP^EgQPAwsy75bveo=}T zFxl(f)s)j(0#N_>Or(xEuV(n$M+`#;Pc$1@OjXEJZumkaekVqgP_i}p`oTx;terTx zZpT+0dpUya2hqlf`SpXN{}>PfhajNk_J0`H|2<5E;U5Vh4F8er z;RxLSFgpGhkU>W?IwdW~NZTyOBrQ84H7_?gviIf71l`EETodG9a1!8e{jW?DpwjL? zGEM&eCzwoZt^P*8KHZ$B<%{I}>46IT%jJ3AnnB5P%D2E2Z_ z1M!vr#8r}1|KTqWA4%67ZdbMW2YJ81b(KF&SQ2L1Qn(y-=J${p?xLMx3W7*MK;LFQ z6Z`aU;;mTL4XrrE;HY*Rkh6N%?qviUGNAKiCB~!P}Z->IpO6E(gGd7I#eDuT7j|?nZ zK}I(EJ>$Kb&@338M~O+em9(L!+=0zBR;JAQesx|3?Ok90)D1aS9P?yTh6Poh8Cr4X zk3zc=f2rE7jj+aP7nUsr@~?^EGP>Q>h#NHS?F{Cn`g-gD<8F&dqOh-0sa%pfL`b+1 zUsF*4a~)KGb4te&K0}bE>z3yb8% zibb5Q%Sfiv7feb1r0tfmiMv z@^4XYwg@KZI=;`wC)`1jUA9Kv{HKe2t$WmRcR4y8)VAFjRi zaz&O7Y2tDmc5+SX(bj6yGHYk$dBkWc96u3u&F)2yEE~*i0F%t9Kg^L6MJSb&?wrXi zGSc;_rln$!^ybwYBeacEFRsVGq-&4uC{F)*Y;<0y7~USXswMo>j4?~5%Zm!m@i@-> zXzi82sa-vpU{6MFRktJy+E0j#w`f`>Lbog{zP|9~hg(r{RCa!uGe>Yl536cn$;ouH za#@8XMvS-kddc1`!1LVq;h57~zV`7IYR}pp3u!JtE6Q67 zq3H9ZUcWPm2V4IukS}MCHSdF0qg2@~ufNx9+VMjQP&exiG_u9TZAeAEj*jw($G)zL zq9%#v{wVyOAC4A~AF=dPX|M}MZV)s(qI9@aIK?Pe+~ch|>QYb+78lDF*Nxz2-vpRbtQ*F4$0fDbvNM#CCatgQ@z1+EZWrt z2dZfywXkiW=no5jus-92>gXn5rFQ-COvKyegmL=4+NPzw6o@a?wGE-1Bt;pCHe;34K%Z z-FnOb%!nH;)gX+!a3nCk?5(f1HaWZBMmmC@lc({dUah+E;NOros{?ui1zPC-Q0);w zEbJmdE$oU$AVGQPdm{?xxI_0CKNG$LbY*i?YRQ$(&;NiA#h@DCxC(U@AJ$Yt}}^xt-EC_ z4!;QlLkjvSOhdx!bR~W|Ezmuf6A#@T`2tsjkr>TvW*lFCMY>Na_v8+{Y|=MCu1P8y z89vPiH5+CKcG-5lzk0oY>~aJC_0+4rS@c@ZVKLAp`G-sJB$$)^4*A!B zmcf}lIw|VxV9NSoJ8Ag3CwN&d7`|@>&B|l9G8tXT^BDHOUPrtC70NgwN4${$k~d_4 zJ@eo6%YQnOgq$th?0{h`KnqYa$Nz@vlHw<%!C5du6<*j1nwquk=uY}B8r7f|lY+v7 zm|JU$US08ugor8E$h3wH$c&i~;guC|3-tqJy#T;v(g( zBZtPMSyv%jzf->435yM(-UfyHq_D=6;ouL4!ZoD+xI5uCM5ay2m)RPmm$I}h>()hS zO!0gzMxc`BPkUZ)WXaXam%1;)gedA7SM8~8yIy@6TPg!hR0=T>4$Zxd)j&P-pXeSF z9W`lg6@~YDhd19B9ETv(%er^Xp8Yj@AuFVR_8t*KS;6VHkEDKI#!@l!l3v6`W1`1~ zP{C@keuV4Q`Rjc08lx?zmT$e$!3esc9&$XZf4nRL(Z*@keUbk!GZi(2Bmyq*saOD? z3Q$V<*P-X1p2}aQmuMw9nSMbOzuASsxten7DKd6A@ftZ=NhJ(0IM|Jr<91uAul4JR zADqY^AOVT3a(NIxg|U;fyc#ZnSzw2cr}#a5lZ38>nP{05D)7~ad7JPhw!LqOwATXtRhK!w0X4HgS1i<%AxbFmGJx9?sEURV+S{k~g zGYF$IWSlQonq6}e;B(X(sIH|;52+(LYW}v_gBcp|x%rEAVB`5LXg_d5{Q5tMDu0_2 z|LOm$@K2?lrLNF=mr%YP|U-t)~9bqd+wHb4KuPmNK<}PK6e@aosGZK57=Zt+kcszVOSbe;`E^dN! ze7`ha3WUUU7(nS0{?@!}{0+-VO4A{7+nL~UOPW9_P(6^GL0h${SLtqG!} zKl~Ng5#@Sy?65wk9z*3SA`Dpd4b4T^@C8Fhd8O)k_4%0RZL5?#b~jmgU+0|DB%0Z) zql-cPC>A9HPjdOTpPC` zQwvF}uB5kG$Xr4XnaH#ruSjM*xG?_hT7y3G+8Ox`flzU^QIgb_>2&-f+XB6MDr-na zSi#S+c!ToK84<&m6sCiGTd^8pNdXo+$3^l3FL_E`0 z>8it5YIDxtTp2Tm(?}FX^w{fbfgh7>^8mtvN>9fWgFN_*a1P`Gz*dyOZF{OV7BC#j zQV=FQM5m>47xXgapI$WbPM5V`V<7J9tD)oz@d~MDoM`R^Y6-Na(lO~uvZlpu?;zw6 zVO1faor3dg#JEb5Q*gz4<W8tgC3nE2BG2jeIQs1)<{In&7hJ39x=;ih;CJDy)>0S1at*7n?Wr0ahYCpFjZ|@u91Zl7( zv;CSBRC65-6f+*JPf4p1UZ)k=XivKTX6_bWT~7V#rq0Xjas6hMO!HJN8GdpBKg_$B zwDHJF6;z?h<;GXFZan8W{XFNPpOj!(&I1`&kWO86p?Xz`a$`7qV7Xqev|7nn_lQuX ziGpU1MMYt&5dE2A62iX3;*0WzNB9*nSTzI%62A+N?f?;S>N@8M=|ef3gtQTIA*=yq zQAAjOqa!CkHOQo4?TsqrrsJLclXcP?dlAVv?v`}YUjo1Htt;6djP@NPFH+&p1I+f_ z)Y279{7OWomY8baT(4TAOlz1OyD{4P?(DGv3XyJTA2IXe=kqD)^h(@*E3{I~w;ws8 z)ZWv7E)pbEM zd3MOXRH3mQhks9 zv6{s;k0y5vrcjXaVfw8^>YyPo=oIqd5IGI{)+TZq5Z5O&hXAw%ZlL}^6FugH;-%vP zAaKFtt3i^ag226=f0YjzdPn6|4(C2sC5wHFX{7QF!tG1E-JFA`>eZ`}$ymcRJK?0c zN363o{&ir)QySOFY0vcu6)kX#;l??|7o{HBDVJN+17rt|w3;(C_1b>d;g9Gp=8YVl zYTtA52@!7AUEkTm@P&h#eg+F*lR zQ7iotZTcMR1frJ0*V@Hw__~CL>_~2H2cCtuzYIUD24=Cv!1j6s{QS!v=PzwQ(a0HS zBKx04KA}-Ue+%9d`?PG*hIij@54RDSQpA7|>qYVIrK_G6%6;#ZkR}NjUgmGju)2F`>|WJoljo)DJgZr4eo1k1i1+o z1D{>^RlpIY8OUaOEf5EBu%a&~c5aWnqM zxBpJq98f=%M^{4mm~5`CWl%)nFR64U{(chmST&2jp+-r z3675V<;Qi-kJud%oWnCLdaU-)xTnMM%rx%Jw6v@=J|Ir=4n-1Z23r-EVf91CGMGNz zb~wyv4V{H-hkr3j3WbGnComiqmS0vn?n?5v2`Vi>{Ip3OZUEPN7N8XeUtF)Ry6>y> zvn0BTLCiqGroFu|m2zG-;Xb6;W`UyLw)@v}H&(M}XCEVXZQoWF=Ykr5lX3XWwyNyF z#jHv)A*L~2BZ4lX?AlN3X#axMwOC)PoVy^6lCGse9bkGjb=qz%kDa6}MOmSwK`cVO zt(e*MW-x}XtU?GY5}9{MKhRhYOlLhJE5=ca+-RmO04^ z66z{40J=s=ey9OCdc(RCzy zd7Zr1%!y3}MG(D=wM_ebhXnJ@MLi7cImDkhm0y{d-Vm81j`0mbi4lF=eirlr)oW~a zCd?26&j^m4AeXEsIUXiTal)+SPM4)HX%%YWF1?(FV47BaA`h9m67S9x>hWMVHx~Hg z1meUYoLL(p@b3?x|9DgWeI|AJ`Ia84*P{Mb%H$ZRROouR4wZhOPX15=KiBMHl!^JnCt$Az`KiH^_d>cev&f zaG2>cWf$=A@&GP~DubsgYb|L~o)cn5h%2`i^!2)bzOTw2UR!>q5^r&2Vy}JaWFUQE04v>2;Z@ZPwXr?y&G(B^@&y zsd6kC=hHdKV>!NDLIj+3rgZJ|dF`%N$DNd;B)9BbiT9Ju^Wt%%u}SvfM^=|q-nxDG zuWCQG9e#~Q5cyf8@y76#kkR^}{c<_KnZ0QsZcAT|YLRo~&tU|N@BjxOuy`#>`X~Q< z?R?-Gsk$$!oo(BveQLlUrcL#eirhgBLh`qHEMg`+sR1`A=1QX7)ZLMRT+GBy?&mM8 zQG^z-!Oa&J-k7I(3_2#Q6Bg=NX<|@X&+YMIOzfEO2$6Mnh}YV!m!e^__{W@-CTprr zbdh3f=BeCD$gHwCrmwgM3LAv3!Mh$wM)~KWzp^w)Cu6roO7uUG5z*}i0_0j47}pK; ztN530`ScGatLOL06~zO)Qmuv`h!gq5l#wx(EliKe&rz-5qH(hb1*fB#B+q`9=jLp@ zOa2)>JTl7ovxMbrif`Xe9;+fqB1K#l=Dv!iT;xF zdkCvS>C5q|O;}ns3AgoE({Ua-zNT-9_5|P0iANmC6O76Sq_(AN?UeEQJ>#b54fi3k zFmh+P%b1x3^)0M;QxXLP!BZ^h|AhOde*{9A=f3|Xq*JAs^Y{eViF|=EBfS6L%k4ip zk+7M$gEKI3?bQg?H3zaE@;cyv9kv;cqK$VxQbFEsy^iM{XXW0@2|DOu$!-k zSFl}Y=jt-VaT>Cx*KQnHTyXt}f9XswFB9ibYh+k2J!ofO+nD?1iw@mwtrqI4_i?nE zhLkPp41ED62me}J<`3RN80#vjW;wt`pP?%oQ!oqy7`miL>d-35a=qotK$p{IzeSk# ze_$CFYp_zIkrPFVaW^s#U4xT1lI^A0IBe~Y<4uS%zSV=wcuLr%gQT=&5$&K*bwqx| zWzCMiz>7t^Et@9CRUm9E+@hy~sBpm9fri$sE1zgLU((1?Yg{N1Sars=DiW&~Zw=3I zi7y)&oTC?UWD2w97xQ&5vx zRXEBGeJ(I?Y}eR0_O{$~)bMJRTsNUPIfR!xU9PE7A>AMNr_wbrFK>&vVw=Y;RH zO$mlpmMsQ}-FQ2cSj7s7GpC+~^Q~dC?y>M}%!-3kq(F3hGWo9B-Gn02AwUgJ>Z-pKOaj zysJBQx{1>Va=*e@sLb2z&RmQ7ira;aBijM-xQ&cpR>X3wP^foXM~u1>sv9xOjzZpX z0K;EGouSYD~oQ&lAafj3~EaXfFShC+>VsRlEMa9cg9i zFxhCKO}K0ax6g4@DEA?dg{mo>s+~RPI^ybb^u--^nTF>**0l5R9pocwB?_K)BG_)S zyLb&k%XZhBVr7U$wlhMqwL)_r&&n%*N$}~qijbkfM|dIWP{MyLx}X&}ES?}7i;9bW zmTVK@zR)7kE2+L42Q`n4m0VVg5l5(W`SC9HsfrLZ=v%lpef=Gj)W59VTLe+Z$8T8i z4V%5+T0t8LnM&H>Rsm5C%qpWBFqgTwL{=_4mE{S3EnBXknM&u8n}A^IIM4$s3m(Rd z>zq=CP-!9p9es2C*)_hoL@tDYABn+o#*l;6@7;knWIyDrt5EuakO99S$}n((Fj4y} zD!VvuRzghcE{!s;jC*<_H$y6!6QpePo2A3ZbX*ZzRnQq*b%KK^NF^z96CHaWmzU@f z#j;y?X=UP&+YS3kZx7;{ zDA{9(wfz7GF`1A6iB6fnXu0?&d|^p|6)%3$aG0Uor~8o? z*e}u#qz7Ri?8Uxp4m_u{a@%bztvz-BzewR6bh*1Xp+G=tQGpcy|4V_&*aOqu|32CM zz3r*E8o8SNea2hYJpLQ-_}R&M9^%@AMx&`1H8aDx4j%-gE+baf2+9zI*+Pmt+v{39 zDZ3Ix_vPYSc;Y;yn68kW4CG>PE5RoaV0n@#eVmk?p$u&Fy&KDTy!f^Hy6&^-H*)#u zdrSCTJPJw?(hLf56%2;_3n|ujUSJOU8VPOTlDULwt0jS@j^t1WS z!n7dZIoT+|O9hFUUMbID4Ec$!cc($DuQWkocVRcYSikFeM&RZ=?BW)mG4?fh#)KVG zcJ!<=-8{&MdE)+}?C8s{k@l49I|Zwswy^ZN3;E!FKyglY~Aq?4m74P-0)sMTGXqd5(S<-(DjjM z&7dL-Mr8jhUCAG$5^mI<|%`;JI5FVUnNj!VO2?Jiqa|c2;4^n!R z`5KK0hyB*F4w%cJ@Un6GC{mY&r%g`OX|1w2$B7wxu97%<@~9>NlXYd9RMF2UM>(z0 zouu4*+u+1*k;+nFPk%ly!nuMBgH4sL5Z`@Rok&?Ef=JrTmvBAS1h?C0)ty5+yEFRz zY$G=coQtNmT@1O5uk#_MQM1&bPPnspy5#>=_7%WcEL*n$;sSAZcXxMpcXxLe;_mLA z5F_paad+bGZV*oh@8h0(|D2P!q# zTHjmiphJ=AazSeKQPkGOR-D8``LjzToyx{lfK-1CDD6M7?pMZOdLKFtjZaZMPk4}k zW)97Fh(Z+_Fqv(Q_CMH-YYi?fR5fBnz7KOt0*t^cxmDoIokc=+`o# zrud|^h_?KW=Gv%byo~(Ln@({?3gnd?DUf-j2J}|$Mk>mOB+1{ZQ8HgY#SA8END(Zw z3T+W)a&;OO54~m}ffemh^oZ!Vv;!O&yhL0~hs(p^(Yv=(3c+PzPXlS5W79Er8B1o* z`c`NyS{Zj_mKChj+q=w)B}K za*zzPhs?c^`EQ;keH{-OXdXJet1EsQ)7;{3eF!-t^4_Srg4(Ot7M*E~91gwnfhqaM zNR7dFaWm7MlDYWS*m}CH${o?+YgHiPC|4?X?`vV+ws&Hf1ZO-w@OGG^o4|`b{bLZj z&9l=aA-Y(L11!EvRjc3Zpxk7lc@yH1e$a}8$_-r$)5++`_eUr1+dTb@ zU~2P1HM#W8qiNN3b*=f+FfG1!rFxnNlGx{15}BTIHgxO>Cq4 z;#9H9YjH%>Z2frJDJ8=xq>Z@H%GxXosS@Z>cY9ppF+)e~t_hWXYlrO6)0p7NBMa`+ z^L>-#GTh;k_XnE)Cgy|0Dw;(c0* zSzW14ZXozu)|I@5mRFF1eO%JM=f~R1dkNpZM+Jh(?&Zje3NgM{2ezg1N`AQg5%+3Y z64PZ0rPq6;_)Pj-hyIOgH_Gh`1$j1!jhml7ksHA1`CH3FDKiHLz+~=^u@kUM{ilI5 z^FPiJ7mSrzBs9{HXi2{sFhl5AyqwUnU{sPcUD{3+l-ZHAQ)C;c$=g1bdoxeG(5N01 zZy=t8i{*w9m?Y>V;uE&Uy~iY{pY4AV3_N;RL_jT_QtLFx^KjcUy~q9KcLE3$QJ{!)@$@En{UGG7&}lc*5Kuc^780;7Bj;)X?1CSy*^^ zPP^M)Pr5R>mvp3_hmCtS?5;W^e@5BjE>Cs<`lHDxj<|gtOK4De?Sf0YuK5GX9G93i zMYB{8X|hw|T6HqCf7Cv&r8A$S@AcgG1cF&iJ5=%+x;3yB`!lQ}2Hr(DE8=LuNb~Vs z=FO&2pdc16nD$1QL7j+!U^XWTI?2qQKt3H8=beVTdHHa9=MiJ&tM1RRQ-=+vy!~iz zj3O{pyRhCQ+b(>jC*H)J)%Wq}p>;?@W*Eut@P&?VU+Sdw^4kE8lvX|6czf{l*~L;J zFm*V~UC;3oQY(ytD|D*%*uVrBB}BbAfjK&%S;z;7$w68(8PV_whC~yvkZmX)xD^s6 z{$1Q}q;99W?*YkD2*;)tRCS{q2s@JzlO~<8x9}X<0?hCD5vpydvOw#Z$2;$@cZkYrp83J0PsS~!CFtY%BP=yxG?<@#{7%2sy zOc&^FJxsUYN36kSY)d7W=*1-{7ghPAQAXwT7z+NlESlkUH&8ODlpc8iC*iQ^MAe(B z?*xO4i{zFz^G=^G#9MsLKIN64rRJykiuIVX5~0#vAyDWc9-=6BDNT_aggS2G{B>dD ze-B%d3b6iCfc5{@yz$>=@1kdK^tX9qh0=ocv@9$ai``a_ofxT=>X7_Y0`X}a^M?d# z%EG)4@`^Ej_=%0_J-{ga!gFtji_byY&Vk@T1c|ucNAr(JNr@)nCWj?QnCyvXg&?FW;S-VOmNL6^km_dqiVjJuIASVGSFEos@EVF7St$WE&Z%)`Q##+0 zjaZ=JI1G@0!?l|^+-ZrNd$WrHBi)DA0-Eke>dp=_XpV<%CO_Wf5kQx}5e<90dt>8k zAi00d0rQ821nA>B4JHN7U8Zz=0;9&U6LOTKOaC1FC8GgO&kc=_wHIOGycL@c*$`ce703t%>S}mvxEnD-V!;6c`2(p74V7D0No1Xxt`urE66$0(ThaAZ1YVG#QP$ zy~NN%kB*zhZ2Y!kjn826pw4bh)75*e!dse+2Db(;bN34Uq7bLpr47XTX{8UEeC?2i z*{$`3dP}32${8pF$!$2Vq^gY|#w+VA_|o(oWmQX8^iw#n_crb(K3{69*iU?<%C-%H zuKi)3M1BhJ@3VW>JA`M>L~5*_bxH@Euy@niFrI$82C1}fwR$p2E&ZYnu?jlS}u7W9AyfdXh2pM>78bIt3 z)JBh&XE@zA!kyCDfvZ1qN^np20c1u#%P6;6tU&dx0phT1l=(mw7`u!-0e=PxEjDds z9E}{E!7f9>jaCQhw)&2TtG-qiD)lD(4jQ!q{`x|8l&nmtHkdul# zy+CIF8lKbp9_w{;oR+jSLtTfE+B@tOd6h=QePP>rh4@~!8c;Hlg9m%%&?e`*Z?qz5-zLEWfi>`ord5uHF-s{^bexKAoMEV@9nU z^5nA{f{dW&g$)BAGfkq@r5D)jr%!Ven~Q58c!Kr;*Li#`4Bu_?BU0`Y`nVQGhNZk@ z!>Yr$+nB=`z#o2nR0)V3M7-eVLuY`z@6CT#OTUXKnxZn$fNLPv7w1y7eGE=Qv@Hey`n;`U=xEl|q@CCV^#l)s0ZfT+mUf z^(j5r4)L5i2jnHW4+!6Si3q_LdOLQi<^fu?6WdohIkn79=jf%Fs3JkeXwF(?_tcF? z?z#j6iXEd(wJy4|p6v?xNk-)iIf2oX5^^Y3q3ziw16p9C6B;{COXul%)`>nuUoM*q zzmr|NJ5n)+sF$!yH5zwp=iM1#ZR`O%L83tyog-qh1I z0%dcj{NUs?{myT~33H^(%0QOM>-$hGFeP;U$puxoJ>>o-%Lk*8X^rx1>j|LtH$*)>1C!Pv&gd16%`qw5LdOIUbkNhaBBTo}5iuE%K&ZV^ zAr_)kkeNKNYJRgjsR%vexa~&8qMrQYY}+RbZ)egRg9_$vkoyV|Nc&MH@8L)`&rpqd zXnVaI@~A;Z^c3+{x=xgdhnocA&OP6^rr@rTvCnhG6^tMox$ulw2U7NgUtW%|-5VeH z_qyd47}1?IbuKtqNbNx$HR`*+9o=8`%vM8&SIKbkX9&%TS++x z5|&6P<%=F$C?owUI`%uvUq^yW0>`>yz!|WjzsoB9dT;2Dx8iSuK%%_XPgy0dTD4kd zDXF@&O_vBVVKQq(9YTClUPM30Sk7B!v7nOyV`XC!BA;BIVwphh+c)?5VJ^(C;GoQ$ zvBxr7_p*k$T%I1ke}`U&)$uf}I_T~#3XTi53OX)PoXVgxEcLJgZG^i47U&>LY(l%_ z;9vVDEtuMCyu2fqZeez|RbbIE7@)UtJvgAcVwVZNLccswxm+*L&w`&t=ttT=sv6Aq z!HouSc-24Y9;0q$>jX<1DnnGmAsP))- z^F~o99gHZw`S&Aw7e4id6Lg7kMk-e)B~=tZ!kE7sGTOJ)8@q}np@j7&7Sy{2`D^FH zI7aX%06vKsfJ168QnCM2=l|i>{I{%@gcr>ExM0Dw{PX6ozEuqFYEt z087%MKC;wVsMV}kIiuu9Zz9~H!21d!;Cu#b;hMDIP7nw3xSX~#?5#SSjyyg+Y@xh| z%(~fv3`0j#5CA2D8!M2TrG=8{%>YFr(j)I0DYlcz(2~92?G*?DeuoadkcjmZszH5& zKI@Lis%;RPJ8mNsbrxH@?J8Y2LaVjUIhRUiO-oqjy<&{2X~*f|)YxnUc6OU&5iac= z*^0qwD~L%FKiPmlzi&~a*9sk2$u<7Al=_`Ox^o2*kEv?p`#G(p(&i|ot8}T;8KLk- zPVf_4A9R`5^e`Om2LV*cK59EshYXse&IoByj}4WZaBomoHAPKqxRKbPcD`lMBI)g- zeMRY{gFaUuecSD6q!+b5(?vAnf>c`Z(8@RJy%Ulf?W~xB1dFAjw?CjSn$ph>st5bc zUac1aD_m6{l|$#g_v6;=32(mwpveQDWhmjR7{|B=$oBhz`7_g7qNp)n20|^^op3 zSfTdWV#Q>cb{CMKlWk91^;mHap{mk)o?udk$^Q^^u@&jd zfZ;)saW6{e*yoL6#0}oVPb2!}r{pAUYtn4{P~ES9tTfC5hXZnM{HrC8^=Pof{G4%Bh#8 ze~?C9m*|fd8MK;{L^!+wMy>=f^8b&y?yr6KnTq28$pFMBW9Oy7!oV5z|VM$s-cZ{I|Xf@}-)1=$V&x7e;9v81eiTi4O5-vs?^5pCKy2l>q);!MA zS!}M48l$scB~+Umz}7NbwyTn=rqt@`YtuwiQSMvCMFk2$83k50Q>OK5&fe*xCddIm)3D0I6vBU<+!3=6?(OhkO|b4fE_-j zimOzyfBB_*7*p8AmZi~X2bgVhyPy>KyGLAnOpou~sx9)S9%r)5dE%ADs4v%fFybDa_w*0?+>PsEHTbhKK^G=pFz z@IxLTCROWiKy*)cV3y%0FwrDvf53Ob_XuA1#tHbyn%Ko!1D#sdhBo`;VC*e1YlhrC z?*y3rp86m#qI|qeo8)_xH*G4q@70aXN|SP+6MQ!fJQqo1kwO_v7zqvUfU=Gwx`CR@ zRFb*O8+54%_8tS(ADh}-hUJzE`s*8wLI>1c4b@$al)l}^%GuIXjzBK!EWFO8W`>F^ ze7y#qPS0NI7*aU)g$_ziF(1ft;2<}6Hfz10cR8P}67FD=+}MfhrpOkF3hFhQu;Q1y zu%=jJHTr;0;oC94Hi@LAF5quAQ(rJG(uo%BiRQ@8U;nhX)j0i?0SL2g-A*YeAqF>RVCBOTrn{0R27vu}_S zS>tX4!#&U4W;ikTE!eFH+PKw%p+B(MR2I%n#+m0{#?qRP_tR@zpgCb=4rcrL!F=;A zh%EIF8m6%JG+qb&mEfuFTLHSxUAZEvC-+kvZKyX~SA3Umt`k}}c!5dy?-sLIM{h@> z!2=C)@nx>`;c9DdwZ&zeUc(7t<21D7qBj!|1^Mp1eZ6)PuvHx+poKSDCSBMFF{bKy z;9*&EyKitD99N}%mK8431rvbT+^%|O|HV23{;RhmS{$5tf!bIPoH9RKps`-EtoW5h zo6H_!s)Dl}2gCeGF6>aZtah9iLuGd19^z0*OryPNt{70RvJSM<#Ox9?HxGg04}b^f zrVEPceD%)#0)v5$YDE?f`73bQ6TA6wV;b^x*u2Ofe|S}+q{s5gr&m~4qGd!wOu|cZ||#h_u=k*fB;R6&k?FoM+c&J;ISg70h!J7*xGus)ta4veTdW)S^@sU@ z4$OBS=a~@F*V0ECic;ht4@?Jw<9kpjBgHfr2FDPykCCz|v2)`JxTH55?b3IM={@DU z!^|9nVO-R#s{`VHypWyH0%cs;0GO3E;It6W@0gX6wZ%W|Dzz&O%m17pa19db(er}C zUId1a4#I+Ou8E1MU$g=zo%g7K(=0Pn$)Rk z<4T2u<0rD)*j+tcy2XvY+0 z0d2pqm4)4lDewsAGThQi{2Kc3&C=|OQF!vOd#WB_`4gG3@inh-4>BoL!&#ij8bw7? zqjFRDaQz!J-YGitV4}$*$hg`vv%N)@#UdzHFI2E<&_@0Uw@h_ZHf}7)G;_NUD3@18 zH5;EtugNT0*RXVK*by>WS>jaDDfe!A61Da=VpIK?mcp^W?!1S2oah^wowRnrYjl~`lgP-mv$?yb6{{S55CCu{R z$9;`dyf0Y>uM1=XSl_$01Lc1Iy68IosWN8Q9Op=~I(F<0+_kKfgC*JggjxNgK6 z-3gQm6;sm?J&;bYe&(dx4BEjvq}b`OT^RqF$J4enP1YkeBK#>l1@-K`ajbn05`0J?0daOtnzh@l3^=BkedW1EahZlRp;`j*CaT;-21&f2wU z+Nh-gc4I36Cw+;3UAc<%ySb`#+c@5y ze~en&bYV|kn?Cn|@fqmGxgfz}U!98$=drjAkMi`43I4R%&H0GKEgx-=7PF}y`+j>r zg&JF`jomnu2G{%QV~Gf_-1gx<3Ky=Md9Q3VnK=;;u0lyTBCuf^aUi?+1+`4lLE6ZK zT#(Bf`5rmr(tgTbIt?yA@y`(Ar=f>-aZ}T~>G32EM%XyFvhn&@PWCm#-<&ApLDCXT zD#(9m|V(OOo7PmE@`vD4$S5;+9IQm19dd zvMEU`)E1_F+0o0-z>YCWqg0u8ciIknU#{q02{~YX)gc_u;8;i233D66pf(IkTDxeN zL=4z2)?S$TV9=ORVr&AkZMl<4tTh(v;Ix1{`pPVqI3n2ci&4Dg+W|N8TBUfZ*WeLF zqCH_1Q0W&f9T$lx3CFJ$o@Lz$99 zW!G&@zFHxTaP!o#z^~xgF|(vrHz8R_r9eo;TX9}2ZyjslrtH=%6O)?1?cL&BT(Amp zTGFU1%%#xl&6sH-UIJk_PGk_McFn7=%yd6tAjm|lnmr8bE2le3I~L{0(ffo}TQjyo zHZZI{-}{E4ohYTlZaS$blB!h$Jq^Rf#(ch}@S+Ww&$b);8+>g84IJcLU%B-W?+IY& zslcZIR>+U4v3O9RFEW;8NpCM0w1ROG84=WpKxQ^R`{=0MZCubg3st z48AyJNEvyxn-jCPTlTwp4EKvyEwD3e%kpdY?^BH0!3n6Eb57_L%J1=a*3>|k68A}v zaW`*4YitylfD}ua8V)vb79)N_Ixw_mpp}yJGbNu+5YYOP9K-7nf*jA1#<^rb4#AcS zKg%zCI)7cotx}L&J8Bqo8O1b0q;B1J#B5N5Z$Zq=wX~nQFgUfAE{@u0+EnmK{1hg> zC{vMfFLD;L8b4L+B51&LCm|scVLPe6h02rws@kGv@R+#IqE8>Xn8i|vRq_Z`V;x6F zNeot$1Zsu`lLS92QlLWF54za6vOEKGYQMdX($0JN*cjG7HP&qZ#3+bEN$8O_PfeAb z0R5;=zXac2IZ?fxu59?Nka;1lKm|;0)6|#RxkD05P5qz;*AL@ig!+f=lW5^Jbag%2 z%9@iM0ph$WFlxS!`p31t92z~TB}P-*CS+1Oo_g;7`6k(Jyj8m8U|Q3Sh7o-Icp4kV zK}%qri5>?%IPfamXIZ8pXbm-#{ytiam<{a5A+3dVP^xz!Pvirsq7Btv?*d7eYgx7q zWFxrzb3-%^lDgMc=Vl7^={=VDEKabTG?VWqOngE`Kt7hs236QKidsoeeUQ_^FzsXjprCDd@pW25rNx#6x&L6ZEpoX9Ffzv@olnH3rGOSW( zG-D|cV0Q~qJ>-L}NIyT?T-+x+wU%;+_GY{>t(l9dI%Ximm+Kmwhee;FK$%{dnF;C% zFjM2&$W68Sz#d*wtfX?*WIOXwT;P6NUw}IHdk|)fw*YnGa0rHx#paG!m=Y6GkS4VX zX`T$4eW9k1W!=q8!(#8A9h67fw))k_G)Q9~Q1e3f`aV@kbcSv7!priDUN}gX(iXTy zr$|kU0Vn%*ylmyDCO&G0Z3g>%JeEPFAW!5*H2Ydl>39w3W+gEUjL&vrRs(xGP{(ze zy7EMWF14@Qh>X>st8_029||TP0>7SG9on_xxeR2Iam3G~Em$}aGsNt$iES9zFa<3W zxtOF*!G@=PhfHO!=9pVPXMUVi30WmkPoy$02w}&6A7mF)G6-`~EVq5CwD2`9Zu`kd)52``#V zNSb`9dG~8(dooi1*-aSMf!fun7Sc`-C$-E(3BoSC$2kKrVcI!&yC*+ff2+C-@!AT_ zsvlAIV+%bRDfd{R*TMF><1&_a%@yZ0G0lg2K;F>7b+7A6pv3-S7qWIgx+Z?dt8}|S z>Qbb6x(+^aoV7FQ!Ph8|RUA6vXWQH*1$GJC+wXLXizNIc9p2yLzw9 z0=MdQ!{NnOwIICJc8!+Jp!zG}**r#E!<}&Te&}|B4q;U57$+pQI^}{qj669zMMe_I z&z0uUCqG%YwtUc8HVN7?0GHpu=bL7&{C>hcd5d(iFV{I5c~jpX&!(a{yS*4MEoYXh z*X4|Y@RVfn;piRm-C%b@{0R;aXrjBtvx^HO;6(>i*RnoG0Rtcd25BT6edxTNOgUAOjn zJ2)l{ipj8IP$KID2}*#F=M%^n&=bA0tY98@+2I+7~A&T-tw%W#3GV>GTmkHaqftl)#+E zMU*P(Rjo>8%P@_@#UNq(_L{}j(&-@1iY0TRizhiATJrnvwSH0v>lYfCI2ex^><3$q znzZgpW0JlQx?JB#0^^s-Js1}}wKh6f>(e%NrMwS`Q(FhazkZb|uyB@d%_9)_xb$6T zS*#-Bn)9gmobhAtvBmL+9H-+0_0US?g6^TOvE8f3v=z3o%NcPjOaf{5EMRnn(_z8- z$|m0D$FTU zDy;21v-#0i)9%_bZ7eo6B9@Q@&XprR&oKl4m>zIj-fiRy4Dqy@VVVs?rscG| zmzaDQ%>AQTi<^vYCmv#KOTd@l7#2VIpsj?nm_WfRZzJako`^uU%Nt3e;cU*y*|$7W zLm%fX#i_*HoUXu!NI$ey>BA<5HQB=|nRAwK!$L#n-Qz;~`zACig0PhAq#^5QS<8L2 zS3A+8%vbVMa7LOtTEM?55apt(DcWh#L}R^P2AY*c8B}Cx=6OFAdMPj1f>k3#^#+Hk z6uW1WJW&RlBRh*1DLb7mJ+KO>!t^t8hX1#_Wk`gjDio9)9IGbyCAGI4DJ~orK+YRv znjxRMtshZQHc$#Y-<-JOV6g^Cr@odj&Xw5B(FmI)*qJ9NHmIz_r{t)TxyB`L-%q5l ztzHgD;S6cw?7Atg*6E1!c6*gPRCb%t7D%z<(xm+K{%EJNiI2N0l8ud0Ch@_av_RW? zIr!nO4dL5466WslE6MsfMss7<)-S!e)2@r2o=7_W)OO`~CwklRWzHTfpB)_HYwgz=BzLhgZ9S<{nLBOwOIgJU=94uj6r!m>Xyn9>&xP+=5!zG_*yEoRgM0`aYts z^)&8(>z5C-QQ*o_s(8E4*?AX#S^0)aqB)OTyX>4BMy8h(cHjA8ji1PRlox@jB*1n? zDIfyDjzeg91Ao(;Q;KE@zei$}>EnrF6I}q&Xd=~&$WdDsyH0H7fJX|E+O~%LS*7^Q zYzZ4`pBdY{b7u72gZm6^5~O-57HwzwAz{)NvVaowo`X02tL3PpgLjwA`^i9F^vSpN zAqH3mRjG8VeJNHZ(1{%!XqC+)Z%D}58Qel{_weSEHoygT9pN@i zi=G;!Vj6XQk2tuJC>lza%ywz|`f7TIz*EN2Gdt!s199Dr4Tfd_%~fu8gXo~|ogt5Q zlEy_CXEe^BgsYM^o@L?s33WM14}7^T(kqohOX_iN@U?u;$l|rAvn{rwy>!yfZw13U zB@X9)qt&4;(C6dP?yRsoTMI!j-f1KC!<%~i1}u7yLXYn)(#a;Z6~r>hp~kfP));mi zcG%kdaB9H)z9M=H!f>kM->fTjRVOELNwh1amgKQT=I8J66kI)u_?0@$$~5f`u%;zl zC?pkr^p2Fe=J~WK%4ItSzKA+QHqJ@~m|Cduv=Q&-P8I5rQ-#G@bYH}YJr zUS(~(w|vKyU(T(*py}jTUp%I%{2!W!K(i$uvotcPjVddW z8_5HKY!oBCwGZcs-q`4Yt`Zk~>K?mcxg51wkZlX5e#B08I75F7#dgn5yf&Hrp`*%$ zQ;_Qg>TYRzBe$x=T(@WI9SC!ReSas9vDm(yslQjBJZde5z8GDU``r|N(MHcxNopGr z_}u39W_zwWDL*XYYt>#Xo!9kL#97|EAGyGBcRXtLTd59x%m=3i zL^9joWYA)HfL15l9%H?q`$mY27!<9$7GH(kxb%MV>`}hR4a?+*LH6aR{dzrX@?6X4 z3e`9L;cjqYb`cJmophbm(OX0b)!AFG?5`c#zLagzMW~o)?-!@e80lvk!p#&CD8u5_r&wp4O0zQ>y!k5U$h_K;rWGk=U)zX!#@Q%|9g*A zWx)qS1?fq6X<$mQTB$#3g;;5tHOYuAh;YKSBz%il3Ui6fPRv#v62SsrCdMRTav)Sg zTq1WOu&@v$Ey;@^+_!)cf|w_X<@RC>!=~+A1-65O0bOFYiH-)abINwZvFB;hJjL_$ z(9iScmUdMp2O$WW!520Hd0Q^Yj?DK%YgJD^ez$Z^?@9@Ab-=KgW@n8nC&88)TDC+E zlJM)L3r+ZJfZW_T$;Imq*#2<(j+FIk8ls7)WJ6CjUu#r5PoXxQs4b)mZza<8=v{o)VlLRM<9yw^0En#tXAj`Sylxvki{<1DPe^ zhjHwx^;c8tb?Vr$6ZB;$Ff$+3(*oinbwpN-#F)bTsXq@Sm?43MC#jQ~`F|twI=7oC zH4TJtu#;ngRA|Y~w5N=UfMZi?s0%ZmKUFTAye&6Y*y-%c1oD3yQ%IF2q2385Zl+=> zfz=o`Bedy|U;oxbyb^rB9ixG{Gb-{h$U0hVe`J;{ql!s_OJ_>>eoQn(G6h7+b^P48 zG<=Wg2;xGD-+d@UMZ!c;0>#3nws$9kIDkK13IfloGT@s14AY>&>>^#>`PT7GV$2Hp zN<{bN*ztlZu_%W=&3+=#3bE(mka6VoHEs~0BjZ$+=0`a@R$iaW)6>wp2w)=v2@|2d z%?34!+iOc5S@;AAC4hELWLH56RGxo4jw8MDMU0Wk2k_G}=Vo(>eRFo(g3@HjG|`H3 zm8b*dK=moM*oB<)*A$M9!!5o~4U``e)wxavm@O_R(`P|u%9^LGi(_%IF<6o;NLp*0 zKsfZ0#24GT8(G`i4UvoMh$^;kOhl?`0yNiyrC#HJH=tqOH^T_d<2Z+ zeN>Y9Zn!X4*DMCK^o75Zk2621bdmV7Rx@AX^alBG4%~;G_vUoxhfhFRlR&+3WwF^T zaL)8xPq|wCZoNT^>3J0K?e{J-kl+hu2rZI>CUv#-z&u@`hjeb+bBZ>bcciQVZ{SbW zez04s9oFEgc8Z+Kp{XFX`MVf-s&w9*dx7wLen(_@y34}Qz@&`$2+osqfxz4&d}{Ql z*g1ag00Gu+$C`0avds{Q65BfGsu9`_`dML*rX~hyWIe$T>CsPRoLIr%MTk3pJ^2zH1qub1MBzPG}PO;Wmav9w%F7?%l=xIf#LlP`! z_Nw;xBQY9anH5-c8A4mME}?{iewjz(Sq-29r{fV;Fc>fv%0!W@(+{={Xl-sJ6aMoc z)9Q+$bchoTGTyWU_oI19!)bD=IG&OImfy;VxNXoIO2hYEfO~MkE#IXTK(~?Z&!ae! zl8z{D&2PC$Q*OBC(rS~-*-GHNJ6AC$@eve>LB@Iq;jbBZj`wk4|LGogE||Ie=M5g= z9d`uYQ1^Sr_q2wmZE>w2WG)!F%^KiqyaDtIAct?}D~JP4shTJy5Bg+-(EA8aXaxbd~BKMtTf2iQ69jD1o* zZF9*S3!v-TdqwK$%&?91Sh2=e63;X0Lci@n7y3XOu2ofyL9^-I767eHESAq{m+@*r zbVDx!FQ|AjT;!bYsXv8ilQjy~Chiu&HNhFXt3R_6kMC8~ChEFqG@MWu#1Q1#=~#ix zrkHpJre_?#r=N0wv`-7cHHqU`phJX2M_^{H0~{VP79Dv{6YP)oA1&TSfKPEPZn2)G z9o{U1huZBLL;Tp_0OYw@+9z(jkrwIGdUrOhKJUbwy?WBt zlIK)*K0lQCY0qZ!$%1?3A#-S70F#YyUnmJF*`xx?aH5;gE5pe-15w)EB#nuf6B*c~ z8Z25NtY%6Wlb)bUA$w%HKs5$!Z*W?YKV-lE0@w^{4vw;J>=rn?u!rv$&eM+rpU6rc=j9>N2Op+C{D^mospMCjF2ZGhe4eADA#skp2EA26%p3Ex9wHW8l&Y@HX z$Qv)mHM}4*@M*#*ll5^hE9M^=q~eyWEai*P;4z<9ZYy!SlNE5nlc7gm;M&Q zKhKE4d*%A>^m0R?{N}y|i6i^k>^n4(wzKvlQeHq{l&JuFD~sTsdhs`(?lFK@Q{pU~ zb!M3c@*3IwN1RUOVjY5>uT+s-2QLWY z4T2>fiSn>>Fob+%B868-v9D@AfWr#M8eM6w#eAlhc#zk6jkLxGBGk`E3$!A@*am!R zy>29&ptYK6>cvP`b!syNp)Q$0UOW|-O@)8!?94GOYF_}+zlW%fCEl|Tep_zx05g6q z>tp47e-&R*hSNe{6{H!mL?+j$c^TXT{C&@T-xIaesNCl05 z9SLb@q&mSb)I{VXMaiWa3PWj=Ed!>*GwUe;^|uk=Pz$njNnfFY^MM>E?zqhf6^{}0 zx&~~dA5#}1ig~7HvOQ#;d9JZBeEQ+}-~v$at`m!(ai z$w(H&mWCC~;PQ1$%iuz3`>dWeb3_p}X>L2LK%2l59Tyc}4m0>9A!8rhoU3m>i2+hl zx?*qs*c^j}+WPs>&v1%1Ko8_ivAGIn@QK7A`hDz-Emkcgv2@wTbYhkiwX2l=xz*XG zaiNg+j4F-I>9v+LjosI-QECrtKjp&0T@xIMKVr+&)gyb4@b3y?2CA?=ooN zT#;rU86WLh(e@#mF*rk(NV-qSIZyr z$6!ZUmzD)%yO-ot`rw3rp6?*_l*@Z*IB0xn4|BGPWHNc-1ZUnNSMWmDh=EzWJRP`) zl%d%J613oXzh5;VY^XWJi{lB`f#u+ThvtP7 zq(HK<4>tw(=yzSBWtYO}XI`S1pMBe3!jFxBHIuwJ(@%zdQFi1Q_hU2eDuHqXte7Ki zOV55H2D6u#4oTfr7|u*3p75KF&jaLEDpxk!4*bhPc%mpfj)Us3XIG3 zIKMX^s^1wt8YK7Ky^UOG=w!o5e7W-<&c|fw2{;Q11vm@J{)@N3-p1U>!0~sKWHaL= zWV(0}1IIyt1p%=_-Fe5Kfzc71wg}`RDDntVZv;4!=&XXF-$48jS0Sc;eDy@Sg;+{A zFStc{dXT}kcIjMXb4F7MbX~2%i;UrBxm%qmLKb|2=?uPr00-$MEUIGR5+JG2l2Nq` zkM{{1RO_R)+8oQ6x&-^kCj)W8Z}TJjS*Wm4>hf+4#VJP)OBaDF%3pms7DclusBUw} z{ND#!*I6h85g6DzNvdAmnwWY{&+!KZM4DGzeHI?MR@+~|su0{y-5-nICz_MIT_#FE zm<5f3zlaKq!XyvY3H`9s&T};z!cK}G%;~!rpzk9-6L}4Rg7vXtKFsl}@sT#U#7)x- z7UWue5sa$R>N&b{J61&gvKcKlozH*;OjoDR+elkh|4bJ!_3AZNMOu?n9&|L>OTD78 z^i->ah_Mqc|Ev)KNDzfu1P3grBIM#%`QZqj5W{qu(HocQhjyS;UINoP`{J+DvV?|1 z_sw6Yr3z6%e7JKVDY<$P=M)dbk@~Yw9|2!Cw!io3%j92wTD!c^e9Vj+7VqXo3>u#= zv#M{HHJ=e$X5vQ>>ML?E8#UlmvJgTnb73{PSPTf*0)mcj6C z{KsfUbDK|F$E(k;ER%8HMdDi`=BfpZzP3cl5yJHu;v^o2FkHNk;cXc17tL8T!CsYI zfeZ6sw@;8ia|mY_AXjCS?kUfxdjDB28)~Tz1dGE|{VfBS9`0m2!m1yG?hR})er^pl4c@9Aq+|}ZlDaHL)K$O| z%9Jp-imI-Id0|(d5{v~w6mx)tUKfbuVD`xNt04Mry%M+jXzE>4(TBsx#&=@wT2Vh) z1yeEY&~17>0%P(eHP0HB^|7C+WJxQBTG$uyOWY@iDloRIb-Cf!p<{WQHR!422#F34 zG`v|#CJ^G}y9U*7jgTlD{D&y$Iv{6&PYG>{Ixg$pGk?lWrE#PJ8KunQC@}^6OP!|< zS;}p3to{S|uZz%kKe|;A0bL0XxPB&Q{J(9PyX`+Kr`k~r2}yP^ND{8!v7Q1&vtk& z2Y}l@J@{|2`oA%sxvM9i0V+8IXrZ4;tey)d;LZI70Kbim<4=WoTPZy=Yd|34v#$Kh zx|#YJ8s`J>W&jt#GcMpx84w2Z3ur-rK7gf-p5cE)=w1R2*|0mj12hvapuUWM0b~dG zMg9p8FmAZI@i{q~0@QuY44&mMUNXd7z>U58shA3o`p5eVLpq>+{(<3->DWuSFVZwC zxd50Uz(w~LxC4}bgag#q#NNokK@yNc+Q|Ap!u>Ddy+df>v;j@I12CDNN9do+0^n8p zMQs7X#+FVF0C5muGfN{r0|Nkql%BQT|K(DDNdR2pzM=_ea5+GO|J67`05AV92t@4l z0Qno0078PIHdaQGHZ~Scw!dzgqjK~3B7kf>BcP__&lLyU(cu3B^uLo%{j|Mb0NR)tkeT7Hcwp4O# z)yzu>cvG(d9~0a^)eZ;;%3ksk@F&1eEBje~ zW+-_s)&RgiweQc!otF>4%vbXKaOU41{!hw?|2`Ld3I8$&#WOsq>EG)1ANb!{N4z9@ zsU!bPG-~-bqCeIDzo^Q;gnucB{tRzm{ZH^Orphm2U+REA!*<*J6YQV83@&xoDl%#wnl5qcBqCcAF-vX5{30}(oJrnSH z{RY85hylK2dMOh2%oO1J8%)0?8TOL%rS8)+CsDv}aQ>4D)Jv+DLK)9gI^n-T^$)Tc zFPUD75qJm!Y-KBqj;JP4dV4 z`X{lGmn<)1IGz330}s}Jrjtf{(lnuuNHe5(ezA(pYa=1|Ff-LhPFK8 zyJh_b{yzu0yll6ZkpRzRjezyYivjyjW7QwO;@6X`m;2Apn2EK2!~7S}-*=;5*7K$B z`x(=!^?zgj(-`&ApZJXI09aDLXaT@<;CH=?fBOY5d|b~wBA@@p^K#nxr`)?i?SqTupI_PJ(A3cx`z~9mX_*)>L F{|7XC?P&l2 literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..6a0f54918 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,25 @@ +# +# Aurora Store +# Copyright (C) 2021, Rahul Kumar Patel +# +# Aurora Store is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# Aurora Store is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with Aurora Store. If not, see . +# +# + +#Fri Oct 16 03:09:47 IST 2020 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip diff --git a/gradlew b/gradlew new file mode 100755 index 000000000..cccdd3d51 --- /dev/null +++ b/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 000000000..e95643d6a --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 000000000..e1566ee31 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,21 @@ +/* + * Aurora Store + * Copyright (C) 2021, Rahul Kumar Patel + * + * Aurora Store is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * Aurora Store is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Aurora Store. If not, see . + * + */ + +include ':app' +rootProject.name = "AuroraStore4" \ No newline at end of file