├── .editorconfig ├── .github └── workflows │ ├── android-lint.yml │ ├── build-and-test.yml │ └── ktlint-check.yml ├── .gitignore ├── LICENSE ├── PRIVACY_POLICY.md ├── README.md ├── app ├── .gitignore ├── benchmark-rules.pro ├── build.gradle.kts ├── proguard-rules.pro ├── schemas │ └── com.rendox.grocerygenius.database.AppDatabase │ │ └── 1.json └── src │ ├── main │ ├── AndroidManifest.xml │ ├── ic_launcher-playstore.png │ ├── java │ │ └── com │ │ │ └── rendox │ │ │ └── grocerygenius │ │ │ ├── GroceryGeniusApp.kt │ │ │ ├── GroceryGeniusNavHost.kt │ │ │ ├── MainActivity.kt │ │ │ ├── MainActivityUiState.kt │ │ │ ├── MainActivityViewModel.kt │ │ │ ├── data │ │ │ ├── DataModule.kt │ │ │ ├── SyncUtilities.kt │ │ │ ├── category │ │ │ │ ├── CategoryRepository.kt │ │ │ │ └── CategoryRepositoryImpl.kt │ │ │ ├── grocery │ │ │ │ ├── GroceryRepository.kt │ │ │ │ └── GroceryRepositoryImpl.kt │ │ │ ├── grocerylist │ │ │ │ ├── GroceryListRepository.kt │ │ │ │ └── GroceryListRepositoryImpl.kt │ │ │ ├── icons │ │ │ │ ├── IconRepository.kt │ │ │ │ └── IconRepositoryImpl.kt │ │ │ ├── model │ │ │ │ ├── Category.kt │ │ │ │ ├── Grocery.kt │ │ │ │ ├── GroceryList.kt │ │ │ │ ├── Icon.kt │ │ │ │ └── Product.kt │ │ │ ├── product │ │ │ │ ├── ProductRepository.kt │ │ │ │ └── ProductRepositoryImpl.kt │ │ │ ├── userpreferences │ │ │ │ ├── UserPreferencesRepository.kt │ │ │ │ └── UserPreferencesRepositoryImpl.kt │ │ │ └── util │ │ │ │ ├── ConnectivityManagerNetworkMonitor.kt │ │ │ │ ├── NetworkMonitor.kt │ │ │ │ └── SyncManager.kt │ │ │ ├── database │ │ │ ├── AppDatabase.kt │ │ │ ├── DaosModule.kt │ │ │ ├── DatabaseModule.kt │ │ │ ├── category │ │ │ │ ├── CategoryDao.kt │ │ │ │ └── CategoryEntity.kt │ │ │ ├── grocery │ │ │ │ ├── CombinedGrocery.kt │ │ │ │ ├── GroceryDao.kt │ │ │ │ └── GroceryEntity.kt │ │ │ ├── groceryicon │ │ │ │ ├── IconDao.kt │ │ │ │ └── IconEntity.kt │ │ │ ├── grocerylist │ │ │ │ ├── GroceryListDao.kt │ │ │ │ └── GroceryListEntity.kt │ │ │ └── product │ │ │ │ ├── CombinedProduct.kt │ │ │ │ ├── ProductDao.kt │ │ │ │ └── ProductEntity.kt │ │ │ ├── datastore │ │ │ ├── ChangeListVersionsDataSource.kt │ │ │ ├── DataStoreModule.kt │ │ │ └── UserPreferencesDataSource.kt │ │ │ ├── feature │ │ │ ├── addgrocery │ │ │ │ ├── AddGroceryBottomSheetContent.kt │ │ │ │ ├── AddGroceryBottomSheetContentType.kt │ │ │ │ ├── AddGroceryBottomSheetState.kt │ │ │ │ ├── AddGroceryUiIntent.kt │ │ │ │ ├── AddGroceryUiState.kt │ │ │ │ └── AddGroceryViewModel.kt │ │ │ ├── dashboardscreen │ │ │ │ ├── GroceryListsDashboardNavigation.kt │ │ │ │ ├── GroceryListsDashboardScreen.kt │ │ │ │ ├── GroceryListsDashboardUiIntent.kt │ │ │ │ ├── GroceryListsDashboardViewModel.kt │ │ │ │ └── recyclerview │ │ │ │ │ ├── DashboardDiffUtilCallback.kt │ │ │ │ │ ├── DashboardItemViewHolder.kt │ │ │ │ │ ├── DashboardRecyclerViewAdapter.kt │ │ │ │ │ └── GroceryListAdderItemViewHolder.kt │ │ │ ├── editgrocery │ │ │ │ ├── EditGroceryBottomSheet.kt │ │ │ │ ├── EditGroceryBottomSheetContent.kt │ │ │ │ ├── EditGroceryUiIntent.kt │ │ │ │ ├── EditGroceryUiState.kt │ │ │ │ ├── EditGroceryViewModel.kt │ │ │ │ └── dialogs │ │ │ │ │ ├── CategoryPickerDialog.kt │ │ │ │ │ ├── IconPickerDialog.kt │ │ │ │ │ ├── PickerDialog.kt │ │ │ │ │ └── PickerDialogType.kt │ │ │ ├── grocerylist │ │ │ │ ├── CategoryScreen.kt │ │ │ │ ├── GroceryListNestedNavigation.kt │ │ │ │ ├── GroceryListPurchaseState.kt │ │ │ │ ├── GroceryListScreen.kt │ │ │ │ ├── GroceryListViewModel.kt │ │ │ │ └── GroceryListsUiIntent.kt │ │ │ ├── iconpicker │ │ │ │ ├── IconPickerIntent.kt │ │ │ │ ├── IconPickerNavigation.kt │ │ │ │ ├── IconPickerScreen.kt │ │ │ │ ├── IconPickerUiState.kt │ │ │ │ └── IconPickerViewModel.kt │ │ │ ├── onboarding │ │ │ │ ├── OnboardingNavigation.kt │ │ │ │ ├── OnboardingSyncScreen.kt │ │ │ │ └── OnboardingViewModel.kt │ │ │ └── settings │ │ │ │ ├── SettingsScreen.kt │ │ │ │ ├── SettingsScreenIntent.kt │ │ │ │ ├── SettingsScreenNavigation.kt │ │ │ │ ├── SettingsScreenState.kt │ │ │ │ ├── SettingsScreenViewModel.kt │ │ │ │ └── categories │ │ │ │ └── recyclerview │ │ │ │ ├── CategoriesRecyclerViewAdapter.kt │ │ │ │ └── CategoryViewHolder.kt │ │ │ ├── filestorage │ │ │ ├── AssetToFileSaver.kt │ │ │ ├── JsonAssetDecoder.kt │ │ │ └── UnzipUtils.kt │ │ │ ├── model │ │ │ ├── Category.kt │ │ │ ├── ChangeListVersions.kt │ │ │ ├── CompoundGroceryId.kt │ │ │ ├── DarkThemeConfig.kt │ │ │ ├── Grocery.kt │ │ │ ├── GroceryGeniusColorScheme.kt │ │ │ ├── GroceryList.kt │ │ │ ├── IconReference.kt │ │ │ ├── Product.kt │ │ │ └── UserPreferences.kt │ │ │ ├── network │ │ │ ├── GitHubApi.kt │ │ │ ├── NetworkUtil.kt │ │ │ ├── data │ │ │ │ └── sources │ │ │ │ │ ├── category │ │ │ │ │ ├── CategoryNetworkDataSource.kt │ │ │ │ │ └── OfflineFirstCategoryNetworkDataSource.kt │ │ │ │ │ ├── icon │ │ │ │ │ ├── IconNetworkDataSource.kt │ │ │ │ │ └── OfflineFirstIconNetworkDataSource.kt │ │ │ │ │ └── product │ │ │ │ │ ├── OfflineFirstProductNetworkDataSource.kt │ │ │ │ │ └── ProductNetworkDataSource.kt │ │ │ ├── di │ │ │ │ ├── DispatchersModule.kt │ │ │ │ ├── GroceryGeniusDispatchers.kt │ │ │ │ ├── NetworkApiModule.kt │ │ │ │ └── NetworkModule.kt │ │ │ └── model │ │ │ │ ├── CategoryNetwork.kt │ │ │ │ ├── GroceryIconAsset.kt │ │ │ │ ├── NetworkChangeList.kt │ │ │ │ └── ProductNetwork.kt │ │ │ ├── sync │ │ │ └── work │ │ │ │ ├── di │ │ │ │ └── SyncModule.kt │ │ │ │ ├── initializers │ │ │ │ ├── SyncInitializer.kt │ │ │ │ └── SyncWorkHelpers.kt │ │ │ │ ├── status │ │ │ │ ├── SyncStatus.kt │ │ │ │ └── WorkManagerSyncManager.kt │ │ │ │ └── workers │ │ │ │ ├── DelegatingWorker.kt │ │ │ │ └── SyncWorker.kt │ │ │ ├── testing │ │ │ └── GroceryGeniusTestRunner.kt │ │ │ └── ui │ │ │ ├── GroceryGeniusTransition.kt │ │ │ ├── components │ │ │ ├── BottomSheetDragHandle.kt │ │ │ ├── CustomIconSetting.kt │ │ │ ├── DeleteConfirmationDialog.kt │ │ │ ├── DropDownMenu.kt │ │ │ ├── GroceryCompact.kt │ │ │ ├── GroceryIcon.kt │ │ │ ├── Scrim.kt │ │ │ ├── SearchField.kt │ │ │ ├── TonalDataInput.kt │ │ │ ├── collapsingtoolbar │ │ │ │ ├── CollapsingToolbar.kt │ │ │ │ ├── CollapsingToolbarScaffoldScrollableState.kt │ │ │ │ └── scrollbehavior │ │ │ │ │ ├── CollapsingToolbarNestedScrollConnection.kt │ │ │ │ │ ├── ExitUntilCollapsedState.kt │ │ │ │ │ ├── FixedScrollFlagState.kt │ │ │ │ │ ├── ScrollFlagState.kt │ │ │ │ │ └── ToolbarState.kt │ │ │ ├── grocerylist │ │ │ │ ├── DefaultGroceryListItemColors.kt │ │ │ │ ├── GroceryGridItem.kt │ │ │ │ ├── GroceryGroup.kt │ │ │ │ ├── GroupedLazyGroceryGrid.kt │ │ │ │ └── LazyGroceryGrid.kt │ │ │ └── scrollbar │ │ │ │ ├── AppScrollbars.kt │ │ │ │ ├── LazyScrollbarUtilities.kt │ │ │ │ ├── Scrollbar.kt │ │ │ │ ├── ScrollbarExt.kt │ │ │ │ └── ThumbExt.kt │ │ │ ├── helpers │ │ │ ├── DragHandleReorderItemTouchHelperCallback.kt │ │ │ └── UiEvent.kt │ │ │ └── theme │ │ │ ├── Dimens.kt │ │ │ ├── ExtendedColors.kt │ │ │ ├── Theme.kt │ │ │ ├── Type.kt │ │ │ └── colorschemes │ │ │ ├── BeigeColorScheme.kt │ │ │ ├── CyanColorScheme.kt │ │ │ ├── GreenColorScheme.kt │ │ │ ├── PinkColorScheme.kt │ │ │ ├── PurpleColorScheme.kt │ │ │ └── YellowColorScheme.kt │ └── res │ │ ├── drawable-hdpi │ │ └── empty_grocery_list_illustration.png │ │ ├── drawable-mdpi │ │ └── empty_grocery_list_illustration.png │ │ ├── drawable-v24 │ │ ├── app_logo_circle.xml │ │ └── ic_launcher_foreground.xml │ │ ├── drawable-xhdpi │ │ └── empty_grocery_list_illustration.png │ │ ├── drawable-xxhdpi │ │ └── empty_grocery_list_illustration.png │ │ ├── drawable-xxxhdpi │ │ └── empty_grocery_list_illustration.png │ │ ├── drawable │ │ ├── app_logo_circle.xml │ │ ├── baseline_drag_handle_24.xml │ │ ├── baseline_folder_24.xml │ │ ├── baseline_history_24.xml │ │ ├── baseline_palette_24.xml │ │ ├── baseline_swap_vert_24.xml │ │ ├── cancel_circle.xml │ │ ├── checkmark_circle.xml │ │ ├── cloud_off.xml │ │ ├── day_night.xml │ │ ├── github_mark.xml │ │ ├── image_icon.xml │ │ ├── mail.xml │ │ └── shopping_cart.xml │ │ ├── font │ │ └── train_one_regular.ttf │ │ ├── layout │ │ └── list_recyclerview.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.webp │ │ └── ic_launcher_round.webp │ │ ├── values-af-rZA │ │ └── strings.xml │ │ ├── values-ar-rSA │ │ └── strings.xml │ │ ├── values-ca-rES │ │ └── strings.xml │ │ ├── values-cs-rCZ │ │ └── strings.xml │ │ ├── values-da-rDK │ │ └── strings.xml │ │ ├── values-de-rDE │ │ └── strings.xml │ │ ├── values-el-rGR │ │ └── strings.xml │ │ ├── values-es-rES │ │ └── strings.xml │ │ ├── values-fi-rFI │ │ └── strings.xml │ │ ├── values-fr-rFR │ │ └── strings.xml │ │ ├── values-hu-rHU │ │ └── strings.xml │ │ ├── values-it-rIT │ │ └── strings.xml │ │ ├── values-iw-rIL │ │ └── strings.xml │ │ ├── values-ja-rJP │ │ └── strings.xml │ │ ├── values-ko-rKR │ │ └── strings.xml │ │ ├── values-nl-rNL │ │ └── strings.xml │ │ ├── values-no-rNO │ │ └── strings.xml │ │ ├── values-pl-rPL │ │ └── strings.xml │ │ ├── values-pt-rBR │ │ └── strings.xml │ │ ├── values-pt-rPT │ │ └── strings.xml │ │ ├── values-ro-rRO │ │ └── strings.xml │ │ ├── values-ru-rRU │ │ └── strings.xml │ │ ├── values-sr-rSP │ │ └── strings.xml │ │ ├── values-sv-rSE │ │ └── strings.xml │ │ ├── values-tr-rTR │ │ └── strings.xml │ │ ├── values-uk-rUA │ │ └── strings.xml │ │ ├── values-vi-rVN │ │ └── strings.xml │ │ ├── values-zh-rCN │ │ └── strings.xml │ │ ├── values-zh-rTW │ │ └── strings.xml │ │ ├── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── themes.xml │ │ └── xml │ │ ├── backup_rules.xml │ │ └── data_extraction_rules.xml │ └── release │ └── generated │ └── baselineProfiles │ ├── baseline-prof.txt │ └── startup-prof.txt ├── assets ├── README.md ├── category │ ├── af_ZA │ │ └── categories.json │ ├── ar_SA │ │ └── categories.json │ ├── ca_ES │ │ └── categories.json │ ├── categories.json │ ├── categories_change_list.json │ ├── categories_en.json │ ├── cs_CZ │ │ └── categories.json │ ├── da_DK │ │ └── categories.json │ ├── de_DE │ │ └── categories.json │ ├── el_GR │ │ └── categories.json │ ├── es_ES │ │ └── categories.json │ ├── fi_FI │ │ └── categories.json │ ├── fr_FR │ │ └── categories.json │ ├── he_IL │ │ └── categories.json │ ├── hu_HU │ │ └── categories.json │ ├── it_IT │ │ └── categories.json │ ├── ja_JP │ │ └── categories.json │ ├── ko_KR │ │ └── categories.json │ ├── nl_NL │ │ └── categories.json │ ├── no_NO │ │ └── categories.json │ ├── pl_PL │ │ └── categories.json │ ├── pt_BR │ │ └── categories.json │ ├── pt_PT │ │ └── categories.json │ ├── ro_RO │ │ └── categories.json │ ├── ru_RU │ │ └── categories.json │ ├── sr_SP │ │ └── categories.json │ ├── sv_SE │ │ └── categories.json │ ├── tr_TR │ │ └── categories.json │ ├── uk_UA │ │ └── categories.json │ ├── vi_VN │ │ └── categories.json │ ├── zh_CN │ │ └── categories.json │ └── zh_TW │ │ └── categories.json ├── icons │ ├── all_icons.zip │ ├── apple.png │ ├── apricots.png │ ├── avocado.png │ ├── bacon.png │ ├── baguette.png │ ├── bananas.png │ ├── batteries.png │ ├── beer.png │ ├── beet.png │ ├── berries.png │ ├── bottle_narrow.png │ ├── bottle_plastic.png │ ├── bottle_small.png │ ├── bread.png │ ├── broccoli.png │ ├── buns.png │ ├── butter.png │ ├── cabbage.png │ ├── cake.png │ ├── can.png │ ├── carrots.png │ ├── cat.png │ ├── cereal.png │ ├── cheese.png │ ├── cherries.png │ ├── chicken.png │ ├── chicken_legs.png │ ├── chips.png │ ├── chocolate.png │ ├── cinnamon.png │ ├── cleaner_spray.png │ ├── condoms.png │ ├── cookies.png │ ├── cottage_cheese.png │ ├── cotton_pads.png │ ├── cotton_swabs.png │ ├── cow.png │ ├── cream.png │ ├── cucumber.png │ ├── deodorant.png │ ├── dog.png │ ├── drinks.png │ ├── eggplant.png │ ├── eggs.png │ ├── fish.png │ ├── flour.png │ ├── fruits.png │ ├── garlic.png │ ├── gift.png │ ├── grain.png │ ├── grapes.png │ ├── ham.png │ ├── honey.png │ ├── ice_cream.png │ ├── icons_change_list.json │ ├── jam.png │ ├── jar_spices.png │ ├── juice.png │ ├── ketchup.png │ ├── kiwi.png │ ├── lemon.png │ ├── lipstick.png │ ├── mandarins.png │ ├── meat.png │ ├── milk.png │ ├── minced_meat.png │ ├── mushroom.png │ ├── napkins.png │ ├── nuts.png │ ├── oats.png │ ├── onion.png │ ├── orange.png │ ├── pads.png │ ├── parsley.png │ ├── pasta.png │ ├── peach.png │ ├── pear.png │ ├── peas.png │ ├── pepper.png │ ├── perfume.png │ ├── pills.png │ ├── pineapple.png │ ├── pizza.png │ ├── plums.png │ ├── popcorn.png │ ├── pork.png │ ├── potatoes.png │ ├── pretzels.png │ ├── pumpkin.png │ ├── raspberry.png │ ├── razor.png │ ├── rice.png │ ├── rubbish_sacks.png │ ├── salami.png │ ├── salt.png │ ├── sauce.png │ ├── sausages.png │ ├── sheep.png │ ├── shopping_bag.png │ ├── shrimp.png │ ├── smoothie.png │ ├── snacks.png │ ├── soap_bar.png │ ├── spices.png │ ├── sponge.png │ ├── strawberry.png │ ├── sugar_cubes.png │ ├── sweet_corn.png │ ├── sweets.png │ ├── tangerines.png │ ├── tea.png │ ├── toast.png │ ├── toilet_paper.png │ ├── tomato_paste.png │ ├── tomatoes.png │ ├── toothbrush.png │ ├── tube.png │ ├── vegetables.png │ ├── walnuts.png │ ├── washing_liquid.png │ ├── watermelon.png │ ├── wine.png │ └── zucchini.png └── product │ ├── af_ZA │ └── default_products.json │ ├── ar_SA │ └── default_products.json │ ├── ca_ES │ └── default_products.json │ ├── cs_CZ │ └── default_products.json │ ├── da_DK │ └── default_products.json │ ├── de_DE │ └── default_products.json │ ├── default_products.json │ ├── default_products_change_list.json │ ├── default_products_en.json │ ├── el_GR │ └── default_products.json │ ├── es_ES │ └── default_products.json │ ├── fi_FI │ └── default_products.json │ ├── fr_FR │ └── default_products.json │ ├── he_IL │ └── default_products.json │ ├── hu_HU │ └── default_products.json │ ├── it_IT │ └── default_products.json │ ├── ja_JP │ └── default_products.json │ ├── ko_KR │ └── default_products.json │ ├── nl_NL │ └── default_products.json │ ├── no_NO │ └── default_products.json │ ├── pl_PL │ └── default_products.json │ ├── pt_BR │ └── default_products.json │ ├── pt_PT │ └── default_products.json │ ├── ro_RO │ └── default_products.json │ ├── ru_RU │ └── default_products.json │ ├── sr_SP │ └── default_products.json │ ├── sv_SE │ └── default_products.json │ ├── tr_TR │ └── default_products.json │ ├── uk_UA │ └── default_products.json │ ├── vi_VN │ └── default_products.json │ ├── zh_CN │ └── default_products.json │ └── zh_TW │ └── default_products.json ├── baselineProfile ├── .gitignore ├── build.gradle.kts └── src │ └── main │ ├── AndroidManifest.xml │ └── java │ └── com │ └── rendox │ └── grocerygenius │ ├── BaselineProfileGenerator.kt │ ├── BenchmarkActions.kt │ ├── BenchmarkUtils.kt │ ├── GroceryGeniusBenchmark.kt │ ├── StartupBaselineProfile.kt │ └── StartupBenchmarks.kt ├── build.gradle.kts ├── crowdin.yml ├── gradle.properties ├── gradle ├── libs.versions.toml └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── images ├── app_logo.svg ├── app_logo_rounded_corners.svg ├── banners │ ├── banner_github.png │ ├── banner_google_play.png │ └── banner_izzy_on_droid.png └── readme │ ├── feature_customization.png │ ├── feature_search_groceries.png │ ├── feature_separate_lists.png │ └── readme_cover_image.png ├── lint.xml └── settings.gradle.kts /.editorconfig: -------------------------------------------------------------------------------- 1 | # kotlin rules 2 | [*.{kt,kts}] 3 | indent_size = 4 4 | indent_style = space 5 | insert_final_newline = false 6 | max_line_length = 120 7 | 8 | ij_kotlin_allow_trailing_comma = false 9 | ij_kotlin_allow_trailing_comma_on_call_site = false 10 | ij_kotlin_imports_layout = * 11 | ij_kotlin_packages_to_use_import_on_demand = java.util.* 12 | ij_kotlin_name_count_to_use_star_import = 2147483647 13 | ij_kotlin_name_count_to_use_star_import_for_members = 2147483647 14 | 15 | # ktlint rules 16 | ktlint_code_style = android_studio 17 | 18 | # disable ktlint_standard_function-naming rule for @Composable functions 19 | ktlint_function_naming_ignore_when_annotated_with = Composable 20 | ktlint_function_signature_rule_force_multiline_when_parameter_count_greater_or_equal_than = 2 21 | ktlint_class_signature_rule_force_multiline_when_parameter_count_greater_or_equal_than = 1 -------------------------------------------------------------------------------- /.github/workflows/android-lint.yml: -------------------------------------------------------------------------------- 1 | name: android-lint 2 | 3 | on: 4 | push: 5 | branches: [ develop, production ] 6 | pull_request: 7 | branches: [ develop, production ] 8 | 9 | workflow_dispatch: 10 | 11 | jobs: 12 | android-lint: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v4 18 | 19 | - name: Restore cache 20 | uses: actions/cache@v4 21 | with: 22 | path: | 23 | ~/.gradle/caches 24 | ~/.gradle/wrapper 25 | key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} 26 | restore-keys: | 27 | ${{ runner.os }}-gradle- 28 | 29 | - name: Setup Java 30 | uses: actions/setup-java@v4 31 | with: 32 | distribution: zulu 33 | java-version: 17 34 | 35 | - name: Ensure gradlew is executable 36 | run: chmod +x ./gradlew 37 | 38 | - name: Debug lint 39 | run: ./gradlew lintDebug 40 | 41 | - name: Upload lint report 42 | uses: actions/upload-artifact@v4 43 | with: 44 | name: lint-reports 45 | path: '**/build/reports/lint-results-debug.html' -------------------------------------------------------------------------------- /.github/workflows/build-and-test.yml: -------------------------------------------------------------------------------- 1 | name: build-and-test 2 | 3 | on: 4 | push: 5 | branches: [ develop, production ] 6 | pull_request: 7 | branches: [ develop, production ] 8 | workflow_dispatch: 9 | 10 | jobs: 11 | build-and-test: 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: Checkout 16 | uses: actions/checkout@v4 17 | 18 | - name: Restore cache 19 | uses: actions/cache@v4 20 | with: 21 | path: | 22 | ~/.gradle/caches 23 | ~/.gradle/wrapper 24 | key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} 25 | restore-keys: | 26 | ${{ runner.os }}-gradle- 27 | 28 | - name: Setup Java 29 | uses: actions/setup-java@v4 30 | with: 31 | distribution: zulu 32 | java-version: 17 33 | 34 | - name: Ensure gradlew is executable 35 | run: chmod +x ./gradlew 36 | 37 | - name: Debug build 38 | run: ./gradlew assembleDebug --stacktrace 39 | 40 | - name: Unit test (all modules) 41 | run: ./gradlew test --stacktrace 42 | 43 | - name: Upload test reports 44 | uses: actions/upload-artifact@v4 45 | with: 46 | name: test-reports 47 | path: '**/build/reports/tests/' 48 | -------------------------------------------------------------------------------- /.github/workflows/ktlint-check.yml: -------------------------------------------------------------------------------- 1 | name: ktlint-check 2 | 3 | on: 4 | push: 5 | branches: [ develop, production ] 6 | pull_request: 7 | branches: [ develop, production ] 8 | 9 | workflow_dispatch: 10 | 11 | jobs: 12 | ktlint-check: 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - name: Checkout 17 | uses: actions/checkout@v4 18 | 19 | - name: Restore cache 20 | uses: actions/cache@v4 21 | with: 22 | path: | 23 | ~/.gradle/caches 24 | ~/.gradle/wrapper 25 | key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} 26 | restore-keys: | 27 | ${{ runner.os }}-gradle- 28 | 29 | - name: Setup Java 30 | uses: actions/setup-java@v4 31 | with: 32 | distribution: zulu 33 | java-version: 17 34 | 35 | - name: Ensure gradlew is executable 36 | run: chmod +x ./gradlew 37 | 38 | - name: Run Ktlint Check 39 | run: ./gradlew ktlintCheck 40 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea 5 | .DS_Store 6 | /build 7 | /captures 8 | .externalNativeBuild 9 | .cxx 10 | local.properties 11 | -------------------------------------------------------------------------------- /PRIVACY_POLICY.md: -------------------------------------------------------------------------------- 1 | # Privacy Policy 2 | 3 | All data provided to Grocery Genius is stored locally on your device. Your data is never uploaded to any external servers, and the developers of Grocery Genius do not have access to it. Your data is not shared with any third parties. Grocery Genius does not include any advertisement libraries or third-party tracking (analytics) code. No registration is required, and the app does not request your name, email, phone number, or any other personal information. 4 | 5 | Grocery Genius uses an internet connection solely to fetch and regularly update the grocery database. This database is downloaded directly from this GitHub repository and is located in the root assets folder of the project. 6 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /app/benchmark-rules.pro: -------------------------------------------------------------------------------- 1 | -dontobfuscate -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | 23 | -keepclassmembers class * extends androidx.datastore.preferences.protobuf.GeneratedMessageLite { 24 | ; 25 | } 26 | 27 | # With R8 full mode generic signatures are stripped for classes that are not 28 | # kept. Suspend functions are wrapped in continuations where the type argument 29 | # is used. 30 | -keep,allowobfuscation,allowshrinking class kotlin.coroutines.Continuation 31 | 32 | # R8 full mode strips generic signatures from return types if not kept. 33 | -if interface * { @retrofit2.http.* public *** *(...); } 34 | -keep,allowoptimization,allowshrinking,allowobfuscation class <3> 35 | 36 | # With R8 full mode generic signatures are stripped for classes that are not kept. 37 | -keep,allowobfuscation,allowshrinking class retrofit2.Response -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 18 | 21 | 22 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/GroceryGeniusApp.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius 2 | 3 | import android.app.Application 4 | import com.rendox.grocerygenius.sync.work.initializers.Sync 5 | import dagger.hilt.android.HiltAndroidApp 6 | 7 | @HiltAndroidApp 8 | class GroceryGeniusApp : Application() { 9 | override fun onCreate() { 10 | super.onCreate() 11 | // Initialize Sync; the system responsible for keeping data in the app up to date. 12 | Sync.initialize(context = this) 13 | } 14 | } -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/MainActivityUiState.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius 2 | 3 | import com.rendox.grocerygenius.feature.dashboardscreen.GROCERY_LISTS_DASHBOARD_ROUTE 4 | import com.rendox.grocerygenius.model.DEFAULT_USER_PREFERENCES 5 | import com.rendox.grocerygenius.model.DarkThemeConfig 6 | import com.rendox.grocerygenius.model.GroceryGeniusColorScheme 7 | 8 | data class MainActivityUiState( 9 | val defaultListId: String? = null, 10 | val startDestinationRoute: String = GROCERY_LISTS_DASHBOARD_ROUTE, 11 | val darkThemeConfig: DarkThemeConfig = DEFAULT_USER_PREFERENCES.darkThemeConfig, 12 | val useSystemAccentColor: Boolean = DEFAULT_USER_PREFERENCES.useSystemAccentColor, 13 | val selectedTheme: GroceryGeniusColorScheme = DEFAULT_USER_PREFERENCES.selectedTheme 14 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/data/category/CategoryRepository.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.data.category 2 | 3 | import com.rendox.grocerygenius.data.Syncable 4 | import com.rendox.grocerygenius.model.Category 5 | import kotlinx.coroutines.flow.Flow 6 | 7 | interface CategoryRepository : Syncable { 8 | fun getAllCategories(): Flow> 9 | fun getCategoryById(id: String): Flow 10 | suspend fun updateCategories(categories: List) 11 | } -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/data/grocery/GroceryRepository.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.data.grocery 2 | 3 | import com.rendox.grocerygenius.model.Grocery 4 | import java.util.UUID 5 | import kotlinx.coroutines.flow.Flow 6 | 7 | interface GroceryRepository { 8 | 9 | suspend fun addGroceryToList( 10 | productId: String, 11 | listId: String, 12 | description: String? = null, 13 | purchased: Boolean, 14 | purchasedLastModified: Long = System.currentTimeMillis() 15 | ) 16 | 17 | suspend fun insertProductAndGrocery( 18 | name: String, 19 | productId: String = UUID.randomUUID().toString(), 20 | iconId: String? = null, 21 | categoryId: String?, 22 | groceryListId: String, 23 | description: String?, 24 | purchased: Boolean = false, 25 | purchasedLastModified: Long = System.currentTimeMillis(), 26 | isDefault: Boolean = false 27 | ) 28 | 29 | fun getGroceriesFromList(listId: String): Flow> 30 | fun getGroceryById( 31 | productId: String, 32 | listId: String 33 | ): Flow 34 | 35 | suspend fun updatePurchased( 36 | productId: String, 37 | listId: String, 38 | purchased: Boolean, 39 | purchasedLastModified: Long = System.currentTimeMillis() 40 | ) 41 | 42 | suspend fun updateDescription( 43 | productId: String, 44 | listId: String, 45 | description: String? 46 | ) 47 | suspend fun removeGroceryFromList( 48 | productId: String, 49 | listId: String 50 | ) 51 | } -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/data/grocerylist/GroceryListRepository.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.data.grocerylist 2 | 3 | import com.rendox.grocerygenius.model.GroceryList 4 | import kotlinx.coroutines.flow.Flow 5 | 6 | interface GroceryListRepository { 7 | suspend fun insertGroceryList(groceryList: GroceryList) 8 | fun getGroceryListById(id: String): Flow 9 | fun getAllGroceryLists(): Flow> 10 | suspend fun updateGroceryListName( 11 | listId: String, 12 | name: String 13 | ) 14 | suspend fun deleteGroceryListById(groceryListId: String) 15 | suspend fun upsertGroceryLists(groceryLists: List) 16 | suspend fun updateGroceryLists(groceryLists: List) 17 | } -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/data/grocerylist/GroceryListRepositoryImpl.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.data.grocerylist 2 | 3 | import com.rendox.grocerygenius.data.model.asEntity 4 | import com.rendox.grocerygenius.database.grocerylist.GroceryListDao 5 | import com.rendox.grocerygenius.model.GroceryList 6 | import javax.inject.Inject 7 | import kotlinx.coroutines.flow.Flow 8 | 9 | class GroceryListRepositoryImpl @Inject constructor( 10 | private val groceryListDao: GroceryListDao 11 | ) : GroceryListRepository { 12 | override suspend fun insertGroceryList(groceryList: GroceryList) { 13 | groceryListDao.insertGroceryList(groceryList.asEntity()) 14 | } 15 | 16 | override fun getGroceryListById(id: String): Flow { 17 | return groceryListDao.getGroceryListById(id) 18 | } 19 | 20 | override fun getAllGroceryLists(): Flow> { 21 | return groceryListDao.getAllGroceryLists() 22 | } 23 | 24 | override suspend fun updateGroceryListName( 25 | listId: String, 26 | name: String 27 | ) { 28 | groceryListDao.updateGroceryListName(listId, name) 29 | } 30 | 31 | override suspend fun deleteGroceryListById(groceryListId: String) { 32 | groceryListDao.deleteGroceryListById(groceryListId) 33 | } 34 | 35 | override suspend fun upsertGroceryLists(groceryLists: List) { 36 | groceryListDao.upsertGroceryLists(groceryLists.map { it.asEntity() }) 37 | } 38 | 39 | override suspend fun updateGroceryLists(groceryLists: List) { 40 | groceryListDao.updateGroceryLists(groceryLists.map { it.asEntity() }) 41 | } 42 | } -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/data/icons/IconRepository.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.data.icons 2 | 3 | import com.rendox.grocerygenius.data.Syncable 4 | import com.rendox.grocerygenius.model.Category 5 | import com.rendox.grocerygenius.model.IconReference 6 | import kotlinx.coroutines.flow.Flow 7 | 8 | interface IconRepository : Syncable { 9 | fun getIconsGroupedByCategory(): Flow>> 10 | suspend fun getGroceryIconsByName(name: String): List 11 | } -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/data/model/Category.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.data.model 2 | 3 | import com.rendox.grocerygenius.database.category.CategoryEntity 4 | import com.rendox.grocerygenius.model.Category 5 | import com.rendox.grocerygenius.network.model.CategoryNetwork 6 | 7 | fun CategoryEntity.asExternalModel() = Category( 8 | id = id, 9 | name = name, 10 | sortingPriority = sortingPriority, 11 | defaultSortingPriority = defaultSortingPriority 12 | ) 13 | 14 | fun Category.asEntity() = CategoryEntity( 15 | id = id, 16 | name = name, 17 | sortingPriority = sortingPriority, 18 | defaultSortingPriority = defaultSortingPriority 19 | ) 20 | 21 | fun CategoryNetwork.asEntity() = CategoryEntity( 22 | id = id, 23 | name = name, 24 | sortingPriority = sortingPriority, 25 | defaultSortingPriority = sortingPriority 26 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/data/model/Grocery.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.data.model 2 | 3 | import com.rendox.grocerygenius.database.grocery.CombinedGrocery 4 | import com.rendox.grocerygenius.model.Category 5 | import com.rendox.grocerygenius.model.Grocery 6 | import com.rendox.grocerygenius.model.IconReference 7 | 8 | fun CombinedGrocery.asExternalModel() = Grocery( 9 | productId = productId, 10 | name = name, 11 | purchased = purchased, 12 | description = description, 13 | icon = icon, 14 | category = category, 15 | purchasedLastModified = purchasedLastModified, 16 | productIsDefault = productIsDefault 17 | ) 18 | 19 | val CombinedGrocery.icon 20 | get() = when { 21 | iconId != null && iconFilePath != null -> IconReference( 22 | uniqueFileName = iconId, 23 | filePath = iconFilePath, 24 | name = this.name 25 | ) 26 | 27 | else -> null 28 | } 29 | 30 | val CombinedGrocery.category 31 | get() = when { 32 | categoryId != null && 33 | categoryName != null && 34 | categorySortingPriority != null -> Category( 35 | id = categoryId, 36 | name = categoryName, 37 | sortingPriority = categorySortingPriority 38 | ) 39 | 40 | else -> null 41 | } -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/data/model/GroceryList.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.data.model 2 | 3 | import com.rendox.grocerygenius.database.grocerylist.GroceryListEntity 4 | import com.rendox.grocerygenius.model.GroceryList 5 | 6 | fun GroceryList.asEntity() = GroceryListEntity( 7 | id = id, 8 | name = name, 9 | sortingPriority = sortingPriority 10 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/data/model/Icon.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.data.model 2 | 3 | import com.rendox.grocerygenius.database.groceryicon.IconEntity 4 | import com.rendox.grocerygenius.model.IconReference 5 | 6 | fun IconReference.asEntity() = IconEntity( 7 | uniqueFileName = uniqueFileName, 8 | filePath = filePath 9 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/data/model/Product.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.data.model 2 | 3 | import com.rendox.grocerygenius.database.product.CombinedProduct 4 | import com.rendox.grocerygenius.database.product.ProductEntity 5 | import com.rendox.grocerygenius.model.Category 6 | import com.rendox.grocerygenius.model.IconReference 7 | import com.rendox.grocerygenius.model.Product 8 | import com.rendox.grocerygenius.network.model.ProductNetwork 9 | 10 | fun Product.asEntity() = ProductEntity( 11 | id = id, 12 | name = name, 13 | categoryId = category?.id, 14 | iconFileName = icon?.uniqueFileName, 15 | isDefault = isDefault 16 | ) 17 | 18 | fun CombinedProduct.asExternalModel() = Product( 19 | id = id, 20 | name = name, 21 | icon = icon, 22 | category = category, 23 | isDefault = isDefault 24 | ) 25 | 26 | val CombinedProduct.icon 27 | get() = when { 28 | iconId != null && iconFilePath != null -> IconReference( 29 | uniqueFileName = iconId, 30 | filePath = iconFilePath, 31 | name = this.name 32 | ) 33 | 34 | else -> null 35 | } 36 | 37 | val CombinedProduct.category 38 | get() = when { 39 | categoryId != null && 40 | categoryName != null && 41 | categorySortingPriority != null -> Category( 42 | id = categoryId, 43 | name = categoryName, 44 | sortingPriority = categorySortingPriority 45 | ) 46 | 47 | else -> null 48 | } 49 | 50 | fun ProductNetwork.asEntity() = ProductEntity( 51 | id = id, 52 | name = name, 53 | categoryId = categoryId, 54 | iconFileName = iconId, 55 | isDefault = isDefault 56 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/data/product/ProductRepository.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.data.product 2 | 3 | import com.rendox.grocerygenius.data.Syncable 4 | import com.rendox.grocerygenius.model.Product 5 | import kotlinx.coroutines.flow.Flow 6 | 7 | interface ProductRepository : Syncable { 8 | suspend fun insertProduct(product: Product) 9 | fun getProductById(productId: String): Flow 10 | fun getProductsByCategory(categoryId: String?): Flow> 11 | suspend fun getProductsByName(name: String): List 12 | suspend fun getProductsByKeywords(keywords: List): List 13 | suspend fun updateProductCategory( 14 | productId: String, 15 | categoryId: String? 16 | ) 17 | suspend fun updateProductIcon( 18 | productId: String, 19 | iconId: String? 20 | ) 21 | suspend fun deleteProductById(productId: String) 22 | } -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/data/userpreferences/UserPreferencesRepository.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.data.userpreferences 2 | 3 | import com.rendox.grocerygenius.model.DarkThemeConfig 4 | import com.rendox.grocerygenius.model.GroceryGeniusColorScheme 5 | import com.rendox.grocerygenius.model.UserPreferences 6 | import kotlinx.coroutines.flow.Flow 7 | 8 | interface UserPreferencesRepository { 9 | 10 | val userPreferencesFlow: Flow 11 | suspend fun updateDefaultListId(listId: String?) 12 | suspend fun updateLastOpenedListId(listId: String) 13 | suspend fun updateDarkThemeConfig(darkThemeConfig: DarkThemeConfig) 14 | suspend fun updateUseSystemAccentColor(useSystemAccentColor: Boolean) 15 | suspend fun updateOpenLastViewedList(openLastViewedList: Boolean) 16 | suspend fun updateSelectedTheme(selectedTheme: GroceryGeniusColorScheme) 17 | suspend fun getGroceryListIdToOpenOnStartup(): String? 18 | } -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/data/util/NetworkMonitor.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.rendox.grocerygenius.data.util 18 | 19 | import kotlinx.coroutines.flow.Flow 20 | 21 | /** 22 | * Utility for reporting app connectivity status 23 | */ 24 | interface NetworkMonitor { 25 | val isOnline: Flow 26 | } -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/data/util/SyncManager.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.rendox.grocerygenius.data.util 18 | 19 | import com.rendox.grocerygenius.sync.work.status.SyncStatus 20 | import kotlinx.coroutines.flow.Flow 21 | 22 | /** 23 | * Reports on if synchronization is in progress 24 | */ 25 | interface SyncManager { 26 | val syncStatus: Flow 27 | fun startOrRetrySync() 28 | } -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/database/AppDatabase.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.database 2 | 3 | import androidx.room.Database 4 | import androidx.room.RoomDatabase 5 | import com.rendox.grocerygenius.database.category.CategoryDao 6 | import com.rendox.grocerygenius.database.category.CategoryEntity 7 | import com.rendox.grocerygenius.database.grocery.GroceryDao 8 | import com.rendox.grocerygenius.database.grocery.GroceryEntity 9 | import com.rendox.grocerygenius.database.groceryicon.IconDao 10 | import com.rendox.grocerygenius.database.groceryicon.IconEntity 11 | import com.rendox.grocerygenius.database.grocerylist.GroceryListDao 12 | import com.rendox.grocerygenius.database.grocerylist.GroceryListEntity 13 | import com.rendox.grocerygenius.database.product.ProductDao 14 | import com.rendox.grocerygenius.database.product.ProductEntity 15 | 16 | @Database( 17 | entities = [ 18 | CategoryEntity::class, 19 | GroceryEntity::class, 20 | GroceryListEntity::class, 21 | ProductEntity::class, 22 | IconEntity::class 23 | ], 24 | version = 1 25 | ) 26 | abstract class AppDatabase : RoomDatabase() { 27 | abstract fun categoryDao(): CategoryDao 28 | abstract fun groceryDao(): GroceryDao 29 | abstract fun groceryListDao(): GroceryListDao 30 | abstract fun productDao(): ProductDao 31 | abstract fun iconDao(): IconDao 32 | } -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/database/DaosModule.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.database 2 | 3 | import com.rendox.grocerygenius.database.category.CategoryDao 4 | import com.rendox.grocerygenius.database.grocery.GroceryDao 5 | import com.rendox.grocerygenius.database.groceryicon.IconDao 6 | import com.rendox.grocerygenius.database.grocerylist.GroceryListDao 7 | import com.rendox.grocerygenius.database.product.ProductDao 8 | import dagger.Module 9 | import dagger.Provides 10 | import dagger.hilt.InstallIn 11 | import dagger.hilt.components.SingletonComponent 12 | 13 | @Module 14 | @InstallIn(SingletonComponent::class) 15 | object DaosModule { 16 | @Provides 17 | fun providesCategoryDao(appDatabase: AppDatabase): CategoryDao = appDatabase.categoryDao() 18 | 19 | @Provides 20 | fun providesGroceryDao(appDatabase: AppDatabase): GroceryDao = appDatabase.groceryDao() 21 | 22 | @Provides 23 | fun providesGroceryListDao(appDatabase: AppDatabase): GroceryListDao = appDatabase.groceryListDao() 24 | 25 | @Provides 26 | fun providesProductDao(appDatabase: AppDatabase): ProductDao = appDatabase.productDao() 27 | 28 | @Provides 29 | fun providesIconDao(appDatabase: AppDatabase): IconDao = appDatabase.iconDao() 30 | } -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/database/DatabaseModule.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.database 2 | 3 | import android.content.Context 4 | import androidx.room.Room 5 | import dagger.Module 6 | import dagger.Provides 7 | import dagger.hilt.InstallIn 8 | import dagger.hilt.android.qualifiers.ApplicationContext 9 | import dagger.hilt.components.SingletonComponent 10 | import javax.inject.Singleton 11 | 12 | @Module 13 | @InstallIn(SingletonComponent::class) 14 | object DatabaseModule { 15 | @Provides 16 | @Singleton 17 | fun providesDatabase(@ApplicationContext context: Context): AppDatabase = Room.databaseBuilder( 18 | context, 19 | AppDatabase::class.java, 20 | "grocery-genius-database" 21 | ).build() 22 | } -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/database/category/CategoryDao.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.database.category 2 | 3 | import androidx.room.Dao 4 | import androidx.room.Insert 5 | import androidx.room.Query 6 | import androidx.room.Update 7 | import androidx.room.Upsert 8 | import kotlinx.coroutines.flow.Flow 9 | 10 | @Dao 11 | interface CategoryDao { 12 | @Insert 13 | suspend fun insertCategories(categories: List) 14 | 15 | @Upsert 16 | suspend fun upsertCategories(categories: List) 17 | 18 | @Query("SELECT * FROM CategoryEntity") 19 | fun getAllCategories(): Flow> 20 | 21 | @Query("SELECT * FROM CategoryEntity WHERE id = :id") 22 | fun getCategoryById(id: String): Flow 23 | 24 | @Update 25 | suspend fun updateCategories(categories: List) 26 | 27 | @Query( 28 | """ 29 | DELETE FROM CategoryEntity 30 | WHERE id in (:ids) 31 | """ 32 | ) 33 | suspend fun deleteCategories(ids: List) 34 | } -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/database/category/CategoryEntity.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.database.category 2 | 3 | import androidx.room.Entity 4 | import androidx.room.PrimaryKey 5 | 6 | @Entity 7 | data class CategoryEntity( 8 | @PrimaryKey val id: String, 9 | val name: String, 10 | val defaultSortingPriority: Long, 11 | val sortingPriority: Long 12 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/database/grocery/CombinedGrocery.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.database.grocery 2 | 3 | data class CombinedGrocery( 4 | val productId: String, 5 | val name: String, 6 | val purchased: Boolean, 7 | val description: String?, 8 | val iconId: String?, 9 | val iconFilePath: String?, 10 | val categoryId: String?, 11 | val categoryName: String?, 12 | val categorySortingPriority: Long?, 13 | val purchasedLastModified: Long, 14 | val productIsDefault: Boolean 15 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/database/grocery/GroceryEntity.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.database.grocery 2 | 3 | import androidx.room.Entity 4 | import androidx.room.ForeignKey 5 | import androidx.room.Index 6 | import com.rendox.grocerygenius.database.grocerylist.GroceryListEntity 7 | import com.rendox.grocerygenius.database.product.ProductEntity 8 | 9 | @Entity( 10 | foreignKeys = [ 11 | ForeignKey( 12 | entity = ProductEntity::class, 13 | parentColumns = ["id"], 14 | childColumns = ["productId"], 15 | onDelete = ForeignKey.CASCADE, 16 | onUpdate = ForeignKey.CASCADE 17 | ), 18 | ForeignKey( 19 | entity = GroceryListEntity::class, 20 | parentColumns = ["id"], 21 | childColumns = ["groceryListId"], 22 | onDelete = ForeignKey.CASCADE, 23 | onUpdate = ForeignKey.CASCADE 24 | ) 25 | ], 26 | primaryKeys = ["productId", "groceryListId"], 27 | indices = [ 28 | Index(value = ["productId"]), 29 | Index(value = ["groceryListId"]) 30 | ] 31 | ) 32 | data class GroceryEntity( 33 | val productId: String, 34 | val groceryListId: String, 35 | val description: String?, 36 | val purchased: Boolean, 37 | val purchasedLastModified: Long 38 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/database/groceryicon/IconDao.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.database.groceryicon 2 | 3 | import androidx.room.Dao 4 | import androidx.room.Insert 5 | import androidx.room.Query 6 | import androidx.room.Upsert 7 | import com.rendox.grocerygenius.model.Category 8 | import com.rendox.grocerygenius.model.IconReference 9 | import kotlinx.coroutines.flow.Flow 10 | 11 | @Dao 12 | abstract class IconDao { 13 | @Insert 14 | abstract suspend fun insertGroceryIcons(groceryIconEntities: List) 15 | 16 | @Upsert 17 | abstract suspend fun upsertGroceryIcons(groceryIconEntities: List) 18 | 19 | @Query( 20 | """ 21 | SELECT 22 | c.id, 23 | c.name, 24 | c.sortingPriority, 25 | c.defaultSortingPriority, 26 | i.uniqueFileName, 27 | i.filePath, 28 | p.name 29 | FROM IconEntity i 30 | INNER JOIN ProductEntity p ON i.uniqueFileName = p.iconFileName 31 | INNER JOIN CategoryEntity c ON p.categoryId = c.id 32 | WHERE p.isDefault IS 1 33 | GROUP BY i.uniqueFileName 34 | """ 35 | ) 36 | abstract fun getIconsGroupedByCategory(): Flow>> 37 | 38 | @Query( 39 | """ 40 | SELECT 41 | i.uniqueFileName, 42 | i.filePath, 43 | p.name 44 | FROM IconEntity i 45 | LEFT JOIN ProductEntity p ON i.uniqueFileName = p.iconFileName 46 | WHERE LOWER(p.name) LIKE LOWER(:name) 47 | GROUP BY i.uniqueFileName 48 | """ 49 | ) 50 | abstract suspend fun getGroceryIconsByName(name: String): List 51 | 52 | @Query( 53 | """ 54 | DELETE FROM IconEntity 55 | WHERE uniqueFileName in (:ids) 56 | """ 57 | ) 58 | abstract suspend fun deleteIcons(ids: List) 59 | } -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/database/groceryicon/IconEntity.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.database.groceryicon 2 | 3 | import androidx.room.Entity 4 | import androidx.room.Index 5 | import androidx.room.PrimaryKey 6 | 7 | @Entity(indices = [Index(value = ["filePath"], unique = true)]) 8 | data class IconEntity( 9 | @PrimaryKey val uniqueFileName: String, 10 | val filePath: String 11 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/database/grocerylist/GroceryListDao.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.database.grocerylist 2 | 3 | import androidx.room.Dao 4 | import androidx.room.Insert 5 | import androidx.room.Query 6 | import androidx.room.Update 7 | import androidx.room.Upsert 8 | import com.rendox.grocerygenius.model.GroceryList 9 | import kotlinx.coroutines.flow.Flow 10 | 11 | @Dao 12 | interface GroceryListDao { 13 | @Upsert 14 | suspend fun upsertGroceryLists(groceryLists: List) 15 | 16 | @Insert 17 | suspend fun insertGroceryList(groceryList: GroceryListEntity) 18 | 19 | @Query( 20 | """ 21 | SELECT 22 | groceryList.id, 23 | groceryList.name, 24 | groceryList.sortingPriority, 25 | COUNT(grocery.productId) as numOfGroceries 26 | FROM GroceryListEntity groceryList 27 | LEFT JOIN GroceryEntity grocery ON grocery.groceryListId = groceryList.id AND grocery.purchased = 0 28 | WHERE groceryList.id = :id 29 | GROUP BY groceryList.id HAVING groceryList.id IS NOT NULL 30 | """ 31 | ) 32 | fun getGroceryListById(id: String): Flow 33 | 34 | @Query( 35 | """ 36 | SELECT 37 | groceryList.id, 38 | groceryList.name, 39 | groceryList.sortingPriority, 40 | COUNT(grocery.productId) as numOfGroceries 41 | FROM GroceryListEntity groceryList 42 | LEFT JOIN GroceryEntity grocery ON grocery.groceryListId = groceryList.id AND grocery.purchased = 0 43 | GROUP BY groceryList.id HAVING groceryList.id IS NOT NULL 44 | """ 45 | ) 46 | fun getAllGroceryLists(): Flow> 47 | 48 | @Query("UPDATE GroceryListEntity SET name = :name WHERE id = :listId") 49 | suspend fun updateGroceryListName( 50 | listId: String, 51 | name: String 52 | ) 53 | 54 | @Update 55 | suspend fun updateGroceryLists(groceryLists: List) 56 | 57 | @Query("DELETE FROM GroceryListEntity WHERE id = :groceryListId") 58 | suspend fun deleteGroceryListById(groceryListId: String) 59 | } -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/database/grocerylist/GroceryListEntity.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.database.grocerylist 2 | 3 | import androidx.room.Entity 4 | import androidx.room.PrimaryKey 5 | 6 | @Entity 7 | data class GroceryListEntity( 8 | @PrimaryKey val id: String, 9 | val name: String, 10 | val sortingPriority: Long 11 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/database/product/CombinedProduct.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.database.product 2 | 3 | data class CombinedProduct( 4 | val id: String, 5 | val name: String, 6 | val iconId: String?, 7 | val iconFilePath: String?, 8 | val categoryId: String?, 9 | val categoryName: String?, 10 | val categorySortingPriority: Long?, 11 | val isDefault: Boolean 12 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/database/product/ProductEntity.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.database.product 2 | 3 | import androidx.room.Entity 4 | import androidx.room.ForeignKey 5 | import androidx.room.Index 6 | import androidx.room.PrimaryKey 7 | import com.rendox.grocerygenius.database.category.CategoryEntity 8 | import com.rendox.grocerygenius.database.groceryicon.IconEntity 9 | 10 | @Entity( 11 | foreignKeys = [ 12 | ForeignKey( 13 | entity = IconEntity::class, 14 | parentColumns = ["uniqueFileName"], 15 | childColumns = ["iconFileName"], 16 | onDelete = ForeignKey.SET_NULL, 17 | onUpdate = ForeignKey.CASCADE 18 | ), 19 | ForeignKey( 20 | entity = CategoryEntity::class, 21 | parentColumns = ["id"], 22 | childColumns = ["categoryId"], 23 | onDelete = ForeignKey.SET_NULL, 24 | onUpdate = ForeignKey.CASCADE 25 | ) 26 | ], 27 | indices = [ 28 | Index(value = ["iconFileName"]), 29 | Index(value = ["categoryId"]) 30 | ] 31 | ) 32 | data class ProductEntity( 33 | @PrimaryKey val id: String, 34 | val name: String, 35 | val isDefault: Boolean, 36 | val iconFileName: String?, 37 | val categoryId: String? 38 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/datastore/DataStoreModule.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.datastore 2 | 3 | import android.content.Context 4 | import androidx.datastore.core.DataStore 5 | import androidx.datastore.core.handlers.ReplaceFileCorruptionHandler 6 | import androidx.datastore.preferences.SharedPreferencesMigration 7 | import androidx.datastore.preferences.core.PreferenceDataStoreFactory 8 | import androidx.datastore.preferences.core.Preferences 9 | import androidx.datastore.preferences.core.emptyPreferences 10 | import androidx.datastore.preferences.preferencesDataStoreFile 11 | import com.rendox.grocerygenius.network.di.Dispatcher 12 | import com.rendox.grocerygenius.network.di.GroceryGeniusDispatchers 13 | import dagger.Module 14 | import dagger.Provides 15 | import dagger.hilt.InstallIn 16 | import dagger.hilt.android.qualifiers.ApplicationContext 17 | import dagger.hilt.components.SingletonComponent 18 | import javax.inject.Singleton 19 | import kotlinx.coroutines.CoroutineDispatcher 20 | import kotlinx.coroutines.CoroutineScope 21 | import kotlinx.coroutines.SupervisorJob 22 | 23 | private const val USER_PREFERENCES = "user_preferences" 24 | 25 | @InstallIn(SingletonComponent::class) 26 | @Module 27 | object DataStoreModule { 28 | 29 | @Singleton 30 | @Provides 31 | fun providePreferencesDataStore( 32 | @ApplicationContext appContext: Context, 33 | @Dispatcher(GroceryGeniusDispatchers.IO) ioDispatcher: CoroutineDispatcher 34 | ): DataStore { 35 | return PreferenceDataStoreFactory.create( 36 | corruptionHandler = ReplaceFileCorruptionHandler( 37 | produceNewData = { emptyPreferences() } 38 | ), 39 | migrations = listOf(SharedPreferencesMigration(appContext, USER_PREFERENCES)), 40 | scope = CoroutineScope(ioDispatcher + SupervisorJob()), 41 | produceFile = { appContext.preferencesDataStoreFile(USER_PREFERENCES) } 42 | ) 43 | } 44 | } -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/feature/addgrocery/AddGroceryBottomSheetContentType.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.feature.addgrocery 2 | 3 | enum class AddGroceryBottomSheetContentType { 4 | Suggestions, 5 | SearchResults, 6 | RefineItemOptions 7 | } -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/feature/addgrocery/AddGroceryUiIntent.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.feature.addgrocery 2 | 3 | import com.rendox.grocerygenius.model.Grocery 4 | import com.rendox.grocerygenius.model.Product 5 | 6 | sealed interface AddGroceryUiIntent { 7 | data class OnUpdateSearchQuery(val query: String) : AddGroceryUiIntent 8 | data class OnGrocerySearchResultClick(val grocery: Grocery) : AddGroceryUiIntent 9 | data object OnSearchFieldKeyboardDone : AddGroceryUiIntent 10 | data object OnClearSearchQuery : AddGroceryUiIntent 11 | data object OnAddGroceryBottomSheetCollapsing : AddGroceryUiIntent 12 | data class OnAddGroceryBottomSheetExpanded(val groceryListId: String) : AddGroceryUiIntent 13 | data class OnCustomProductClick(val customProduct: Product) : AddGroceryUiIntent 14 | } -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/feature/addgrocery/AddGroceryUiState.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.feature.addgrocery 2 | 3 | import com.rendox.grocerygenius.model.Grocery 4 | import com.rendox.grocerygenius.model.Product 5 | 6 | data class AddGroceryUiState( 7 | val previouslyAddedGrocery: Grocery? = null, 8 | val bottomSheetContentType: AddGroceryBottomSheetContentType = AddGroceryBottomSheetContentType.Suggestions, 9 | val grocerySearchResults: List = emptyList(), 10 | val customProducts: List = emptyList(), 11 | val clearSearchQueryButtonIsShown: Boolean = false 12 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/feature/dashboardscreen/GroceryListsDashboardNavigation.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.feature.dashboardscreen 2 | 3 | import androidx.compose.animation.EnterTransition 4 | import androidx.compose.animation.ExitTransition 5 | import androidx.compose.animation.slideInHorizontally 6 | import androidx.compose.animation.slideOutHorizontally 7 | import androidx.navigation.NavController 8 | import androidx.navigation.NavGraphBuilder 9 | import androidx.navigation.NavOptionsBuilder 10 | import androidx.navigation.compose.composable 11 | import com.rendox.grocerygenius.feature.grocerylist.GROCERY_LIST_ROUTE 12 | 13 | const val GROCERY_LISTS_DASHBOARD_ROUTE = "grocery_lists_dashboard_route" 14 | 15 | fun NavController.navigateToGroceryListsDashboard(navOptions: (NavOptionsBuilder.() -> Unit) = {}) { 16 | this.navigate( 17 | route = GROCERY_LISTS_DASHBOARD_ROUTE, 18 | builder = navOptions 19 | ) 20 | } 21 | 22 | fun NavGraphBuilder.groceryListsDashboardScreen( 23 | navigateToGroceryListScreen: (String) -> Unit, 24 | navigateToSettingsScreen: () -> Unit 25 | ) { 26 | composable( 27 | route = GROCERY_LISTS_DASHBOARD_ROUTE, 28 | enterTransition = { 29 | when (initialState.destination.route) { 30 | GROCERY_LIST_ROUTE -> slideInHorizontally(initialOffsetX = { -it }) 31 | else -> EnterTransition.None 32 | } 33 | }, 34 | exitTransition = { 35 | when (targetState.destination.route) { 36 | GROCERY_LIST_ROUTE -> slideOutHorizontally(targetOffsetX = { -it }) 37 | else -> ExitTransition.None 38 | } 39 | } 40 | ) { 41 | GroceryListsDashboardRoute( 42 | navigateToGroceryListScreen = navigateToGroceryListScreen, 43 | navigateToSettingsScreen = navigateToSettingsScreen 44 | ) 45 | } 46 | } -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/feature/dashboardscreen/GroceryListsDashboardUiIntent.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.feature.dashboardscreen 2 | 3 | import com.rendox.grocerygenius.model.GroceryList 4 | 5 | sealed interface GroceryListsDashboardUiIntent { 6 | data class OnUpdateGroceryLists(val groceryLists: List) : 7 | GroceryListsDashboardUiIntent 8 | data object OnAdderItemClick : GroceryListsDashboardUiIntent 9 | } -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/feature/dashboardscreen/recyclerview/DashboardDiffUtilCallback.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.feature.dashboardscreen.recyclerview 2 | 3 | import androidx.recyclerview.widget.DiffUtil 4 | import com.rendox.grocerygenius.model.GroceryList 5 | 6 | class DashboardDiffUtilCallback( 7 | private val oldList: List, 8 | private val newList: List 9 | ) : DiffUtil.Callback() { 10 | override fun getOldListSize() = oldList.size 11 | override fun getNewListSize() = newList.size 12 | 13 | override fun areItemsTheSame( 14 | oldItemPosition: Int, 15 | newItemPosition: Int 16 | ) = oldList[oldItemPosition].id == newList[newItemPosition].id 17 | 18 | override fun areContentsTheSame( 19 | oldItemPosition: Int, 20 | newItemPosition: Int 21 | ) = oldList[oldItemPosition] == newList[newItemPosition] 22 | } -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/feature/editgrocery/EditGroceryUiIntent.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.feature.editgrocery 2 | 3 | import androidx.compose.ui.text.input.TextFieldValue 4 | import com.rendox.grocerygenius.model.Category 5 | 6 | sealed interface EditGroceryUiIntent { 7 | data class OnDescriptionChanged(val description: TextFieldValue) : EditGroceryUiIntent 8 | data object OnClearDescription : EditGroceryUiIntent 9 | data class OnCategorySelected(val category: Category) : EditGroceryUiIntent 10 | data object OnCustomCategorySelected : EditGroceryUiIntent 11 | data object OnRemoveGroceryFromList : EditGroceryUiIntent 12 | data object OnDeleteProduct : EditGroceryUiIntent 13 | 14 | data class OnEditOtherGrocery( 15 | val productId: String, 16 | val groceryListId: String 17 | ) : EditGroceryUiIntent 18 | } -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/feature/editgrocery/EditGroceryUiState.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.feature.editgrocery 2 | 3 | import com.rendox.grocerygenius.model.Category 4 | import com.rendox.grocerygenius.model.Grocery 5 | 6 | data class EditGroceryUiState( 7 | val editGrocery: Grocery? = null, 8 | val clearEditGroceryDescriptionButtonIsShown: Boolean = false, 9 | val groceryCategories: List = emptyList() 10 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/feature/editgrocery/dialogs/PickerDialogType.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.feature.editgrocery.dialogs 2 | 3 | enum class PickerDialogType { 4 | None, 5 | CategoryPicker, 6 | IconPicker 7 | } -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/feature/grocerylist/GroceryListPurchaseState.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.feature.grocerylist 2 | 3 | enum class GroceryListPurchaseState { 4 | SHOPPING_DONE, 5 | LIST_IS_EMPTY, 6 | LIST_IS_FULL 7 | } -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/feature/grocerylist/GroceryListsUiIntent.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.feature.grocerylist 2 | 3 | import androidx.compose.ui.text.input.TextFieldValue 4 | import com.rendox.grocerygenius.model.Grocery 5 | 6 | sealed interface GroceryListsUiIntent { 7 | data class OnGroceryItemClick(val item: Grocery) : GroceryListsUiIntent 8 | data class UpdateGroceryListName(val name: TextFieldValue) : GroceryListsUiIntent 9 | data object OnKeyboardHidden : GroceryListsUiIntent 10 | data object OnDeleteGroceryList : GroceryListsUiIntent 11 | data class OnEditGroceryListToggle(val editModeIsEnabled: Boolean) : GroceryListsUiIntent 12 | data class OnNavigateToCategoryScreen(val categoryId: String?) : GroceryListsUiIntent 13 | } -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/feature/iconpicker/IconPickerIntent.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.feature.iconpicker 2 | 3 | import com.rendox.grocerygenius.model.IconReference 4 | 5 | sealed interface IconPickerIntent { 6 | data class OnPickIcon(val iconReference: IconReference) : IconPickerIntent 7 | data class OnUpdateSearchQuery(val query: String) : IconPickerIntent 8 | data object OnClearSearchQuery : IconPickerIntent 9 | data object OnRemoveIcon : IconPickerIntent 10 | } -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/feature/iconpicker/IconPickerNavigation.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.feature.iconpicker 2 | 3 | import androidx.navigation.NavController 4 | import androidx.navigation.NavGraphBuilder 5 | import androidx.navigation.NavOptionsBuilder 6 | import androidx.navigation.NavType 7 | import androidx.navigation.compose.composable 8 | import androidx.navigation.navArgument 9 | import com.rendox.grocerygenius.ui.GroceryGeniusTransition 10 | 11 | const val ICON_PICKER_ROUTE = "icon_picker_route" 12 | const val PRODUCT_ID_ARG = "edit_grocery_id_arg" 13 | const val ICON_PICKER_GROCERY_LIST_ID_ARG = "grocery_list_id_arg" 14 | const val ICON_PICKER_ROUTE_WITH_ARGS = "$ICON_PICKER_ROUTE/{$PRODUCT_ID_ARG}/{$ICON_PICKER_GROCERY_LIST_ID_ARG}" 15 | 16 | fun NavController.navigateToIconPicker( 17 | editProductId: String, 18 | groceryListId: String, 19 | navOptions: (NavOptionsBuilder.() -> Unit) = {} 20 | ) { 21 | this.navigate( 22 | route = "$ICON_PICKER_ROUTE/$editProductId/$groceryListId", 23 | builder = navOptions 24 | ) 25 | } 26 | 27 | fun NavGraphBuilder.iconPickerScreen(navigateBack: () -> Unit) { 28 | composable( 29 | route = ICON_PICKER_ROUTE_WITH_ARGS, 30 | enterTransition = { GroceryGeniusTransition.SlideInVertically }, 31 | exitTransition = { GroceryGeniusTransition.SlideOutVertically }, 32 | arguments = listOf( 33 | navArgument(PRODUCT_ID_ARG) { 34 | type = NavType.StringType 35 | }, 36 | navArgument(ICON_PICKER_GROCERY_LIST_ID_ARG) { 37 | type = NavType.StringType 38 | } 39 | ) 40 | ) { 41 | IconPickerRoute( 42 | navigateBack = navigateBack 43 | ) 44 | } 45 | } -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/feature/iconpicker/IconPickerUiState.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.feature.iconpicker 2 | 3 | import com.rendox.grocerygenius.model.Category 4 | import com.rendox.grocerygenius.model.IconReference 5 | import com.rendox.grocerygenius.model.Product 6 | 7 | data class IconPickerUiState( 8 | val groupedIcons: Map> = emptyMap(), 9 | val searchResults: List = emptyList(), 10 | val product: Product? = null, 11 | val clearSearchQueryButtonIsShown: Boolean = false, 12 | val searchResultsShown: Boolean = false 13 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/feature/onboarding/OnboardingNavigation.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.feature.onboarding 2 | 3 | import androidx.navigation.NavGraphBuilder 4 | import androidx.navigation.compose.composable 5 | import com.rendox.grocerygenius.ui.GroceryGeniusTransition 6 | 7 | const val ONBOARDING_ROUTE = "onboarding_route" 8 | 9 | fun NavGraphBuilder.onboardingScreen(closeOnboarding: () -> Unit) { 10 | composable( 11 | route = ONBOARDING_ROUTE, 12 | enterTransition = { GroceryGeniusTransition.SlideInVertically }, 13 | exitTransition = { GroceryGeniusTransition.SlideOutVertically } 14 | ) { 15 | OnboardingSyncRoute( 16 | closeOnboarding = closeOnboarding 17 | ) 18 | } 19 | } -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/feature/onboarding/OnboardingViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.feature.onboarding 2 | 3 | import androidx.lifecycle.ViewModel 4 | import androidx.lifecycle.viewModelScope 5 | import com.rendox.grocerygenius.data.util.ConnectivityManagerNetworkMonitor 6 | import com.rendox.grocerygenius.data.util.SyncManager 7 | import com.rendox.grocerygenius.sync.work.status.SyncStatus 8 | import dagger.hilt.android.lifecycle.HiltViewModel 9 | import javax.inject.Inject 10 | import kotlinx.coroutines.ExperimentalCoroutinesApi 11 | import kotlinx.coroutines.delay 12 | import kotlinx.coroutines.flow.SharingStarted 13 | import kotlinx.coroutines.flow.combine 14 | import kotlinx.coroutines.flow.flatMapLatest 15 | import kotlinx.coroutines.flow.flow 16 | import kotlinx.coroutines.flow.flowOf 17 | import kotlinx.coroutines.flow.stateIn 18 | 19 | @HiltViewModel 20 | class OnboardingViewModel @Inject constructor( 21 | private val syncManager: SyncManager, 22 | networkMonitor: ConnectivityManagerNetworkMonitor 23 | ) : ViewModel() { 24 | @OptIn(ExperimentalCoroutinesApi::class) 25 | val syncStatusFlow = syncManager.syncStatus 26 | .combine(networkMonitor.isOnline) { syncStatus, isOnline -> 27 | if (!isOnline) SyncStatus.OFFLINE else syncStatus 28 | } 29 | // if the operation takes longer than 10 seconds, 30 | // emit failed to not keep the user waiting 31 | .flatMapLatest { syncStatus -> 32 | if (syncStatus == SyncStatus.RUNNING) { 33 | flow { 34 | emit(syncStatus) 35 | delay(10_000) 36 | emit(SyncStatus.FAILED) 37 | } 38 | } else { 39 | flowOf(syncStatus) 40 | } 41 | } 42 | .stateIn( 43 | scope = viewModelScope, 44 | started = SharingStarted.WhileSubscribed(5000), 45 | initialValue = SyncStatus.RUNNING 46 | ) 47 | 48 | fun onRetrySync() { 49 | syncManager.startOrRetrySync() 50 | } 51 | } -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/feature/settings/SettingsScreenIntent.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.feature.settings 2 | 3 | import com.rendox.grocerygenius.model.Category 4 | import com.rendox.grocerygenius.model.DarkThemeConfig 5 | import com.rendox.grocerygenius.model.GroceryGeniusColorScheme 6 | 7 | sealed interface SettingsScreenIntent { 8 | data class ChangeDarkThemeConfig(val config: DarkThemeConfig) : SettingsScreenIntent 9 | data class ChangeUseSystemAccentColor(val use: Boolean) : SettingsScreenIntent 10 | data class OnChangeDefaultList(val listId: String?) : SettingsScreenIntent 11 | data class ChangeOpenLastViewedListConfig(val openLastViewedList: Boolean) : SettingsScreenIntent 12 | data class ChangeColorScheme(val scheme: GroceryGeniusColorScheme) : SettingsScreenIntent 13 | data class OnUpdateCategories(val categories: List) : SettingsScreenIntent 14 | data object OnResetCategoriesOrder : SettingsScreenIntent 15 | } -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/feature/settings/SettingsScreenNavigation.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.feature.settings 2 | 3 | import androidx.navigation.NavController 4 | import androidx.navigation.NavGraphBuilder 5 | import androidx.navigation.NavOptionsBuilder 6 | import androidx.navigation.compose.composable 7 | import com.rendox.grocerygenius.ui.GroceryGeniusTransition 8 | 9 | const val SETTINGS_ROUTE = "settings_route" 10 | 11 | fun NavController.navigateToSettings(navOptions: (NavOptionsBuilder.() -> Unit) = {}) { 12 | this.navigate( 13 | route = SETTINGS_ROUTE, 14 | builder = navOptions 15 | ) 16 | } 17 | 18 | fun NavGraphBuilder.settingsScreen(navigateBack: () -> Unit) { 19 | composable( 20 | route = SETTINGS_ROUTE, 21 | enterTransition = { GroceryGeniusTransition.SlideInVertically }, 22 | exitTransition = { GroceryGeniusTransition.SlideOutVertically } 23 | ) { 24 | SettingsRoute( 25 | navigateBack = navigateBack 26 | ) 27 | } 28 | } -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/feature/settings/SettingsScreenState.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.feature.settings 2 | 3 | import com.rendox.grocerygenius.model.Category 4 | import com.rendox.grocerygenius.model.DEFAULT_USER_PREFERENCES 5 | import com.rendox.grocerygenius.model.GroceryList 6 | import com.rendox.grocerygenius.model.UserPreferences 7 | 8 | data class SettingsScreenState( 9 | val userPreferences: UserPreferences = DEFAULT_USER_PREFERENCES, 10 | val groceryLists: List = emptyList(), 11 | val categories: List = emptyList(), 12 | val isLoading: Boolean = true 13 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/feature/settings/categories/recyclerview/CategoriesRecyclerViewAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.feature.settings.categories.recyclerview 2 | 3 | import android.view.ViewGroup 4 | import androidx.compose.ui.platform.ComposeView 5 | import androidx.recyclerview.widget.ItemTouchHelper 6 | import androidx.recyclerview.widget.RecyclerView 7 | import com.rendox.grocerygenius.model.Category 8 | import com.rendox.grocerygenius.ui.helpers.DragHandleReorderItemTouchHelperCallback 9 | import java.util.Collections 10 | 11 | class CategoriesRecyclerViewAdapter( 12 | recyclerView: RecyclerView, 13 | var categories: List, 14 | private val updateLists: (List) -> Unit 15 | ) : RecyclerView.Adapter() { 16 | 17 | private val touchHelperCallback = DragHandleReorderItemTouchHelperCallback( 18 | onItemMove = { fromPosition, toPosition -> 19 | onItemMove(fromPosition, toPosition) 20 | }, 21 | onClearView = { updateLists(categories) } 22 | ) 23 | private val touchHelper = ItemTouchHelper(touchHelperCallback) 24 | 25 | init { 26 | touchHelper.attachToRecyclerView(recyclerView) 27 | } 28 | 29 | override fun onCreateViewHolder( 30 | parent: ViewGroup, 31 | viewType: Int 32 | ) = CategoryViewHolder( 33 | composeView = ComposeView(parent.context), 34 | onDrag = { touchHelper.startDrag(it) } 35 | ) 36 | 37 | override fun onBindViewHolder( 38 | holder: CategoryViewHolder, 39 | position: Int 40 | ) { 41 | holder.bind(categories[position]) 42 | } 43 | 44 | override fun getItemCount() = categories.size 45 | 46 | private fun onItemMove( 47 | fromPosition: Int, 48 | toPosition: Int 49 | ) { 50 | categories = this.categories.toMutableList().also { 51 | Collections.swap(it, fromPosition, toPosition) 52 | } 53 | notifyItemMoved(fromPosition, toPosition) 54 | } 55 | } -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/filestorage/JsonAssetDecoder.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.filestorage 2 | 3 | import android.content.Context 4 | import android.util.Log 5 | import com.rendox.grocerygenius.network.di.Dispatcher 6 | import com.rendox.grocerygenius.network.di.GroceryGeniusDispatchers 7 | import com.squareup.moshi.JsonAdapter 8 | import com.squareup.moshi.JsonDataException 9 | import dagger.hilt.android.qualifiers.ApplicationContext 10 | import java.io.IOException 11 | import javax.inject.Inject 12 | import kotlinx.coroutines.CoroutineDispatcher 13 | import kotlinx.coroutines.withContext 14 | 15 | class JsonAssetDecoder @Inject constructor( 16 | @ApplicationContext val appContext: Context, 17 | @Dispatcher(GroceryGeniusDispatchers.IO) val ioDispatcher: CoroutineDispatcher 18 | ) { 19 | suspend fun decodeFromFile( 20 | adapter: JsonAdapter, 21 | fileName: String 22 | ): T? = withContext(ioDispatcher) { 23 | try { 24 | val json = appContext.assets 25 | .open(fileName) 26 | .bufferedReader() 27 | .use { it.readText() } 28 | adapter.fromJson(json) 29 | } catch (e: IOException) { 30 | Log.e("JsonAssetDecoder", "Error reading $fileName: ${e.message}") 31 | null 32 | } catch (e: JsonDataException) { 33 | Log.e("JsonAssetDecoder", "Error parsing $fileName: ${e.message}") 34 | null 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/model/Category.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.model 2 | 3 | data class Category( 4 | val id: String, 5 | val name: String, 6 | val sortingPriority: Long = System.currentTimeMillis(), 7 | val defaultSortingPriority: Long = sortingPriority 8 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/model/ChangeListVersions.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.model 2 | 3 | data class ChangeListVersions( 4 | val iconVersion: Int = -1, 5 | val categoryVersion: Int = -1, 6 | val productVersion: Int = -1 7 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/model/CompoundGroceryId.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.model 2 | 3 | data class CompoundGroceryId( 4 | val productId: String, 5 | val groceryListId: String 6 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/model/DarkThemeConfig.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.model 2 | 3 | enum class DarkThemeConfig { 4 | FOLLOW_SYSTEM, 5 | LIGHT, 6 | DARK 7 | } -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/model/Grocery.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.model 2 | 3 | data class Grocery( 4 | val productId: String, 5 | val name: String, 6 | val purchased: Boolean, 7 | val description: String? = null, 8 | val icon: IconReference? = null, 9 | val category: Category? = null, 10 | val purchasedLastModified: Long = System.currentTimeMillis(), 11 | val productIsDefault: Boolean = false 12 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/model/GroceryGeniusColorScheme.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.model 2 | 3 | enum class GroceryGeniusColorScheme { 4 | BeigeColorScheme, 5 | CyanColorScheme, 6 | GreenColorScheme, 7 | PinkColorScheme, 8 | PurpleColorScheme, 9 | YellowColorScheme 10 | } -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/model/GroceryList.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.model 2 | 3 | import java.util.UUID 4 | 5 | data class GroceryList( 6 | val id: String = UUID.randomUUID().toString(), 7 | val name: String, 8 | val sortingPriority: Long = System.currentTimeMillis(), 9 | val numOfGroceries: Int = 0 10 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/model/IconReference.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.model 2 | 3 | data class IconReference( 4 | val uniqueFileName: String, 5 | val filePath: String, 6 | val name: String? = null 7 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/model/Product.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.model 2 | 3 | import java.util.UUID 4 | 5 | data class Product( 6 | val id: String = UUID.randomUUID().toString(), 7 | val name: String, 8 | val icon: IconReference? = null, 9 | val category: Category? = null, 10 | val isDefault: Boolean = false 11 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/model/UserPreferences.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.model 2 | 3 | data class UserPreferences( 4 | val defaultListId: String? = DEFAULT_USER_PREFERENCES.defaultListId, 5 | val lastOpenedListId: String? = DEFAULT_USER_PREFERENCES.lastOpenedListId, 6 | val darkThemeConfig: DarkThemeConfig = DEFAULT_USER_PREFERENCES.darkThemeConfig, 7 | val useSystemAccentColor: Boolean = DEFAULT_USER_PREFERENCES.useSystemAccentColor, 8 | val openLastViewedList: Boolean = DEFAULT_USER_PREFERENCES.openLastViewedList, 9 | val selectedTheme: GroceryGeniusColorScheme = DEFAULT_USER_PREFERENCES.selectedTheme 10 | ) 11 | 12 | val DEFAULT_USER_PREFERENCES = UserPreferences( 13 | defaultListId = null, 14 | lastOpenedListId = null, 15 | darkThemeConfig = DarkThemeConfig.FOLLOW_SYSTEM, 16 | useSystemAccentColor = true, 17 | openLastViewedList = true, 18 | selectedTheme = GroceryGeniusColorScheme.CyanColorScheme 19 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/network/GitHubApi.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.network 2 | 3 | import com.rendox.grocerygenius.network.model.CategoryNetwork 4 | import com.rendox.grocerygenius.network.model.NetworkChangeList 5 | import com.rendox.grocerygenius.network.model.ProductNetwork 6 | import okhttp3.ResponseBody 7 | import retrofit2.http.GET 8 | import retrofit2.http.Path 9 | import retrofit2.http.Streaming 10 | 11 | interface GitHubApi { 12 | 13 | @GET("category/categories_{languageCode}.json") 14 | suspend fun getCategories(@Path("languageCode") languageCode: String = "en"): List 15 | 16 | @GET("category/categories_change_list.json") 17 | suspend fun getCategoriesChangeList(): List 18 | 19 | @GET("icons/all_icons.zip") 20 | @Streaming 21 | suspend fun getIconsArchive(): ResponseBody 22 | 23 | @GET("icons/{iconName}") 24 | suspend fun getIconByName(@Path("iconName") iconName: String): ResponseBody 25 | 26 | @GET("icons/icons_change_list.json") 27 | suspend fun getIconChangeList(): List 28 | 29 | @GET("product/default_products_{languageCode}.json") 30 | suspend fun getProducts(@Path("languageCode") languageCode: String = "en"): List 31 | 32 | @GET("product/default_products_change_list.json") 33 | suspend fun getProductsChangeList(): List 34 | } -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/network/NetworkUtil.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.network 2 | 3 | import com.squareup.moshi.JsonAdapter 4 | import com.squareup.moshi.Moshi 5 | import com.squareup.moshi.Types 6 | 7 | inline fun Moshi.listAdapter(): JsonAdapter> { 8 | val type = Types.newParameterizedType(List::class.java, T::class.java) 9 | return adapter(type) 10 | } -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/network/data/sources/category/CategoryNetworkDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.network.data.sources.category 2 | 3 | import com.rendox.grocerygenius.network.model.CategoryNetwork 4 | import com.rendox.grocerygenius.network.model.NetworkChangeList 5 | 6 | interface CategoryNetworkDataSource { 7 | suspend fun getAllCategories(): List 8 | suspend fun getCategoriesByIds(ids: List): List 9 | suspend fun getCategoryChangeList(after: Int): List 10 | } -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/network/data/sources/category/OfflineFirstCategoryNetworkDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.network.data.sources.category 2 | 3 | import com.rendox.grocerygenius.network.GitHubApi 4 | import com.rendox.grocerygenius.network.model.CategoryNetwork 5 | import com.rendox.grocerygenius.network.model.NetworkChangeList 6 | import javax.inject.Inject 7 | 8 | class OfflineFirstCategoryNetworkDataSource @Inject constructor( 9 | private val gitHubApi: GitHubApi 10 | ) : CategoryNetworkDataSource { 11 | override suspend fun getAllCategories(): List = gitHubApi.getCategories() 12 | 13 | override suspend fun getCategoriesByIds(ids: List): List = 14 | getAllCategories().filter { it.id in ids } 15 | 16 | override suspend fun getCategoryChangeList(after: Int): List = 17 | gitHubApi.getCategoriesChangeList() 18 | } -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/network/data/sources/icon/IconNetworkDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.network.data.sources.icon 2 | 3 | import com.rendox.grocerygenius.model.IconReference 4 | import com.rendox.grocerygenius.network.model.NetworkChangeList 5 | 6 | interface IconNetworkDataSource { 7 | suspend fun downloadIcons(): List 8 | suspend fun downloadIconsByIds(ids: List): List 9 | suspend fun getIconChangeList(after: Int): List 10 | } -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/network/data/sources/product/OfflineFirstProductNetworkDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.network.data.sources.product 2 | 3 | import com.rendox.grocerygenius.network.GitHubApi 4 | import com.rendox.grocerygenius.network.model.NetworkChangeList 5 | import com.rendox.grocerygenius.network.model.ProductNetwork 6 | import javax.inject.Inject 7 | 8 | class OfflineFirstProductNetworkDataSource @Inject constructor( 9 | private val gitHubApi: GitHubApi 10 | ) : ProductNetworkDataSource { 11 | override suspend fun getAllProducts(): List = gitHubApi.getProducts() 12 | 13 | override suspend fun getProductsByIds(ids: List): List = 14 | gitHubApi.getProducts().filter { it.id in ids } 15 | 16 | override suspend fun getProductChangeList(after: Int): List = 17 | gitHubApi.getProductsChangeList().filter { it.changeListVersion > after } 18 | } -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/network/data/sources/product/ProductNetworkDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.network.data.sources.product 2 | 3 | import com.rendox.grocerygenius.network.model.NetworkChangeList 4 | import com.rendox.grocerygenius.network.model.ProductNetwork 5 | 6 | interface ProductNetworkDataSource { 7 | suspend fun getAllProducts(): List 8 | suspend fun getProductsByIds(ids: List): List 9 | suspend fun getProductChangeList(after: Int): List 10 | } -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/network/di/DispatchersModule.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.network.di 2 | 3 | import dagger.Module 4 | import dagger.Provides 5 | import dagger.hilt.InstallIn 6 | import dagger.hilt.components.SingletonComponent 7 | import kotlinx.coroutines.CoroutineDispatcher 8 | import kotlinx.coroutines.Dispatchers 9 | 10 | @Module 11 | @InstallIn(SingletonComponent::class) 12 | object DispatchersModule { 13 | @Provides 14 | @Dispatcher(GroceryGeniusDispatchers.IO) 15 | fun providesIODispatcher(): CoroutineDispatcher = Dispatchers.IO 16 | 17 | @Provides 18 | @Dispatcher(GroceryGeniusDispatchers.Default) 19 | fun providesDefaultDispatcher(): CoroutineDispatcher = Dispatchers.Default 20 | } -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/network/di/GroceryGeniusDispatchers.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.network.di 2 | 3 | import javax.inject.Qualifier 4 | 5 | @Qualifier 6 | @Retention(AnnotationRetention.RUNTIME) 7 | annotation class Dispatcher(val dispatcher: GroceryGeniusDispatchers) 8 | 9 | enum class GroceryGeniusDispatchers { 10 | Default, 11 | IO 12 | } -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/network/di/NetworkModule.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.network.di 2 | 3 | import com.rendox.grocerygenius.network.data.sources.category.CategoryNetworkDataSource 4 | import com.rendox.grocerygenius.network.data.sources.category.OfflineFirstCategoryNetworkDataSource 5 | import com.rendox.grocerygenius.network.data.sources.icon.IconNetworkDataSource 6 | import com.rendox.grocerygenius.network.data.sources.icon.OfflineFirstIconNetworkDataSource 7 | import com.rendox.grocerygenius.network.data.sources.product.OfflineFirstProductNetworkDataSource 8 | import com.rendox.grocerygenius.network.data.sources.product.ProductNetworkDataSource 9 | import dagger.Binds 10 | import dagger.Module 11 | import dagger.hilt.InstallIn 12 | import dagger.hilt.components.SingletonComponent 13 | import javax.inject.Singleton 14 | 15 | @Module 16 | @InstallIn(SingletonComponent::class) 17 | abstract class NetworkModule { 18 | 19 | @Binds 20 | @Singleton 21 | abstract fun bindCategoryNetworkDataSource( 22 | categoryNetworkDataSource: OfflineFirstCategoryNetworkDataSource 23 | ): CategoryNetworkDataSource 24 | 25 | @Binds 26 | @Singleton 27 | abstract fun bindProductNetworkDataSource( 28 | productNetworkDataSource: OfflineFirstProductNetworkDataSource 29 | ): ProductNetworkDataSource 30 | 31 | @Binds 32 | @Singleton 33 | abstract fun bindIconNetworkDataSource( 34 | iconNetworkDataSource: OfflineFirstIconNetworkDataSource 35 | ): IconNetworkDataSource 36 | } -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/network/model/CategoryNetwork.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.network.model 2 | 3 | import com.squareup.moshi.JsonClass 4 | 5 | @JsonClass(generateAdapter = true) 6 | data class CategoryNetwork( 7 | val id: String, 8 | val name: String, 9 | val sortingPriority: Long 10 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/network/model/GroceryIconAsset.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.network.model 2 | 3 | import com.squareup.moshi.JsonClass 4 | 5 | @JsonClass(generateAdapter = true) 6 | data class GroceryIconAsset( 7 | val fileName: String, 8 | val assetFilePath: String 9 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/network/model/NetworkChangeList.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.network.model 2 | 3 | import com.squareup.moshi.JsonClass 4 | 5 | @JsonClass(generateAdapter = true) 6 | data class NetworkChangeList( 7 | val id: String, 8 | val changeListVersion: Int, 9 | val isDeleted: Boolean 10 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/network/model/ProductNetwork.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.network.model 2 | 3 | import com.squareup.moshi.JsonClass 4 | 5 | @JsonClass(generateAdapter = true) 6 | data class ProductNetwork( 7 | val id: String, 8 | val name: String, 9 | val iconId: String?, 10 | val categoryId: String?, 11 | val isDefault: Boolean 12 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/sync/work/di/SyncModule.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.sync.work.di 2 | 3 | import com.rendox.grocerygenius.data.util.SyncManager 4 | import com.rendox.grocerygenius.sync.work.status.WorkManagerSyncManager 5 | import dagger.Binds 6 | import dagger.Module 7 | import dagger.hilt.InstallIn 8 | import dagger.hilt.components.SingletonComponent 9 | 10 | @Module 11 | @InstallIn(SingletonComponent::class) 12 | abstract class SyncModule { 13 | @Binds 14 | internal abstract fun bindsSyncStatusMonitor(syncStatusMonitor: WorkManagerSyncManager): SyncManager 15 | } -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/sync/work/initializers/SyncInitializer.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.rendox.grocerygenius.sync.work.initializers 18 | 19 | import android.content.Context 20 | import androidx.work.ExistingWorkPolicy 21 | import androidx.work.WorkManager 22 | import com.rendox.grocerygenius.sync.work.workers.SyncWorker 23 | 24 | object Sync { 25 | // This method is initializes sync, the process that keeps the app's data current. 26 | // It is called from the app module's Application.onCreate() and should be only done once. 27 | fun initialize(context: Context) { 28 | WorkManager.getInstance(context).apply { 29 | // Run sync on app startup and ensure only one sync worker runs at any time 30 | enqueueUniqueWork( 31 | SYNC_WORK_NAME, 32 | ExistingWorkPolicy.REPLACE, 33 | SyncWorker.startUpSyncWork() 34 | ) 35 | } 36 | } 37 | } 38 | 39 | // This name should not be changed otherwise the app may have concurrent sync requests running 40 | internal const val SYNC_WORK_NAME = "SyncWorkName" -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/sync/work/status/SyncStatus.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.sync.work.status 2 | 3 | enum class SyncStatus { 4 | RUNNING, 5 | SUCCEEDED, 6 | FAILED, 7 | OFFLINE 8 | } -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/sync/work/status/WorkManagerSyncManager.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.sync.work.status 2 | 3 | import android.content.Context 4 | import androidx.work.ExistingWorkPolicy 5 | import androidx.work.WorkInfo 6 | import androidx.work.WorkManager 7 | import com.rendox.grocerygenius.data.util.SyncManager 8 | import com.rendox.grocerygenius.sync.work.initializers.SYNC_WORK_NAME 9 | import com.rendox.grocerygenius.sync.work.workers.SyncWorker 10 | import dagger.hilt.android.qualifiers.ApplicationContext 11 | import javax.inject.Inject 12 | import kotlinx.coroutines.flow.Flow 13 | import kotlinx.coroutines.flow.conflate 14 | import kotlinx.coroutines.flow.map 15 | 16 | /** 17 | * [SyncManager] backed by [WorkInfo] from [WorkManager] 18 | */ 19 | internal class WorkManagerSyncManager @Inject constructor( 20 | @ApplicationContext private val context: Context 21 | ) : SyncManager { 22 | override val syncStatus: Flow = 23 | WorkManager.getInstance(context).getWorkInfosForUniqueWorkFlow(SYNC_WORK_NAME) 24 | .map { 25 | when (it.lastOrNull()?.state) { 26 | WorkInfo.State.RUNNING, WorkInfo.State.ENQUEUED, null -> SyncStatus.RUNNING 27 | WorkInfo.State.SUCCEEDED -> SyncStatus.SUCCEEDED 28 | WorkInfo.State.FAILED, WorkInfo.State.BLOCKED, WorkInfo.State.CANCELLED -> 29 | SyncStatus.FAILED 30 | } 31 | } 32 | .conflate() 33 | 34 | override fun startOrRetrySync() { 35 | val workManager = WorkManager.getInstance(context) 36 | // Run sync on app startup and ensure only one sync worker runs at any time 37 | workManager.enqueueUniqueWork( 38 | SYNC_WORK_NAME, 39 | ExistingWorkPolicy.REPLACE, 40 | SyncWorker.startUpSyncWork() 41 | ) 42 | } 43 | } -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/testing/GroceryGeniusTestRunner.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.testing 2 | 3 | import android.app.Application 4 | import android.content.Context 5 | import androidx.test.runner.AndroidJUnitRunner 6 | import dagger.hilt.android.testing.HiltTestApplication 7 | 8 | /** 9 | * A custom runner to set up the instrumented application class for tests. 10 | */ 11 | class GroceryGeniusTestRunner : AndroidJUnitRunner() { 12 | override fun newApplication( 13 | cl: ClassLoader, 14 | name: String, 15 | context: Context 16 | ): Application = super.newApplication(cl, HiltTestApplication::class.java.name, context) 17 | } -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/ui/GroceryGeniusTransition.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.ui 2 | 3 | import androidx.compose.animation.core.LinearEasing 4 | import androidx.compose.animation.core.tween 5 | import androidx.compose.animation.fadeIn 6 | import androidx.compose.animation.fadeOut 7 | import androidx.compose.animation.scaleIn 8 | import androidx.compose.animation.slideInHorizontally 9 | import androidx.compose.animation.slideInVertically 10 | import androidx.compose.animation.slideOutHorizontally 11 | import androidx.compose.animation.slideOutVertically 12 | 13 | /** 14 | 15 | */ 16 | object GroceryGeniusTransition { 17 | 18 | /* 19 | * The code of sharedZ animation was taken from https://github.com/ofalvai/HabitBuilder 20 | * Thank you @ofalvai! 21 | */ 22 | val SharedZAxisEnterForward = scaleIn( 23 | initialScale = 0.8f, 24 | animationSpec = tween(300) 25 | ) + fadeIn(animationSpec = tween(durationMillis = 60, delayMillis = 60, easing = LinearEasing)) 26 | val SharedZAxisEnterBackward = scaleIn( 27 | initialScale = 1.1f, 28 | animationSpec = tween(300) 29 | ) 30 | val SlideInVertically = slideInVertically( 31 | initialOffsetY = { it / 10 }, 32 | animationSpec = tween(durationMillis = 200) 33 | ) + fadeIn(animationSpec = tween(durationMillis = 150, easing = LinearEasing)) 34 | val SlideOutVertically = slideOutVertically( 35 | targetOffsetY = { it / 10 }, 36 | animationSpec = tween(durationMillis = 200) 37 | ) + fadeOut(animationSpec = tween(durationMillis = 150, easing = LinearEasing)) 38 | 39 | val SlideInHorizontallyEnterForward = slideInHorizontally { it } 40 | val SlideInHorizontallyEnterBackward = slideInHorizontally { -it } 41 | val SlideOutHorizontallyExitForward = slideOutHorizontally { it } 42 | val SlideOutHorizontallyExitBackward = slideOutHorizontally { -it } 43 | } -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/ui/components/BottomSheetDragHandle.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.ui.components 2 | 3 | import androidx.compose.foundation.layout.Box 4 | import androidx.compose.foundation.layout.padding 5 | import androidx.compose.foundation.layout.size 6 | import androidx.compose.foundation.shape.RoundedCornerShape 7 | import androidx.compose.material3.MaterialTheme 8 | import androidx.compose.material3.Surface 9 | import androidx.compose.runtime.Composable 10 | import androidx.compose.ui.Modifier 11 | import androidx.compose.ui.unit.dp 12 | 13 | @Composable 14 | fun BottomSheetDragHandle(modifier: Modifier = Modifier) { 15 | Surface( 16 | modifier = modifier.padding(vertical = 12.dp), 17 | color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.4f), 18 | shape = RoundedCornerShape(28.dp) 19 | ) { 20 | Box( 21 | Modifier.size( 22 | width = 32.dp, 23 | height = 4.dp 24 | ) 25 | ) 26 | } 27 | } -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/ui/components/DeleteConfirmationDialog.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.ui.components 2 | 3 | import androidx.compose.material3.AlertDialog 4 | import androidx.compose.material3.FilledTonalButton 5 | import androidx.compose.material3.Text 6 | import androidx.compose.material3.TextButton 7 | import androidx.compose.runtime.Composable 8 | import androidx.compose.ui.Modifier 9 | import androidx.compose.ui.res.stringResource 10 | import com.rendox.grocerygenius.R 11 | 12 | @Composable 13 | fun DeleteConfirmationDialog( 14 | modifier: Modifier = Modifier, 15 | bodyText: String, 16 | onConfirm: () -> Unit, 17 | onDismissRequest: () -> Unit 18 | ) { 19 | AlertDialog( 20 | modifier = modifier, 21 | onDismissRequest = onDismissRequest, 22 | title = { 23 | Text(text = "${stringResource(R.string.delete)}?") 24 | }, 25 | text = { 26 | Text(text = bodyText) 27 | }, 28 | confirmButton = { 29 | FilledTonalButton(onClick = onConfirm) { 30 | Text(text = stringResource(R.string.delete)) 31 | } 32 | }, 33 | dismissButton = { 34 | TextButton(onClick = onDismissRequest) { 35 | Text(text = stringResource(android.R.string.cancel)) 36 | } 37 | } 38 | ) 39 | } -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/ui/components/GroceryCompact.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.ui.components 2 | 3 | import androidx.compose.foundation.layout.Row 4 | import androidx.compose.foundation.layout.padding 5 | import androidx.compose.material3.MaterialTheme 6 | import androidx.compose.material3.Surface 7 | import androidx.compose.material3.Text 8 | import androidx.compose.runtime.Composable 9 | import androidx.compose.ui.Alignment 10 | import androidx.compose.ui.Modifier 11 | import androidx.compose.ui.graphics.Color 12 | import androidx.compose.ui.unit.dp 13 | import com.rendox.grocerygenius.ui.theme.CornerRoundingDefault 14 | 15 | @Composable 16 | fun GroceryCompact( 17 | modifier: Modifier = Modifier, 18 | title: String, 19 | backgroundColor: Color = Color.Unspecified, 20 | icon: @Composable () -> Unit 21 | ) { 22 | Surface( 23 | modifier = modifier, 24 | shape = CornerRoundingDefault, 25 | color = backgroundColor 26 | ) { 27 | Row( 28 | modifier = modifier.padding(horizontal = 12.dp), 29 | verticalAlignment = Alignment.CenterVertically 30 | ) { 31 | icon() 32 | Text( 33 | text = title, 34 | style = MaterialTheme.typography.labelLarge, 35 | modifier = Modifier.padding(start = 8.dp) 36 | ) 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/ui/components/Scrim.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.ui.components 2 | 3 | import androidx.compose.foundation.background 4 | import androidx.compose.foundation.layout.Box 5 | import androidx.compose.foundation.layout.fillMaxSize 6 | import androidx.compose.material3.MaterialTheme 7 | import androidx.compose.runtime.Composable 8 | import androidx.compose.ui.Modifier 9 | 10 | @Composable 11 | fun Scrim(modifier: Modifier = Modifier) { 12 | Box( 13 | modifier = modifier 14 | .fillMaxSize() 15 | .background(color = MaterialTheme.colorScheme.scrim.copy(alpha = 0.32F)) 16 | ) 17 | } -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/ui/components/TonalDataInput.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.ui.components 2 | 3 | import androidx.compose.foundation.Indication 4 | import androidx.compose.foundation.LocalIndication 5 | import androidx.compose.foundation.background 6 | import androidx.compose.foundation.clickable 7 | import androidx.compose.foundation.interaction.MutableInteractionSource 8 | import androidx.compose.foundation.layout.Box 9 | import androidx.compose.foundation.shape.RoundedCornerShape 10 | import androidx.compose.material3.MaterialTheme 11 | import androidx.compose.runtime.Composable 12 | import androidx.compose.runtime.remember 13 | import androidx.compose.ui.Modifier 14 | import androidx.compose.ui.draw.clip 15 | 16 | @Composable 17 | fun TonalDataInput( 18 | modifier: Modifier = Modifier, 19 | onClick: () -> Unit, 20 | indication: Indication? = LocalIndication.current, 21 | dropDownMenu: @Composable (() -> Unit)? = null, 22 | content: @Composable () -> Unit 23 | ) { 24 | Box( 25 | modifier = modifier 26 | .clip(shape = RoundedCornerShape(30)) 27 | .background(MaterialTheme.colorScheme.secondaryContainer) 28 | .clickable( 29 | onClick = onClick, 30 | indication = indication, 31 | interactionSource = remember { MutableInteractionSource() } 32 | ) 33 | ) { 34 | content() 35 | dropDownMenu?.let { it() } 36 | } 37 | } -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/ui/components/collapsingtoolbar/CollapsingToolbarScaffoldScrollableState.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.ui.components.collapsingtoolbar 2 | 3 | import androidx.compose.foundation.gestures.ScrollableState 4 | 5 | interface CollapsingToolbarScaffoldScrollableState : ScrollableState { 6 | val firstVisibleItemIndex: Int 7 | val firstVisibleItemScrollOffset: Int 8 | } -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/ui/components/collapsingtoolbar/scrollbehavior/FixedScrollFlagState.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.ui.components.collapsingtoolbar.scrollbehavior 2 | 3 | abstract class FixedScrollFlagState(heightRange: IntRange) : ScrollFlagState(heightRange) { 4 | 5 | final override val offset: Float = 0f 6 | } -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/ui/components/collapsingtoolbar/scrollbehavior/ScrollFlagState.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("ktlint:standard:property-naming") 2 | 3 | package com.rendox.grocerygenius.ui.components.collapsingtoolbar.scrollbehavior 4 | 5 | abstract class ScrollFlagState( 6 | heightRange: IntRange 7 | ) : ToolbarState { 8 | 9 | init { 10 | require(heightRange.first >= 0 && heightRange.last >= heightRange.first) { 11 | "The lowest height value must be >= 0 and the highest height value must be >= the lowest value." 12 | } 13 | } 14 | 15 | protected val minHeight = heightRange.first 16 | protected val maxHeight = heightRange.last 17 | protected val rangeDifference = maxHeight - minHeight 18 | 19 | protected var _consumed: Float = 0f 20 | 21 | protected abstract var _scrollOffset: Float 22 | 23 | final override val height: Float 24 | get() = (maxHeight - scrollOffset).coerceIn(minHeight.toFloat(), maxHeight.toFloat()) 25 | 26 | final override val progress: Float 27 | get() = 1 - (maxHeight - height) / rangeDifference 28 | 29 | final override val consumed: Float 30 | get() = _consumed 31 | 32 | final override var scrollTopLimitReached: Boolean = true 33 | } -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/ui/components/collapsingtoolbar/scrollbehavior/ToolbarState.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.ui.components.collapsingtoolbar.scrollbehavior 2 | 3 | import androidx.compose.runtime.Stable 4 | 5 | @Stable 6 | interface ToolbarState { 7 | val offset: Float 8 | val height: Float 9 | val progress: Float 10 | val consumed: Float 11 | var scrollTopLimitReached: Boolean 12 | var scrollOffset: Float 13 | } -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/ui/components/grocerylist/DefaultGroceryListItemColors.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.ui.components.grocerylist 2 | 3 | import androidx.compose.material3.ColorScheme 4 | import androidx.compose.runtime.Immutable 5 | import androidx.compose.ui.graphics.Color 6 | 7 | @Immutable 8 | class DefaultGroceryListItemColors( 9 | val defaultBackgroundColor: Color, 10 | val purchasedBackgroundColor: Color 11 | ) 12 | 13 | val ColorScheme.groceryListItemColors: DefaultGroceryListItemColors 14 | get() = DefaultGroceryListItemColors( 15 | defaultBackgroundColor = primaryContainer, 16 | purchasedBackgroundColor = tertiaryContainer 17 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/ui/components/grocerylist/GroceryGroup.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.ui.components.grocerylist 2 | 3 | import androidx.annotation.StringRes 4 | import com.rendox.grocerygenius.model.Grocery 5 | 6 | data class GroceryGroup( 7 | @StringRes val titleId: Int?, 8 | val groceries: List 9 | ) -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/ui/helpers/DragHandleReorderItemTouchHelperCallback.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.ui.helpers 2 | 3 | import androidx.recyclerview.widget.ItemTouchHelper 4 | import androidx.recyclerview.widget.RecyclerView 5 | import com.rendox.grocerygenius.feature.dashboardscreen.recyclerview.GroceryListAdderItemViewHolder 6 | 7 | class DragHandleReorderItemTouchHelperCallback( 8 | private val onItemMove: (fromPosition: Int, toPosition: Int) -> Unit, 9 | private val onClearView: () -> Unit 10 | ) : ItemTouchHelper.SimpleCallback( 11 | ItemTouchHelper.UP or ItemTouchHelper.DOWN, 12 | 0 13 | ) { 14 | override fun isLongPressDragEnabled() = false 15 | 16 | override fun isItemViewSwipeEnabled() = false 17 | 18 | override fun getMovementFlags( 19 | recyclerView: RecyclerView, 20 | viewHolder: RecyclerView.ViewHolder 21 | ): Int { 22 | val dragFlags = ItemTouchHelper.UP or ItemTouchHelper.DOWN 23 | return makeMovementFlags(dragFlags, 0) 24 | } 25 | 26 | override fun onMove( 27 | recyclerView: RecyclerView, 28 | viewHolder: RecyclerView.ViewHolder, 29 | target: RecyclerView.ViewHolder 30 | ): Boolean { 31 | if (target is GroceryListAdderItemViewHolder) return false 32 | onItemMove( 33 | viewHolder.bindingAdapterPosition, 34 | target.bindingAdapterPosition 35 | ) 36 | return true 37 | } 38 | 39 | override fun onSwiped( 40 | viewHolder: RecyclerView.ViewHolder, 41 | direction: Int 42 | ) { 43 | } 44 | 45 | override fun clearView( 46 | recyclerView: RecyclerView, 47 | viewHolder: RecyclerView.ViewHolder 48 | ) { 49 | onClearView() 50 | super.clearView(recyclerView, viewHolder) 51 | } 52 | } -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/ui/helpers/UiEvent.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.ui.helpers 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.LaunchedEffect 5 | 6 | interface UiEvent { 7 | val data: T 8 | fun onConsumed() 9 | } 10 | 11 | @Composable 12 | fun ObserveUiEvent( 13 | event: UiEvent?, 14 | onEvent: suspend (T) -> Unit 15 | ) { 16 | LaunchedEffect(event) { 17 | if (event != null) { 18 | onEvent(event.data) 19 | event.onConsumed() 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/ui/theme/Dimens.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.ui.theme 2 | 3 | import androidx.compose.foundation.shape.RoundedCornerShape 4 | import androidx.compose.ui.unit.dp 5 | 6 | val CornerRoundingDefault = RoundedCornerShape(20) 7 | val GroceryItemRounding = 8.dp 8 | 9 | val TopAppBarSmallHeight = 56.dp 10 | val TopAppBarMediumHeight = 112.dp 11 | val TopAppBarActionsHorizontalPadding = 12.dp -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/ui/theme/ExtendedColors.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.ui.theme 2 | 3 | import androidx.compose.material3.MaterialTheme 4 | import androidx.compose.runtime.Composable 5 | import androidx.compose.runtime.Immutable 6 | import androidx.compose.runtime.ReadOnlyComposable 7 | import androidx.compose.runtime.compositionLocalOf 8 | import androidx.compose.ui.graphics.Color 9 | 10 | @Immutable 11 | data class ExtendedColors( 12 | val redAccent: Color, 13 | val onRedAccent: Color 14 | ) 15 | 16 | val LightExtendedColors = ExtendedColors( 17 | redAccent = Color(0xFFDD5B4B), 18 | onRedAccent = Color(0xFFFFFFFF) 19 | ) 20 | 21 | val DarkExtendedColors = ExtendedColors( 22 | redAccent = Color(0xFFFF988D), 23 | onRedAccent = Color(0xFF490003) 24 | ) 25 | 26 | val LocalExtendedColors = compositionLocalOf { LightExtendedColors } 27 | 28 | @Suppress("UnusedReceiverParameter") 29 | val MaterialTheme.extendedColors: ExtendedColors 30 | @Composable 31 | @ReadOnlyComposable 32 | get() = LocalExtendedColors.current -------------------------------------------------------------------------------- /app/src/main/java/com/rendox/grocerygenius/ui/theme/Type.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius.ui.theme 2 | 3 | import androidx.compose.ui.text.font.Font 4 | import androidx.compose.ui.text.font.FontFamily 5 | import androidx.compose.ui.text.font.FontWeight 6 | import com.rendox.grocerygenius.R 7 | 8 | val trainOneFontFamily = FontFamily( 9 | Font(R.font.train_one_regular, FontWeight.Normal) 10 | ) -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/empty_grocery_list_illustration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/app/src/main/res/drawable-hdpi/empty_grocery_list_illustration.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/empty_grocery_list_illustration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/app/src/main/res/drawable-mdpi/empty_grocery_list_illustration.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/empty_grocery_list_illustration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/app/src/main/res/drawable-xhdpi/empty_grocery_list_illustration.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/empty_grocery_list_illustration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/app/src/main/res/drawable-xxhdpi/empty_grocery_list_illustration.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/empty_grocery_list_illustration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/app/src/main/res/drawable-xxxhdpi/empty_grocery_list_illustration.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_drag_handle_24.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_folder_24.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_history_24.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_palette_24.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_swap_vert_24.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/cancel_circle.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/checkmark_circle.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/cloud_off.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/day_night.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/github_mark.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/image_icon.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/mail.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/shopping_cart.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/font/train_one_regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/app/src/main/res/font/train_one_regular.ttf -------------------------------------------------------------------------------- /app/src/main/res/layout/list_recyclerview.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #06335C 4 | #60000000 5 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/xml/backup_rules.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/xml/data_extraction_rules.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | 12 | 13 | 19 | -------------------------------------------------------------------------------- /assets/README.md: -------------------------------------------------------------------------------- 1 | ## GPLv3 does not apply to icons inside the assets/icons folder 2 | 3 | The project's license does not apply to the icons in the assets/icons folder. Images in that folder were generated by Bing Image Creator and Recraft AI and are owned by the companies behind these services respectively. No commercial use is allowed. 4 | 5 | ## Algorithm for updating the predefined groceries 6 | 7 | 1. Switch the Gradle build variant to debug 8 | 2. Build and launch the app with existing data 9 | 3. Update files in the debug/assets source set 10 | 4. Update change lists 11 | 5. Update the icon archive (if you changed icons) 12 | 6. Without uninstalling the app or clearing its data, build and launch the app again 13 | 7. Verify the data on the device has been updated 14 | 8. Uninstall the app, switch to the release mode, and build again 15 | 9. Copy the changes to the top-level assets folder 16 | 10. Push changes to the develop branch 17 | 11. Open a pull request to the production branch 18 | 12. After the PR got merged, relaunch the app to trigger updates, without uninstalling the app or clearing its data 19 | 13. Verify the data on the device has been updated 20 | 14. At this point the data on users' devices should get updated as well once they open the app again 21 | 22 | ## Contributing icons 23 | 24 | - If you are a designer who wants to add new icons and you don't know git and GitHub well, it will be easier to just email me and not go through this complicated process yourself. Please, submit them in PNG format and make sure their copyright allows at least non-commercial use. 25 | - If you are not a designer but want to suggest some currently missing icons, feel free to reach out as well. 26 | 27 | If there is demand, the process of adding and contributing icons will be improved in the future. -------------------------------------------------------------------------------- /assets/category/af_ZA/categories.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "default-category-dairy-and-eggs", 4 | "name": "Dairy & Eggs", 5 | "sortingPriority": 2 6 | }, 7 | { 8 | "id": "default-category-drinks", 9 | "name": "Drinks", 10 | "sortingPriority": 7 11 | }, 12 | { 13 | "id": "default-category-fruits-and-vegetables", 14 | "name": "Fruits & Vegetables", 15 | "sortingPriority": 1 16 | }, 17 | { 18 | "id": "default-category-grain-products", 19 | "name": "Grain Products", 20 | "sortingPriority": 5 21 | }, 22 | { 23 | "id": "default-category-health-and-beauty", 24 | "name": "Health & Beauty", 25 | "sortingPriority": 9 26 | }, 27 | { 28 | "id": "default-category-household", 29 | "name": "Household", 30 | "sortingPriority": 8 31 | }, 32 | { 33 | "id": "default-category-meat-and-fish", 34 | "name": "Meat & Fish", 35 | "sortingPriority": 3 36 | }, 37 | { 38 | "id": "default-category-pet-supplies", 39 | "name": "Pet Supplies", 40 | "sortingPriority": 10 41 | }, 42 | { 43 | "id": "default-category-sauces-and-spices", 44 | "name": "Sauces & Spices", 45 | "sortingPriority": 4 46 | }, 47 | { 48 | "id": "default-category-snacks-and-bakery", 49 | "name": "Snacks & Bakery", 50 | "sortingPriority": 6 51 | } 52 | ] -------------------------------------------------------------------------------- /assets/category/ar_SA/categories.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "default-category-dairy-and-eggs", 4 | "name": "الألبان والبيض", 5 | "sortingPriority": 2 6 | }, 7 | { 8 | "id": "default-category-drinks", 9 | "name": "مشروبات", 10 | "sortingPriority": 7 11 | }, 12 | { 13 | "id": "default-category-fruits-and-vegetables", 14 | "name": "الفواكه والخضروات", 15 | "sortingPriority": 1 16 | }, 17 | { 18 | "id": "default-category-grain-products", 19 | "name": "منتجات الحبوب", 20 | "sortingPriority": 5 21 | }, 22 | { 23 | "id": "default-category-health-and-beauty", 24 | "name": "الصحة والجمال", 25 | "sortingPriority": 9 26 | }, 27 | { 28 | "id": "default-category-household", 29 | "name": "Household", 30 | "sortingPriority": 8 31 | }, 32 | { 33 | "id": "default-category-meat-and-fish", 34 | "name": "اللحوم والسمك", 35 | "sortingPriority": 3 36 | }, 37 | { 38 | "id": "default-category-pet-supplies", 39 | "name": "لوازم الحيوانات الأليفة", 40 | "sortingPriority": 10 41 | }, 42 | { 43 | "id": "default-category-sauces-and-spices", 44 | "name": "الصلصات والتوابل", 45 | "sortingPriority": 4 46 | }, 47 | { 48 | "id": "default-category-snacks-and-bakery", 49 | "name": "وجبات خفيفة ومخبوزات", 50 | "sortingPriority": 6 51 | } 52 | ] -------------------------------------------------------------------------------- /assets/category/ca_ES/categories.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "default-category-dairy-and-eggs", 4 | "name": "Dairy & Eggs", 5 | "sortingPriority": 2 6 | }, 7 | { 8 | "id": "default-category-drinks", 9 | "name": "Drinks", 10 | "sortingPriority": 7 11 | }, 12 | { 13 | "id": "default-category-fruits-and-vegetables", 14 | "name": "Fruits & Vegetables", 15 | "sortingPriority": 1 16 | }, 17 | { 18 | "id": "default-category-grain-products", 19 | "name": "Grain Products", 20 | "sortingPriority": 5 21 | }, 22 | { 23 | "id": "default-category-health-and-beauty", 24 | "name": "Health & Beauty", 25 | "sortingPriority": 9 26 | }, 27 | { 28 | "id": "default-category-household", 29 | "name": "Household", 30 | "sortingPriority": 8 31 | }, 32 | { 33 | "id": "default-category-meat-and-fish", 34 | "name": "Meat & Fish", 35 | "sortingPriority": 3 36 | }, 37 | { 38 | "id": "default-category-pet-supplies", 39 | "name": "Pet Supplies", 40 | "sortingPriority": 10 41 | }, 42 | { 43 | "id": "default-category-sauces-and-spices", 44 | "name": "Sauces & Spices", 45 | "sortingPriority": 4 46 | }, 47 | { 48 | "id": "default-category-snacks-and-bakery", 49 | "name": "Snacks & Bakery", 50 | "sortingPriority": 6 51 | } 52 | ] -------------------------------------------------------------------------------- /assets/category/categories.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "default-category-dairy-and-eggs", 4 | "name": "Dairy & Eggs", 5 | "sortingPriority": 2 6 | }, 7 | { 8 | "id": "default-category-drinks", 9 | "name": "Drinks", 10 | "sortingPriority": 7 11 | }, 12 | { 13 | "id": "default-category-fruits-and-vegetables", 14 | "name": "Fruits & Vegetables", 15 | "sortingPriority": 1 16 | }, 17 | { 18 | "id": "default-category-grain-products", 19 | "name": "Grain Products", 20 | "sortingPriority": 5 21 | }, 22 | { 23 | "id": "default-category-health-and-beauty", 24 | "name": "Health & Beauty", 25 | "sortingPriority": 9 26 | }, 27 | { 28 | "id": "default-category-household", 29 | "name": "Household", 30 | "sortingPriority": 8 31 | }, 32 | { 33 | "id": "default-category-meat-and-fish", 34 | "name": "Meat & Fish", 35 | "sortingPriority": 3 36 | }, 37 | { 38 | "id": "default-category-pet-supplies", 39 | "name": "Pet Supplies", 40 | "sortingPriority": 10 41 | }, 42 | { 43 | "id": "default-category-sauces-and-spices", 44 | "name": "Sauces & Spices", 45 | "sortingPriority": 4 46 | }, 47 | { 48 | "id": "default-category-snacks-and-bakery", 49 | "name": "Snacks & Bakery", 50 | "sortingPriority": 6 51 | } 52 | ] -------------------------------------------------------------------------------- /assets/category/categories_change_list.json: -------------------------------------------------------------------------------- 1 | [ 2 | 3 | ] -------------------------------------------------------------------------------- /assets/category/categories_en.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "default-category-dairy-and-eggs", 4 | "name": "Dairy & Eggs", 5 | "sortingPriority": 2 6 | }, 7 | { 8 | "id": "default-category-drinks", 9 | "name": "Drinks", 10 | "sortingPriority": 7 11 | }, 12 | { 13 | "id": "default-category-fruits-and-vegetables", 14 | "name": "Fruits & Vegetables", 15 | "sortingPriority": 1 16 | }, 17 | { 18 | "id": "default-category-grain-products", 19 | "name": "Grain Products", 20 | "sortingPriority": 5 21 | }, 22 | { 23 | "id": "default-category-health-and-beauty", 24 | "name": "Health & Beauty", 25 | "sortingPriority": 9 26 | }, 27 | { 28 | "id": "default-category-household", 29 | "name": "Household", 30 | "sortingPriority": 8 31 | }, 32 | { 33 | "id": "default-category-meat-and-fish", 34 | "name": "Meat & Fish", 35 | "sortingPriority": 3 36 | }, 37 | { 38 | "id": "default-category-pet-supplies", 39 | "name": "Pet Supplies", 40 | "sortingPriority": 10 41 | }, 42 | { 43 | "id": "default-category-sauces-and-spices", 44 | "name": "Sauces & Spices", 45 | "sortingPriority": 4 46 | }, 47 | { 48 | "id": "default-category-snacks-and-bakery", 49 | "name": "Snacks & Bakery", 50 | "sortingPriority": 6 51 | } 52 | ] -------------------------------------------------------------------------------- /assets/category/cs_CZ/categories.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "default-category-dairy-and-eggs", 4 | "name": "Dairy & Eggs", 5 | "sortingPriority": 2 6 | }, 7 | { 8 | "id": "default-category-drinks", 9 | "name": "Drinks", 10 | "sortingPriority": 7 11 | }, 12 | { 13 | "id": "default-category-fruits-and-vegetables", 14 | "name": "Fruits & Vegetables", 15 | "sortingPriority": 1 16 | }, 17 | { 18 | "id": "default-category-grain-products", 19 | "name": "Grain Products", 20 | "sortingPriority": 5 21 | }, 22 | { 23 | "id": "default-category-health-and-beauty", 24 | "name": "Health & Beauty", 25 | "sortingPriority": 9 26 | }, 27 | { 28 | "id": "default-category-household", 29 | "name": "Household", 30 | "sortingPriority": 8 31 | }, 32 | { 33 | "id": "default-category-meat-and-fish", 34 | "name": "Meat & Fish", 35 | "sortingPriority": 3 36 | }, 37 | { 38 | "id": "default-category-pet-supplies", 39 | "name": "Pet Supplies", 40 | "sortingPriority": 10 41 | }, 42 | { 43 | "id": "default-category-sauces-and-spices", 44 | "name": "Sauces & Spices", 45 | "sortingPriority": 4 46 | }, 47 | { 48 | "id": "default-category-snacks-and-bakery", 49 | "name": "Snacks & Bakery", 50 | "sortingPriority": 6 51 | } 52 | ] -------------------------------------------------------------------------------- /assets/category/da_DK/categories.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "default-category-dairy-and-eggs", 4 | "name": "Dairy & Eggs", 5 | "sortingPriority": 2 6 | }, 7 | { 8 | "id": "default-category-drinks", 9 | "name": "Drinks", 10 | "sortingPriority": 7 11 | }, 12 | { 13 | "id": "default-category-fruits-and-vegetables", 14 | "name": "Fruits & Vegetables", 15 | "sortingPriority": 1 16 | }, 17 | { 18 | "id": "default-category-grain-products", 19 | "name": "Grain Products", 20 | "sortingPriority": 5 21 | }, 22 | { 23 | "id": "default-category-health-and-beauty", 24 | "name": "Health & Beauty", 25 | "sortingPriority": 9 26 | }, 27 | { 28 | "id": "default-category-household", 29 | "name": "Household", 30 | "sortingPriority": 8 31 | }, 32 | { 33 | "id": "default-category-meat-and-fish", 34 | "name": "Meat & Fish", 35 | "sortingPriority": 3 36 | }, 37 | { 38 | "id": "default-category-pet-supplies", 39 | "name": "Pet Supplies", 40 | "sortingPriority": 10 41 | }, 42 | { 43 | "id": "default-category-sauces-and-spices", 44 | "name": "Sauces & Spices", 45 | "sortingPriority": 4 46 | }, 47 | { 48 | "id": "default-category-snacks-and-bakery", 49 | "name": "Snacks & Bakery", 50 | "sortingPriority": 6 51 | } 52 | ] -------------------------------------------------------------------------------- /assets/category/de_DE/categories.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "default-category-dairy-and-eggs", 4 | "name": "Milchprodukte & Eier", 5 | "sortingPriority": 2 6 | }, 7 | { 8 | "id": "default-category-drinks", 9 | "name": "Getränke", 10 | "sortingPriority": 7 11 | }, 12 | { 13 | "id": "default-category-fruits-and-vegetables", 14 | "name": "Obst & Gemüse", 15 | "sortingPriority": 1 16 | }, 17 | { 18 | "id": "default-category-grain-products", 19 | "name": "Getreideprodukte", 20 | "sortingPriority": 5 21 | }, 22 | { 23 | "id": "default-category-health-and-beauty", 24 | "name": "Gesundheit & Kosmetik", 25 | "sortingPriority": 9 26 | }, 27 | { 28 | "id": "default-category-household", 29 | "name": "Haushalt", 30 | "sortingPriority": 8 31 | }, 32 | { 33 | "id": "default-category-meat-and-fish", 34 | "name": "Fleisch & Fisch", 35 | "sortingPriority": 3 36 | }, 37 | { 38 | "id": "default-category-pet-supplies", 39 | "name": "Haustierbedarf", 40 | "sortingPriority": 10 41 | }, 42 | { 43 | "id": "default-category-sauces-and-spices", 44 | "name": "Soßen & Gewürze", 45 | "sortingPriority": 4 46 | }, 47 | { 48 | "id": "default-category-snacks-and-bakery", 49 | "name": "Snacks & Backwaren", 50 | "sortingPriority": 6 51 | } 52 | ] -------------------------------------------------------------------------------- /assets/category/el_GR/categories.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "default-category-dairy-and-eggs", 4 | "name": "Dairy & Eggs", 5 | "sortingPriority": 2 6 | }, 7 | { 8 | "id": "default-category-drinks", 9 | "name": "Drinks", 10 | "sortingPriority": 7 11 | }, 12 | { 13 | "id": "default-category-fruits-and-vegetables", 14 | "name": "Fruits & Vegetables", 15 | "sortingPriority": 1 16 | }, 17 | { 18 | "id": "default-category-grain-products", 19 | "name": "Grain Products", 20 | "sortingPriority": 5 21 | }, 22 | { 23 | "id": "default-category-health-and-beauty", 24 | "name": "Health & Beauty", 25 | "sortingPriority": 9 26 | }, 27 | { 28 | "id": "default-category-household", 29 | "name": "Household", 30 | "sortingPriority": 8 31 | }, 32 | { 33 | "id": "default-category-meat-and-fish", 34 | "name": "Meat & Fish", 35 | "sortingPriority": 3 36 | }, 37 | { 38 | "id": "default-category-pet-supplies", 39 | "name": "Pet Supplies", 40 | "sortingPriority": 10 41 | }, 42 | { 43 | "id": "default-category-sauces-and-spices", 44 | "name": "Sauces & Spices", 45 | "sortingPriority": 4 46 | }, 47 | { 48 | "id": "default-category-snacks-and-bakery", 49 | "name": "Snacks & Bakery", 50 | "sortingPriority": 6 51 | } 52 | ] -------------------------------------------------------------------------------- /assets/category/es_ES/categories.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "default-category-dairy-and-eggs", 4 | "name": "Dairy & Eggs", 5 | "sortingPriority": 2 6 | }, 7 | { 8 | "id": "default-category-drinks", 9 | "name": "Drinks", 10 | "sortingPriority": 7 11 | }, 12 | { 13 | "id": "default-category-fruits-and-vegetables", 14 | "name": "Fruits & Vegetables", 15 | "sortingPriority": 1 16 | }, 17 | { 18 | "id": "default-category-grain-products", 19 | "name": "Grain Products", 20 | "sortingPriority": 5 21 | }, 22 | { 23 | "id": "default-category-health-and-beauty", 24 | "name": "Health & Beauty", 25 | "sortingPriority": 9 26 | }, 27 | { 28 | "id": "default-category-household", 29 | "name": "Household", 30 | "sortingPriority": 8 31 | }, 32 | { 33 | "id": "default-category-meat-and-fish", 34 | "name": "Meat & Fish", 35 | "sortingPriority": 3 36 | }, 37 | { 38 | "id": "default-category-pet-supplies", 39 | "name": "Pet Supplies", 40 | "sortingPriority": 10 41 | }, 42 | { 43 | "id": "default-category-sauces-and-spices", 44 | "name": "Sauces & Spices", 45 | "sortingPriority": 4 46 | }, 47 | { 48 | "id": "default-category-snacks-and-bakery", 49 | "name": "Panadería y Aperitivos", 50 | "sortingPriority": 6 51 | } 52 | ] -------------------------------------------------------------------------------- /assets/category/fi_FI/categories.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "default-category-dairy-and-eggs", 4 | "name": "Dairy & Eggs", 5 | "sortingPriority": 2 6 | }, 7 | { 8 | "id": "default-category-drinks", 9 | "name": "Drinks", 10 | "sortingPriority": 7 11 | }, 12 | { 13 | "id": "default-category-fruits-and-vegetables", 14 | "name": "Fruits & Vegetables", 15 | "sortingPriority": 1 16 | }, 17 | { 18 | "id": "default-category-grain-products", 19 | "name": "Grain Products", 20 | "sortingPriority": 5 21 | }, 22 | { 23 | "id": "default-category-health-and-beauty", 24 | "name": "Health & Beauty", 25 | "sortingPriority": 9 26 | }, 27 | { 28 | "id": "default-category-household", 29 | "name": "Household", 30 | "sortingPriority": 8 31 | }, 32 | { 33 | "id": "default-category-meat-and-fish", 34 | "name": "Meat & Fish", 35 | "sortingPriority": 3 36 | }, 37 | { 38 | "id": "default-category-pet-supplies", 39 | "name": "Pet Supplies", 40 | "sortingPriority": 10 41 | }, 42 | { 43 | "id": "default-category-sauces-and-spices", 44 | "name": "Sauces & Spices", 45 | "sortingPriority": 4 46 | }, 47 | { 48 | "id": "default-category-snacks-and-bakery", 49 | "name": "Snacks & Bakery", 50 | "sortingPriority": 6 51 | } 52 | ] -------------------------------------------------------------------------------- /assets/category/fr_FR/categories.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "default-category-dairy-and-eggs", 4 | "name": "Lait et œufs", 5 | "sortingPriority": 2 6 | }, 7 | { 8 | "id": "default-category-drinks", 9 | "name": "Boissons", 10 | "sortingPriority": 7 11 | }, 12 | { 13 | "id": "default-category-fruits-and-vegetables", 14 | "name": "Fruits et légumes", 15 | "sortingPriority": 1 16 | }, 17 | { 18 | "id": "default-category-grain-products", 19 | "name": "Céréales et pâtes", 20 | "sortingPriority": 5 21 | }, 22 | { 23 | "id": "default-category-health-and-beauty", 24 | "name": "Santé et beauté", 25 | "sortingPriority": 9 26 | }, 27 | { 28 | "id": "default-category-household", 29 | "name": "Drogerie", 30 | "sortingPriority": 8 31 | }, 32 | { 33 | "id": "default-category-meat-and-fish", 34 | "name": "Viande et poisson", 35 | "sortingPriority": 3 36 | }, 37 | { 38 | "id": "default-category-pet-supplies", 39 | "name": "Animaux", 40 | "sortingPriority": 10 41 | }, 42 | { 43 | "id": "default-category-sauces-and-spices", 44 | "name": "Sauces et épices", 45 | "sortingPriority": 4 46 | }, 47 | { 48 | "id": "default-category-snacks-and-bakery", 49 | "name": "Snacks et boulangerie", 50 | "sortingPriority": 6 51 | } 52 | ] -------------------------------------------------------------------------------- /assets/category/he_IL/categories.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "default-category-dairy-and-eggs", 4 | "name": "Dairy & Eggs", 5 | "sortingPriority": 2 6 | }, 7 | { 8 | "id": "default-category-drinks", 9 | "name": "Drinks", 10 | "sortingPriority": 7 11 | }, 12 | { 13 | "id": "default-category-fruits-and-vegetables", 14 | "name": "Fruits & Vegetables", 15 | "sortingPriority": 1 16 | }, 17 | { 18 | "id": "default-category-grain-products", 19 | "name": "Grain Products", 20 | "sortingPriority": 5 21 | }, 22 | { 23 | "id": "default-category-health-and-beauty", 24 | "name": "Health & Beauty", 25 | "sortingPriority": 9 26 | }, 27 | { 28 | "id": "default-category-household", 29 | "name": "Household", 30 | "sortingPriority": 8 31 | }, 32 | { 33 | "id": "default-category-meat-and-fish", 34 | "name": "Meat & Fish", 35 | "sortingPriority": 3 36 | }, 37 | { 38 | "id": "default-category-pet-supplies", 39 | "name": "Pet Supplies", 40 | "sortingPriority": 10 41 | }, 42 | { 43 | "id": "default-category-sauces-and-spices", 44 | "name": "Sauces & Spices", 45 | "sortingPriority": 4 46 | }, 47 | { 48 | "id": "default-category-snacks-and-bakery", 49 | "name": "Snacks & Bakery", 50 | "sortingPriority": 6 51 | } 52 | ] -------------------------------------------------------------------------------- /assets/category/hu_HU/categories.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "default-category-dairy-and-eggs", 4 | "name": "Dairy & Eggs", 5 | "sortingPriority": 2 6 | }, 7 | { 8 | "id": "default-category-drinks", 9 | "name": "Drinks", 10 | "sortingPriority": 7 11 | }, 12 | { 13 | "id": "default-category-fruits-and-vegetables", 14 | "name": "Fruits & Vegetables", 15 | "sortingPriority": 1 16 | }, 17 | { 18 | "id": "default-category-grain-products", 19 | "name": "Grain Products", 20 | "sortingPriority": 5 21 | }, 22 | { 23 | "id": "default-category-health-and-beauty", 24 | "name": "Health & Beauty", 25 | "sortingPriority": 9 26 | }, 27 | { 28 | "id": "default-category-household", 29 | "name": "Household", 30 | "sortingPriority": 8 31 | }, 32 | { 33 | "id": "default-category-meat-and-fish", 34 | "name": "Meat & Fish", 35 | "sortingPriority": 3 36 | }, 37 | { 38 | "id": "default-category-pet-supplies", 39 | "name": "Pet Supplies", 40 | "sortingPriority": 10 41 | }, 42 | { 43 | "id": "default-category-sauces-and-spices", 44 | "name": "Sauces & Spices", 45 | "sortingPriority": 4 46 | }, 47 | { 48 | "id": "default-category-snacks-and-bakery", 49 | "name": "Snacks & Bakery", 50 | "sortingPriority": 6 51 | } 52 | ] -------------------------------------------------------------------------------- /assets/category/it_IT/categories.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "default-category-dairy-and-eggs", 4 | "name": "Dairy & Eggs", 5 | "sortingPriority": 2 6 | }, 7 | { 8 | "id": "default-category-drinks", 9 | "name": "Drinks", 10 | "sortingPriority": 7 11 | }, 12 | { 13 | "id": "default-category-fruits-and-vegetables", 14 | "name": "Fruits & Vegetables", 15 | "sortingPriority": 1 16 | }, 17 | { 18 | "id": "default-category-grain-products", 19 | "name": "Grain Products", 20 | "sortingPriority": 5 21 | }, 22 | { 23 | "id": "default-category-health-and-beauty", 24 | "name": "Health & Beauty", 25 | "sortingPriority": 9 26 | }, 27 | { 28 | "id": "default-category-household", 29 | "name": "Household", 30 | "sortingPriority": 8 31 | }, 32 | { 33 | "id": "default-category-meat-and-fish", 34 | "name": "Meat & Fish", 35 | "sortingPriority": 3 36 | }, 37 | { 38 | "id": "default-category-pet-supplies", 39 | "name": "Pet Supplies", 40 | "sortingPriority": 10 41 | }, 42 | { 43 | "id": "default-category-sauces-and-spices", 44 | "name": "Sauces & Spices", 45 | "sortingPriority": 4 46 | }, 47 | { 48 | "id": "default-category-snacks-and-bakery", 49 | "name": "Snacks & Bakery", 50 | "sortingPriority": 6 51 | } 52 | ] -------------------------------------------------------------------------------- /assets/category/ja_JP/categories.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "default-category-dairy-and-eggs", 4 | "name": "Dairy & Eggs", 5 | "sortingPriority": 2 6 | }, 7 | { 8 | "id": "default-category-drinks", 9 | "name": "Drinks", 10 | "sortingPriority": 7 11 | }, 12 | { 13 | "id": "default-category-fruits-and-vegetables", 14 | "name": "Fruits & Vegetables", 15 | "sortingPriority": 1 16 | }, 17 | { 18 | "id": "default-category-grain-products", 19 | "name": "Grain Products", 20 | "sortingPriority": 5 21 | }, 22 | { 23 | "id": "default-category-health-and-beauty", 24 | "name": "Health & Beauty", 25 | "sortingPriority": 9 26 | }, 27 | { 28 | "id": "default-category-household", 29 | "name": "Household", 30 | "sortingPriority": 8 31 | }, 32 | { 33 | "id": "default-category-meat-and-fish", 34 | "name": "Meat & Fish", 35 | "sortingPriority": 3 36 | }, 37 | { 38 | "id": "default-category-pet-supplies", 39 | "name": "Pet Supplies", 40 | "sortingPriority": 10 41 | }, 42 | { 43 | "id": "default-category-sauces-and-spices", 44 | "name": "Sauces & Spices", 45 | "sortingPriority": 4 46 | }, 47 | { 48 | "id": "default-category-snacks-and-bakery", 49 | "name": "Snacks & Bakery", 50 | "sortingPriority": 6 51 | } 52 | ] -------------------------------------------------------------------------------- /assets/category/ko_KR/categories.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "default-category-dairy-and-eggs", 4 | "name": "Dairy & Eggs", 5 | "sortingPriority": 2 6 | }, 7 | { 8 | "id": "default-category-drinks", 9 | "name": "Drinks", 10 | "sortingPriority": 7 11 | }, 12 | { 13 | "id": "default-category-fruits-and-vegetables", 14 | "name": "Fruits & Vegetables", 15 | "sortingPriority": 1 16 | }, 17 | { 18 | "id": "default-category-grain-products", 19 | "name": "Grain Products", 20 | "sortingPriority": 5 21 | }, 22 | { 23 | "id": "default-category-health-and-beauty", 24 | "name": "Health & Beauty", 25 | "sortingPriority": 9 26 | }, 27 | { 28 | "id": "default-category-household", 29 | "name": "Household", 30 | "sortingPriority": 8 31 | }, 32 | { 33 | "id": "default-category-meat-and-fish", 34 | "name": "Meat & Fish", 35 | "sortingPriority": 3 36 | }, 37 | { 38 | "id": "default-category-pet-supplies", 39 | "name": "Pet Supplies", 40 | "sortingPriority": 10 41 | }, 42 | { 43 | "id": "default-category-sauces-and-spices", 44 | "name": "Sauces & Spices", 45 | "sortingPriority": 4 46 | }, 47 | { 48 | "id": "default-category-snacks-and-bakery", 49 | "name": "Snacks & Bakery", 50 | "sortingPriority": 6 51 | } 52 | ] -------------------------------------------------------------------------------- /assets/category/nl_NL/categories.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "default-category-dairy-and-eggs", 4 | "name": "Dairy & Eggs", 5 | "sortingPriority": 2 6 | }, 7 | { 8 | "id": "default-category-drinks", 9 | "name": "Drinks", 10 | "sortingPriority": 7 11 | }, 12 | { 13 | "id": "default-category-fruits-and-vegetables", 14 | "name": "Fruits & Vegetables", 15 | "sortingPriority": 1 16 | }, 17 | { 18 | "id": "default-category-grain-products", 19 | "name": "Grain Products", 20 | "sortingPriority": 5 21 | }, 22 | { 23 | "id": "default-category-health-and-beauty", 24 | "name": "Health & Beauty", 25 | "sortingPriority": 9 26 | }, 27 | { 28 | "id": "default-category-household", 29 | "name": "Household", 30 | "sortingPriority": 8 31 | }, 32 | { 33 | "id": "default-category-meat-and-fish", 34 | "name": "Meat & Fish", 35 | "sortingPriority": 3 36 | }, 37 | { 38 | "id": "default-category-pet-supplies", 39 | "name": "Pet Supplies", 40 | "sortingPriority": 10 41 | }, 42 | { 43 | "id": "default-category-sauces-and-spices", 44 | "name": "Sauces & Spices", 45 | "sortingPriority": 4 46 | }, 47 | { 48 | "id": "default-category-snacks-and-bakery", 49 | "name": "Snacks & Bakery", 50 | "sortingPriority": 6 51 | } 52 | ] -------------------------------------------------------------------------------- /assets/category/no_NO/categories.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "default-category-dairy-and-eggs", 4 | "name": "Dairy & Eggs", 5 | "sortingPriority": 2 6 | }, 7 | { 8 | "id": "default-category-drinks", 9 | "name": "Drinks", 10 | "sortingPriority": 7 11 | }, 12 | { 13 | "id": "default-category-fruits-and-vegetables", 14 | "name": "Fruits & Vegetables", 15 | "sortingPriority": 1 16 | }, 17 | { 18 | "id": "default-category-grain-products", 19 | "name": "Grain Products", 20 | "sortingPriority": 5 21 | }, 22 | { 23 | "id": "default-category-health-and-beauty", 24 | "name": "Health & Beauty", 25 | "sortingPriority": 9 26 | }, 27 | { 28 | "id": "default-category-household", 29 | "name": "Household", 30 | "sortingPriority": 8 31 | }, 32 | { 33 | "id": "default-category-meat-and-fish", 34 | "name": "Meat & Fish", 35 | "sortingPriority": 3 36 | }, 37 | { 38 | "id": "default-category-pet-supplies", 39 | "name": "Pet Supplies", 40 | "sortingPriority": 10 41 | }, 42 | { 43 | "id": "default-category-sauces-and-spices", 44 | "name": "Sauces & Spices", 45 | "sortingPriority": 4 46 | }, 47 | { 48 | "id": "default-category-snacks-and-bakery", 49 | "name": "Snacks & Bakery", 50 | "sortingPriority": 6 51 | } 52 | ] -------------------------------------------------------------------------------- /assets/category/pl_PL/categories.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "default-category-dairy-and-eggs", 4 | "name": "Dairy & Eggs", 5 | "sortingPriority": 2 6 | }, 7 | { 8 | "id": "default-category-drinks", 9 | "name": "Drinks", 10 | "sortingPriority": 7 11 | }, 12 | { 13 | "id": "default-category-fruits-and-vegetables", 14 | "name": "Fruits & Vegetables", 15 | "sortingPriority": 1 16 | }, 17 | { 18 | "id": "default-category-grain-products", 19 | "name": "Grain Products", 20 | "sortingPriority": 5 21 | }, 22 | { 23 | "id": "default-category-health-and-beauty", 24 | "name": "Health & Beauty", 25 | "sortingPriority": 9 26 | }, 27 | { 28 | "id": "default-category-household", 29 | "name": "Household", 30 | "sortingPriority": 8 31 | }, 32 | { 33 | "id": "default-category-meat-and-fish", 34 | "name": "Meat & Fish", 35 | "sortingPriority": 3 36 | }, 37 | { 38 | "id": "default-category-pet-supplies", 39 | "name": "Pet Supplies", 40 | "sortingPriority": 10 41 | }, 42 | { 43 | "id": "default-category-sauces-and-spices", 44 | "name": "Sauces & Spices", 45 | "sortingPriority": 4 46 | }, 47 | { 48 | "id": "default-category-snacks-and-bakery", 49 | "name": "Snacks & Bakery", 50 | "sortingPriority": 6 51 | } 52 | ] -------------------------------------------------------------------------------- /assets/category/pt_BR/categories.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "default-category-dairy-and-eggs", 4 | "name": "Laticínios e Ovos", 5 | "sortingPriority": 2 6 | }, 7 | { 8 | "id": "default-category-drinks", 9 | "name": "Bebidas", 10 | "sortingPriority": 7 11 | }, 12 | { 13 | "id": "default-category-fruits-and-vegetables", 14 | "name": "Frutas e Vegetais", 15 | "sortingPriority": 1 16 | }, 17 | { 18 | "id": "default-category-grain-products", 19 | "name": "Produtos de Grãos", 20 | "sortingPriority": 5 21 | }, 22 | { 23 | "id": "default-category-health-and-beauty", 24 | "name": "Saúde e Beleza", 25 | "sortingPriority": 9 26 | }, 27 | { 28 | "id": "default-category-household", 29 | "name": "Doméstico", 30 | "sortingPriority": 8 31 | }, 32 | { 33 | "id": "default-category-meat-and-fish", 34 | "name": "Carne e Peixe", 35 | "sortingPriority": 3 36 | }, 37 | { 38 | "id": "default-category-pet-supplies", 39 | "name": "Suprimentos para animais de estimação", 40 | "sortingPriority": 10 41 | }, 42 | { 43 | "id": "default-category-sauces-and-spices", 44 | "name": "Molhos e Temperos", 45 | "sortingPriority": 4 46 | }, 47 | { 48 | "id": "default-category-snacks-and-bakery", 49 | "name": "Lanches e Padaria", 50 | "sortingPriority": 6 51 | } 52 | ] -------------------------------------------------------------------------------- /assets/category/pt_PT/categories.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "default-category-dairy-and-eggs", 4 | "name": "Dairy & Eggs", 5 | "sortingPriority": 2 6 | }, 7 | { 8 | "id": "default-category-drinks", 9 | "name": "Drinks", 10 | "sortingPriority": 7 11 | }, 12 | { 13 | "id": "default-category-fruits-and-vegetables", 14 | "name": "Fruits & Vegetables", 15 | "sortingPriority": 1 16 | }, 17 | { 18 | "id": "default-category-grain-products", 19 | "name": "Grain Products", 20 | "sortingPriority": 5 21 | }, 22 | { 23 | "id": "default-category-health-and-beauty", 24 | "name": "Health & Beauty", 25 | "sortingPriority": 9 26 | }, 27 | { 28 | "id": "default-category-household", 29 | "name": "Household", 30 | "sortingPriority": 8 31 | }, 32 | { 33 | "id": "default-category-meat-and-fish", 34 | "name": "Meat & Fish", 35 | "sortingPriority": 3 36 | }, 37 | { 38 | "id": "default-category-pet-supplies", 39 | "name": "Pet Supplies", 40 | "sortingPriority": 10 41 | }, 42 | { 43 | "id": "default-category-sauces-and-spices", 44 | "name": "Sauces & Spices", 45 | "sortingPriority": 4 46 | }, 47 | { 48 | "id": "default-category-snacks-and-bakery", 49 | "name": "Snacks & Bakery", 50 | "sortingPriority": 6 51 | } 52 | ] -------------------------------------------------------------------------------- /assets/category/ro_RO/categories.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "default-category-dairy-and-eggs", 4 | "name": "Dairy & Eggs", 5 | "sortingPriority": 2 6 | }, 7 | { 8 | "id": "default-category-drinks", 9 | "name": "Drinks", 10 | "sortingPriority": 7 11 | }, 12 | { 13 | "id": "default-category-fruits-and-vegetables", 14 | "name": "Fruits & Vegetables", 15 | "sortingPriority": 1 16 | }, 17 | { 18 | "id": "default-category-grain-products", 19 | "name": "Grain Products", 20 | "sortingPriority": 5 21 | }, 22 | { 23 | "id": "default-category-health-and-beauty", 24 | "name": "Health & Beauty", 25 | "sortingPriority": 9 26 | }, 27 | { 28 | "id": "default-category-household", 29 | "name": "Household", 30 | "sortingPriority": 8 31 | }, 32 | { 33 | "id": "default-category-meat-and-fish", 34 | "name": "Meat & Fish", 35 | "sortingPriority": 3 36 | }, 37 | { 38 | "id": "default-category-pet-supplies", 39 | "name": "Pet Supplies", 40 | "sortingPriority": 10 41 | }, 42 | { 43 | "id": "default-category-sauces-and-spices", 44 | "name": "Sauces & Spices", 45 | "sortingPriority": 4 46 | }, 47 | { 48 | "id": "default-category-snacks-and-bakery", 49 | "name": "Snacks & Bakery", 50 | "sortingPriority": 6 51 | } 52 | ] -------------------------------------------------------------------------------- /assets/category/ru_RU/categories.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "default-category-dairy-and-eggs", 4 | "name": "Молочные продукты и яйца", 5 | "sortingPriority": 2 6 | }, 7 | { 8 | "id": "default-category-drinks", 9 | "name": "Напитки", 10 | "sortingPriority": 7 11 | }, 12 | { 13 | "id": "default-category-fruits-and-vegetables", 14 | "name": "Фрукты и овощи", 15 | "sortingPriority": 1 16 | }, 17 | { 18 | "id": "default-category-grain-products", 19 | "name": "Зерновые продукты", 20 | "sortingPriority": 5 21 | }, 22 | { 23 | "id": "default-category-health-and-beauty", 24 | "name": "Здоровье и красота", 25 | "sortingPriority": 9 26 | }, 27 | { 28 | "id": "default-category-household", 29 | "name": "Домашнее хозяйство", 30 | "sortingPriority": 8 31 | }, 32 | { 33 | "id": "default-category-meat-and-fish", 34 | "name": "Мясо и рыба", 35 | "sortingPriority": 3 36 | }, 37 | { 38 | "id": "default-category-pet-supplies", 39 | "name": "Зоотовары", 40 | "sortingPriority": 10 41 | }, 42 | { 43 | "id": "default-category-sauces-and-spices", 44 | "name": "Соусы и специи", 45 | "sortingPriority": 4 46 | }, 47 | { 48 | "id": "default-category-snacks-and-bakery", 49 | "name": "Закуски и выпечка", 50 | "sortingPriority": 6 51 | } 52 | ] -------------------------------------------------------------------------------- /assets/category/sr_SP/categories.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "default-category-dairy-and-eggs", 4 | "name": "Dairy & Eggs", 5 | "sortingPriority": 2 6 | }, 7 | { 8 | "id": "default-category-drinks", 9 | "name": "Drinks", 10 | "sortingPriority": 7 11 | }, 12 | { 13 | "id": "default-category-fruits-and-vegetables", 14 | "name": "Fruits & Vegetables", 15 | "sortingPriority": 1 16 | }, 17 | { 18 | "id": "default-category-grain-products", 19 | "name": "Grain Products", 20 | "sortingPriority": 5 21 | }, 22 | { 23 | "id": "default-category-health-and-beauty", 24 | "name": "Health & Beauty", 25 | "sortingPriority": 9 26 | }, 27 | { 28 | "id": "default-category-household", 29 | "name": "Household", 30 | "sortingPriority": 8 31 | }, 32 | { 33 | "id": "default-category-meat-and-fish", 34 | "name": "Meat & Fish", 35 | "sortingPriority": 3 36 | }, 37 | { 38 | "id": "default-category-pet-supplies", 39 | "name": "Pet Supplies", 40 | "sortingPriority": 10 41 | }, 42 | { 43 | "id": "default-category-sauces-and-spices", 44 | "name": "Sauces & Spices", 45 | "sortingPriority": 4 46 | }, 47 | { 48 | "id": "default-category-snacks-and-bakery", 49 | "name": "Snacks & Bakery", 50 | "sortingPriority": 6 51 | } 52 | ] -------------------------------------------------------------------------------- /assets/category/sv_SE/categories.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "default-category-dairy-and-eggs", 4 | "name": "Dairy & Eggs", 5 | "sortingPriority": 2 6 | }, 7 | { 8 | "id": "default-category-drinks", 9 | "name": "Drinks", 10 | "sortingPriority": 7 11 | }, 12 | { 13 | "id": "default-category-fruits-and-vegetables", 14 | "name": "Fruits & Vegetables", 15 | "sortingPriority": 1 16 | }, 17 | { 18 | "id": "default-category-grain-products", 19 | "name": "Grain Products", 20 | "sortingPriority": 5 21 | }, 22 | { 23 | "id": "default-category-health-and-beauty", 24 | "name": "Health & Beauty", 25 | "sortingPriority": 9 26 | }, 27 | { 28 | "id": "default-category-household", 29 | "name": "Household", 30 | "sortingPriority": 8 31 | }, 32 | { 33 | "id": "default-category-meat-and-fish", 34 | "name": "Meat & Fish", 35 | "sortingPriority": 3 36 | }, 37 | { 38 | "id": "default-category-pet-supplies", 39 | "name": "Pet Supplies", 40 | "sortingPriority": 10 41 | }, 42 | { 43 | "id": "default-category-sauces-and-spices", 44 | "name": "Sauces & Spices", 45 | "sortingPriority": 4 46 | }, 47 | { 48 | "id": "default-category-snacks-and-bakery", 49 | "name": "Snacks & Bakery", 50 | "sortingPriority": 6 51 | } 52 | ] -------------------------------------------------------------------------------- /assets/category/tr_TR/categories.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "default-category-dairy-and-eggs", 4 | "name": "Dairy & Eggs", 5 | "sortingPriority": 2 6 | }, 7 | { 8 | "id": "default-category-drinks", 9 | "name": "Drinks", 10 | "sortingPriority": 7 11 | }, 12 | { 13 | "id": "default-category-fruits-and-vegetables", 14 | "name": "Fruits & Vegetables", 15 | "sortingPriority": 1 16 | }, 17 | { 18 | "id": "default-category-grain-products", 19 | "name": "Grain Products", 20 | "sortingPriority": 5 21 | }, 22 | { 23 | "id": "default-category-health-and-beauty", 24 | "name": "Health & Beauty", 25 | "sortingPriority": 9 26 | }, 27 | { 28 | "id": "default-category-household", 29 | "name": "Household", 30 | "sortingPriority": 8 31 | }, 32 | { 33 | "id": "default-category-meat-and-fish", 34 | "name": "Meat & Fish", 35 | "sortingPriority": 3 36 | }, 37 | { 38 | "id": "default-category-pet-supplies", 39 | "name": "Pet Supplies", 40 | "sortingPriority": 10 41 | }, 42 | { 43 | "id": "default-category-sauces-and-spices", 44 | "name": "Sauces & Spices", 45 | "sortingPriority": 4 46 | }, 47 | { 48 | "id": "default-category-snacks-and-bakery", 49 | "name": "Snacks & Bakery", 50 | "sortingPriority": 6 51 | } 52 | ] -------------------------------------------------------------------------------- /assets/category/uk_UA/categories.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "default-category-dairy-and-eggs", 4 | "name": "Молочні продукти та яйця", 5 | "sortingPriority": 2 6 | }, 7 | { 8 | "id": "default-category-drinks", 9 | "name": "Напої", 10 | "sortingPriority": 7 11 | }, 12 | { 13 | "id": "default-category-fruits-and-vegetables", 14 | "name": "Фрукти та овочі", 15 | "sortingPriority": 1 16 | }, 17 | { 18 | "id": "default-category-grain-products", 19 | "name": "Зернові продукти", 20 | "sortingPriority": 5 21 | }, 22 | { 23 | "id": "default-category-health-and-beauty", 24 | "name": "Здоров'я і краса", 25 | "sortingPriority": 9 26 | }, 27 | { 28 | "id": "default-category-household", 29 | "name": "Домашнє господарство", 30 | "sortingPriority": 8 31 | }, 32 | { 33 | "id": "default-category-meat-and-fish", 34 | "name": "М'ясо і риба", 35 | "sortingPriority": 3 36 | }, 37 | { 38 | "id": "default-category-pet-supplies", 39 | "name": "Зоотовари", 40 | "sortingPriority": 10 41 | }, 42 | { 43 | "id": "default-category-sauces-and-spices", 44 | "name": "Соуси та спеції", 45 | "sortingPriority": 4 46 | }, 47 | { 48 | "id": "default-category-snacks-and-bakery", 49 | "name": "Закуски та випічка", 50 | "sortingPriority": 6 51 | } 52 | ] -------------------------------------------------------------------------------- /assets/category/vi_VN/categories.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "default-category-dairy-and-eggs", 4 | "name": "Dairy & Eggs", 5 | "sortingPriority": 2 6 | }, 7 | { 8 | "id": "default-category-drinks", 9 | "name": "Drinks", 10 | "sortingPriority": 7 11 | }, 12 | { 13 | "id": "default-category-fruits-and-vegetables", 14 | "name": "Fruits & Vegetables", 15 | "sortingPriority": 1 16 | }, 17 | { 18 | "id": "default-category-grain-products", 19 | "name": "Grain Products", 20 | "sortingPriority": 5 21 | }, 22 | { 23 | "id": "default-category-health-and-beauty", 24 | "name": "Health & Beauty", 25 | "sortingPriority": 9 26 | }, 27 | { 28 | "id": "default-category-household", 29 | "name": "Household", 30 | "sortingPriority": 8 31 | }, 32 | { 33 | "id": "default-category-meat-and-fish", 34 | "name": "Meat & Fish", 35 | "sortingPriority": 3 36 | }, 37 | { 38 | "id": "default-category-pet-supplies", 39 | "name": "Pet Supplies", 40 | "sortingPriority": 10 41 | }, 42 | { 43 | "id": "default-category-sauces-and-spices", 44 | "name": "Sauces & Spices", 45 | "sortingPriority": 4 46 | }, 47 | { 48 | "id": "default-category-snacks-and-bakery", 49 | "name": "Snacks & Bakery", 50 | "sortingPriority": 6 51 | } 52 | ] -------------------------------------------------------------------------------- /assets/category/zh_CN/categories.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "default-category-dairy-and-eggs", 4 | "name": "Dairy & Eggs", 5 | "sortingPriority": 2 6 | }, 7 | { 8 | "id": "default-category-drinks", 9 | "name": "Drinks", 10 | "sortingPriority": 7 11 | }, 12 | { 13 | "id": "default-category-fruits-and-vegetables", 14 | "name": "Fruits & Vegetables", 15 | "sortingPriority": 1 16 | }, 17 | { 18 | "id": "default-category-grain-products", 19 | "name": "Grain Products", 20 | "sortingPriority": 5 21 | }, 22 | { 23 | "id": "default-category-health-and-beauty", 24 | "name": "Health & Beauty", 25 | "sortingPriority": 9 26 | }, 27 | { 28 | "id": "default-category-household", 29 | "name": "Household", 30 | "sortingPriority": 8 31 | }, 32 | { 33 | "id": "default-category-meat-and-fish", 34 | "name": "Meat & Fish", 35 | "sortingPriority": 3 36 | }, 37 | { 38 | "id": "default-category-pet-supplies", 39 | "name": "Pet Supplies", 40 | "sortingPriority": 10 41 | }, 42 | { 43 | "id": "default-category-sauces-and-spices", 44 | "name": "Sauces & Spices", 45 | "sortingPriority": 4 46 | }, 47 | { 48 | "id": "default-category-snacks-and-bakery", 49 | "name": "Snacks & Bakery", 50 | "sortingPriority": 6 51 | } 52 | ] -------------------------------------------------------------------------------- /assets/category/zh_TW/categories.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "default-category-dairy-and-eggs", 4 | "name": "Dairy & Eggs", 5 | "sortingPriority": 2 6 | }, 7 | { 8 | "id": "default-category-drinks", 9 | "name": "Drinks", 10 | "sortingPriority": 7 11 | }, 12 | { 13 | "id": "default-category-fruits-and-vegetables", 14 | "name": "Fruits & Vegetables", 15 | "sortingPriority": 1 16 | }, 17 | { 18 | "id": "default-category-grain-products", 19 | "name": "Grain Products", 20 | "sortingPriority": 5 21 | }, 22 | { 23 | "id": "default-category-health-and-beauty", 24 | "name": "Health & Beauty", 25 | "sortingPriority": 9 26 | }, 27 | { 28 | "id": "default-category-household", 29 | "name": "Household", 30 | "sortingPriority": 8 31 | }, 32 | { 33 | "id": "default-category-meat-and-fish", 34 | "name": "Meat & Fish", 35 | "sortingPriority": 3 36 | }, 37 | { 38 | "id": "default-category-pet-supplies", 39 | "name": "Pet Supplies", 40 | "sortingPriority": 10 41 | }, 42 | { 43 | "id": "default-category-sauces-and-spices", 44 | "name": "Sauces & Spices", 45 | "sortingPriority": 4 46 | }, 47 | { 48 | "id": "default-category-snacks-and-bakery", 49 | "name": "Snacks & Bakery", 50 | "sortingPriority": 6 51 | } 52 | ] -------------------------------------------------------------------------------- /assets/icons/all_icons.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/all_icons.zip -------------------------------------------------------------------------------- /assets/icons/apple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/apple.png -------------------------------------------------------------------------------- /assets/icons/apricots.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/apricots.png -------------------------------------------------------------------------------- /assets/icons/avocado.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/avocado.png -------------------------------------------------------------------------------- /assets/icons/bacon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/bacon.png -------------------------------------------------------------------------------- /assets/icons/baguette.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/baguette.png -------------------------------------------------------------------------------- /assets/icons/bananas.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/bananas.png -------------------------------------------------------------------------------- /assets/icons/batteries.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/batteries.png -------------------------------------------------------------------------------- /assets/icons/beer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/beer.png -------------------------------------------------------------------------------- /assets/icons/beet.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/beet.png -------------------------------------------------------------------------------- /assets/icons/berries.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/berries.png -------------------------------------------------------------------------------- /assets/icons/bottle_narrow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/bottle_narrow.png -------------------------------------------------------------------------------- /assets/icons/bottle_plastic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/bottle_plastic.png -------------------------------------------------------------------------------- /assets/icons/bottle_small.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/bottle_small.png -------------------------------------------------------------------------------- /assets/icons/bread.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/bread.png -------------------------------------------------------------------------------- /assets/icons/broccoli.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/broccoli.png -------------------------------------------------------------------------------- /assets/icons/buns.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/buns.png -------------------------------------------------------------------------------- /assets/icons/butter.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/butter.png -------------------------------------------------------------------------------- /assets/icons/cabbage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/cabbage.png -------------------------------------------------------------------------------- /assets/icons/cake.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/cake.png -------------------------------------------------------------------------------- /assets/icons/can.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/can.png -------------------------------------------------------------------------------- /assets/icons/carrots.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/carrots.png -------------------------------------------------------------------------------- /assets/icons/cat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/cat.png -------------------------------------------------------------------------------- /assets/icons/cereal.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/cereal.png -------------------------------------------------------------------------------- /assets/icons/cheese.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/cheese.png -------------------------------------------------------------------------------- /assets/icons/cherries.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/cherries.png -------------------------------------------------------------------------------- /assets/icons/chicken.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/chicken.png -------------------------------------------------------------------------------- /assets/icons/chicken_legs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/chicken_legs.png -------------------------------------------------------------------------------- /assets/icons/chips.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/chips.png -------------------------------------------------------------------------------- /assets/icons/chocolate.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/chocolate.png -------------------------------------------------------------------------------- /assets/icons/cinnamon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/cinnamon.png -------------------------------------------------------------------------------- /assets/icons/cleaner_spray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/cleaner_spray.png -------------------------------------------------------------------------------- /assets/icons/condoms.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/condoms.png -------------------------------------------------------------------------------- /assets/icons/cookies.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/cookies.png -------------------------------------------------------------------------------- /assets/icons/cottage_cheese.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/cottage_cheese.png -------------------------------------------------------------------------------- /assets/icons/cotton_pads.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/cotton_pads.png -------------------------------------------------------------------------------- /assets/icons/cotton_swabs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/cotton_swabs.png -------------------------------------------------------------------------------- /assets/icons/cow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/cow.png -------------------------------------------------------------------------------- /assets/icons/cream.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/cream.png -------------------------------------------------------------------------------- /assets/icons/cucumber.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/cucumber.png -------------------------------------------------------------------------------- /assets/icons/deodorant.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/deodorant.png -------------------------------------------------------------------------------- /assets/icons/dog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/dog.png -------------------------------------------------------------------------------- /assets/icons/drinks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/drinks.png -------------------------------------------------------------------------------- /assets/icons/eggplant.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/eggplant.png -------------------------------------------------------------------------------- /assets/icons/eggs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/eggs.png -------------------------------------------------------------------------------- /assets/icons/fish.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/fish.png -------------------------------------------------------------------------------- /assets/icons/flour.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/flour.png -------------------------------------------------------------------------------- /assets/icons/fruits.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/fruits.png -------------------------------------------------------------------------------- /assets/icons/garlic.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/garlic.png -------------------------------------------------------------------------------- /assets/icons/gift.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/gift.png -------------------------------------------------------------------------------- /assets/icons/grain.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/grain.png -------------------------------------------------------------------------------- /assets/icons/grapes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/grapes.png -------------------------------------------------------------------------------- /assets/icons/ham.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/ham.png -------------------------------------------------------------------------------- /assets/icons/honey.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/honey.png -------------------------------------------------------------------------------- /assets/icons/ice_cream.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/ice_cream.png -------------------------------------------------------------------------------- /assets/icons/icons_change_list.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "chicken.png", 4 | "changeListVersion": 1, 5 | "isDeleted": false 6 | }, 7 | { 8 | "id": "lamb.png", 9 | "changeListVersion": 1, 10 | "isDeleted": true 11 | }, 12 | { 13 | "id": "mayonnaise.png", 14 | "changeListVersion": 1, 15 | "isDeleted": true 16 | }, 17 | { 18 | "id": "salami.png", 19 | "changeListVersion": 1, 20 | "isDeleted": false 21 | }, 22 | { 23 | "id": "sheep.png", 24 | "changeListVersion": 1, 25 | "isDeleted": false 26 | }, 27 | { 28 | "id": "tomatoes.png", 29 | "changeListVersion": 1, 30 | "isDeleted": false 31 | }, 32 | { 33 | "id": "pork.png", 34 | "changeListVersion": 1, 35 | "isDeleted": false 36 | }, 37 | { 38 | "id": "kiwi.png", 39 | "changeListVersion": 1, 40 | "isDeleted": false 41 | }, 42 | { 43 | "id": "can.png", 44 | "changeListVersion": 2, 45 | "isDeleted": false 46 | }, 47 | { 48 | "id": "baguette.png", 49 | "changeListVersion": 2, 50 | "isDeleted": false 51 | }, 52 | { 53 | "id": "buns.png", 54 | "changeListVersion": 2, 55 | "isDeleted": false 56 | }, 57 | { 58 | "id": "cotton_swabs.png", 59 | "changeListVersion": 2, 60 | "isDeleted": false 61 | }, 62 | { 63 | "id": "cotton_pads.png", 64 | "changeListVersion": 2, 65 | "isDeleted": false 66 | }, 67 | { 68 | "id": "peas.png", 69 | "changeListVersion": 2, 70 | "isDeleted": false 71 | }, 72 | { 73 | "id": "pretzels.png", 74 | "changeListVersion": 2, 75 | "isDeleted": false 76 | }, 77 | { 78 | "id": "smoothie.png", 79 | "changeListVersion": 2, 80 | "isDeleted": false 81 | }, 82 | { 83 | "id": "toast.png", 84 | "changeListVersion": 2, 85 | "isDeleted": false 86 | }, 87 | { 88 | "id": "tomato_paste.png", 89 | "changeListVersion": 2, 90 | "isDeleted": false 91 | } 92 | ] -------------------------------------------------------------------------------- /assets/icons/jam.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/jam.png -------------------------------------------------------------------------------- /assets/icons/jar_spices.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/jar_spices.png -------------------------------------------------------------------------------- /assets/icons/juice.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/juice.png -------------------------------------------------------------------------------- /assets/icons/ketchup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/ketchup.png -------------------------------------------------------------------------------- /assets/icons/kiwi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/kiwi.png -------------------------------------------------------------------------------- /assets/icons/lemon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/lemon.png -------------------------------------------------------------------------------- /assets/icons/lipstick.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/lipstick.png -------------------------------------------------------------------------------- /assets/icons/mandarins.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/mandarins.png -------------------------------------------------------------------------------- /assets/icons/meat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/meat.png -------------------------------------------------------------------------------- /assets/icons/milk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/milk.png -------------------------------------------------------------------------------- /assets/icons/minced_meat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/minced_meat.png -------------------------------------------------------------------------------- /assets/icons/mushroom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/mushroom.png -------------------------------------------------------------------------------- /assets/icons/napkins.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/napkins.png -------------------------------------------------------------------------------- /assets/icons/nuts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/nuts.png -------------------------------------------------------------------------------- /assets/icons/oats.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/oats.png -------------------------------------------------------------------------------- /assets/icons/onion.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/onion.png -------------------------------------------------------------------------------- /assets/icons/orange.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/orange.png -------------------------------------------------------------------------------- /assets/icons/pads.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/pads.png -------------------------------------------------------------------------------- /assets/icons/parsley.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/parsley.png -------------------------------------------------------------------------------- /assets/icons/pasta.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/pasta.png -------------------------------------------------------------------------------- /assets/icons/peach.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/peach.png -------------------------------------------------------------------------------- /assets/icons/pear.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/pear.png -------------------------------------------------------------------------------- /assets/icons/peas.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/peas.png -------------------------------------------------------------------------------- /assets/icons/pepper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/pepper.png -------------------------------------------------------------------------------- /assets/icons/perfume.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/perfume.png -------------------------------------------------------------------------------- /assets/icons/pills.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/pills.png -------------------------------------------------------------------------------- /assets/icons/pineapple.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/pineapple.png -------------------------------------------------------------------------------- /assets/icons/pizza.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/pizza.png -------------------------------------------------------------------------------- /assets/icons/plums.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/plums.png -------------------------------------------------------------------------------- /assets/icons/popcorn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/popcorn.png -------------------------------------------------------------------------------- /assets/icons/pork.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/pork.png -------------------------------------------------------------------------------- /assets/icons/potatoes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/potatoes.png -------------------------------------------------------------------------------- /assets/icons/pretzels.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/pretzels.png -------------------------------------------------------------------------------- /assets/icons/pumpkin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/pumpkin.png -------------------------------------------------------------------------------- /assets/icons/raspberry.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/raspberry.png -------------------------------------------------------------------------------- /assets/icons/razor.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/razor.png -------------------------------------------------------------------------------- /assets/icons/rice.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/rice.png -------------------------------------------------------------------------------- /assets/icons/rubbish_sacks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/rubbish_sacks.png -------------------------------------------------------------------------------- /assets/icons/salami.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/salami.png -------------------------------------------------------------------------------- /assets/icons/salt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/salt.png -------------------------------------------------------------------------------- /assets/icons/sauce.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/sauce.png -------------------------------------------------------------------------------- /assets/icons/sausages.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/sausages.png -------------------------------------------------------------------------------- /assets/icons/sheep.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/sheep.png -------------------------------------------------------------------------------- /assets/icons/shopping_bag.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/shopping_bag.png -------------------------------------------------------------------------------- /assets/icons/shrimp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/shrimp.png -------------------------------------------------------------------------------- /assets/icons/smoothie.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/smoothie.png -------------------------------------------------------------------------------- /assets/icons/snacks.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/snacks.png -------------------------------------------------------------------------------- /assets/icons/soap_bar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/soap_bar.png -------------------------------------------------------------------------------- /assets/icons/spices.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/spices.png -------------------------------------------------------------------------------- /assets/icons/sponge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/sponge.png -------------------------------------------------------------------------------- /assets/icons/strawberry.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/strawberry.png -------------------------------------------------------------------------------- /assets/icons/sugar_cubes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/sugar_cubes.png -------------------------------------------------------------------------------- /assets/icons/sweet_corn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/sweet_corn.png -------------------------------------------------------------------------------- /assets/icons/sweets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/sweets.png -------------------------------------------------------------------------------- /assets/icons/tangerines.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/tangerines.png -------------------------------------------------------------------------------- /assets/icons/tea.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/tea.png -------------------------------------------------------------------------------- /assets/icons/toast.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/toast.png -------------------------------------------------------------------------------- /assets/icons/toilet_paper.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/toilet_paper.png -------------------------------------------------------------------------------- /assets/icons/tomato_paste.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/tomato_paste.png -------------------------------------------------------------------------------- /assets/icons/tomatoes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/tomatoes.png -------------------------------------------------------------------------------- /assets/icons/toothbrush.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/toothbrush.png -------------------------------------------------------------------------------- /assets/icons/tube.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/tube.png -------------------------------------------------------------------------------- /assets/icons/vegetables.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/vegetables.png -------------------------------------------------------------------------------- /assets/icons/walnuts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/walnuts.png -------------------------------------------------------------------------------- /assets/icons/washing_liquid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/washing_liquid.png -------------------------------------------------------------------------------- /assets/icons/watermelon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/watermelon.png -------------------------------------------------------------------------------- /assets/icons/wine.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/wine.png -------------------------------------------------------------------------------- /assets/icons/zucchini.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/assets/icons/zucchini.png -------------------------------------------------------------------------------- /assets/product/default_products_change_list.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "default-product-apple", 4 | "changeListVersion": 1, 5 | "isDeleted": false 6 | }, 7 | { 8 | "id": "default-product-barbecue-sauce", 9 | "changeListVersion": 2, 10 | "isDeleted": true 11 | }, 12 | { 13 | "id": "default-product-lamb", 14 | "changeListVersion": 2, 15 | "isDeleted": false 16 | }, 17 | { 18 | "id": "default-product-mayonnaise", 19 | "changeListVersion": 2, 20 | "isDeleted": false 21 | }, 22 | { 23 | "id": "default-product-sauce", 24 | "changeListVersion": 2, 25 | "isDeleted": false 26 | }, 27 | { 28 | "id": "default-product-sour-cream", 29 | "changeListVersion": 2, 30 | "isDeleted": false 31 | }, 32 | { 33 | "id": "default-product-tomatoes", 34 | "changeListVersion": 2, 35 | "isDeleted": false 36 | }, 37 | { 38 | "id": "default-product-baguette", 39 | "changeListVersion": 3, 40 | "isDeleted": false 41 | }, 42 | { 43 | "id": "default-product-buns", 44 | "changeListVersion": 3, 45 | "isDeleted": false 46 | }, 47 | { 48 | "id": "default-product-cola", 49 | "changeListVersion": 3, 50 | "isDeleted": false 51 | }, 52 | { 53 | "id": "default-product-cotton-swabs", 54 | "changeListVersion": 3, 55 | "isDeleted": false 56 | }, 57 | { 58 | "id": "default-product-cotton-pads", 59 | "changeListVersion": 3, 60 | "isDeleted": false 61 | }, 62 | { 63 | "id": "default-product-margarine", 64 | "changeListVersion": 3, 65 | "isDeleted": false 66 | }, 67 | { 68 | "id": "default-product-peas", 69 | "changeListVersion": 3, 70 | "isDeleted": false 71 | }, 72 | { 73 | "id": "default-product-pretzels", 74 | "changeListVersion": 3, 75 | "isDeleted": false 76 | }, 77 | { 78 | "id": "default-product-smoothie", 79 | "changeListVersion": 3, 80 | "isDeleted": false 81 | }, 82 | { 83 | "id": "default-product-toast", 84 | "changeListVersion": 3, 85 | "isDeleted": false 86 | }, 87 | { 88 | "id": "default-product-tomato-paste", 89 | "changeListVersion": 3, 90 | "isDeleted": false 91 | } 92 | ] -------------------------------------------------------------------------------- /baselineProfile/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /baselineProfile/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.androidTest) 3 | alias(libs.plugins.jetbrainsKotlin) 4 | alias(libs.plugins.baselineprofile) 5 | } 6 | 7 | android { 8 | namespace = "com.rendox.grocerygenius.baselineprofile" 9 | compileSdk = 34 10 | 11 | compileOptions { 12 | sourceCompatibility = JavaVersion.VERSION_1_8 13 | targetCompatibility = JavaVersion.VERSION_1_8 14 | } 15 | 16 | kotlinOptions { 17 | jvmTarget = "1.8" 18 | } 19 | 20 | defaultConfig { 21 | minSdk = 28 22 | targetSdk = 34 23 | 24 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 25 | } 26 | 27 | targetProjectPath = ":app" 28 | 29 | } 30 | 31 | // This is the configuration block for the Baseline Profile plugin. 32 | // You can specify to run the generators on a managed devices or connected devices. 33 | baselineProfile { 34 | useConnectedDevices = true 35 | } 36 | 37 | dependencies { 38 | implementation(libs.androidx.junit) 39 | implementation(libs.androidx.espresso.core) 40 | implementation(libs.androidx.uiautomator) 41 | implementation(libs.androidx.benchmark.macro.junit4) 42 | } 43 | 44 | androidComponents { 45 | onVariants { v -> 46 | v.instrumentationRunnerArguments.put( 47 | "targetAppId", 48 | v.testedApks.map { v.artifacts.getBuiltArtifactsLoader().load(it)?.applicationId } 49 | ) 50 | } 51 | } -------------------------------------------------------------------------------- /baselineProfile/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /baselineProfile/src/main/java/com/rendox/grocerygenius/BenchmarkActions.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius 2 | 3 | import androidx.benchmark.macro.MacrobenchmarkScope 4 | import androidx.test.uiautomator.By 5 | import androidx.test.uiautomator.Direction 6 | 7 | fun MacrobenchmarkScope.createNewGroceryList(name: String) { 8 | val createListButtonSelector = By.text("Add new grocery list") 9 | device.waitAndFindObject(createListButtonSelector, 10_000).click() 10 | device.waitForIdle() 11 | 12 | val listNameFieldSelector = By.res("GroceryListNameField") 13 | device.waitAndFindObject(listNameFieldSelector, 5_000).text = name 14 | device.pressBack() 15 | } 16 | 17 | fun MacrobenchmarkScope.addGroceries() { 18 | val groceryGrid = device.waitAndFindObject(By.res("LazyGroceryGrid"), 5_000) 19 | groceryGrid.findObjects(By.res("GroceryGridItem")).forEach { it.click() } 20 | device.waitForIdle() 21 | } 22 | 23 | fun MacrobenchmarkScope.groceryGridScrollDown() { 24 | val feedList = device.waitAndFindObject(By.res("grouped_lazy_grocery_grid"), 5_000) 25 | feedList.setGestureMargin(device.displayWidth / 5) 26 | feedList.fling(Direction.DOWN) 27 | } 28 | 29 | fun MacrobenchmarkScope.navigateToCategory() { 30 | groceryGridScrollDown() 31 | val categoryButton = device.waitAndFindObject( 32 | selector = By.text("Health & Beauty"), 33 | timeout = 5_000, 34 | ) 35 | categoryButton.click() 36 | } 37 | 38 | fun MacrobenchmarkScope.navigateToGroceryList(groceryListName: String) { 39 | val groceryListButton = device.waitAndFindObject( 40 | selector = By.text(groceryListName), 41 | timeout = 10_000, 42 | ) 43 | groceryListButton.click() 44 | device.waitForIdle() 45 | } 46 | 47 | fun MacrobenchmarkScope.deleteGroceryList() { 48 | device.waitAndFindObject(By.desc("Delete"), 5_000).click() 49 | device.waitForIdle() 50 | device.waitAndFindObject(By.text("Delete"), 5_000).click() 51 | } 52 | 53 | fun MacrobenchmarkScope.navigateToSettings() { 54 | device.waitAndFindObject(By.desc("Settings"), 5_000).click() 55 | device.waitForIdle() 56 | } 57 | 58 | -------------------------------------------------------------------------------- /baselineProfile/src/main/java/com/rendox/grocerygenius/BenchmarkUtils.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius 2 | 3 | import androidx.test.uiautomator.BySelector 4 | import androidx.test.uiautomator.Direction 5 | import androidx.test.uiautomator.UiDevice 6 | import androidx.test.uiautomator.UiObject2 7 | import androidx.test.uiautomator.Until 8 | 9 | /** 10 | * Waits until an object with [selector] if visible on screen and returns the object. 11 | * If the element is not available in [timeout], throws [AssertionError] 12 | */ 13 | fun UiDevice.waitAndFindObject( 14 | selector: BySelector, 15 | timeout: Long, 16 | throwTimeOutException: Boolean = true, 17 | ): UiObject2 { 18 | if (!wait(Until.hasObject(selector), timeout) && throwTimeOutException) { 19 | throw AssertionError("Element not found on screen in ${timeout}ms (selector=$selector)") 20 | } 21 | return findObject(selector) 22 | } 23 | 24 | fun UiDevice.flingElementDownUp(element: UiObject2) { 25 | // Set some margin from the sides to prevent triggering system navigation 26 | element.setGestureMargin(displayWidth / 5) 27 | 28 | element.fling(Direction.DOWN) 29 | waitForIdle() 30 | element.fling(Direction.UP) 31 | } 32 | -------------------------------------------------------------------------------- /baselineProfile/src/main/java/com/rendox/grocerygenius/GroceryGeniusBenchmark.kt: -------------------------------------------------------------------------------- 1 | package com.rendox.grocerygenius 2 | 3 | import androidx.benchmark.macro.CompilationMode 4 | import androidx.benchmark.macro.FrameTimingMetric 5 | import androidx.benchmark.macro.StartupMode 6 | import androidx.benchmark.macro.junit4.MacrobenchmarkRule 7 | import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner 8 | import org.junit.Rule 9 | import org.junit.Test 10 | import org.junit.runner.RunWith 11 | 12 | @RunWith(AndroidJUnit4ClassRunner::class) 13 | class GroceryGeniusBenchmark { 14 | @get:Rule 15 | val benchmarkRule = MacrobenchmarkRule() 16 | 17 | @Test 18 | fun groceryGeniusBenchmarkCompilationNone() = benchmark(CompilationMode.None()) 19 | 20 | @Test 21 | fun groceryGeniusBenchmarkCompilationBaselineProfile() = benchmark(CompilationMode.Partial()) 22 | 23 | @Test 24 | fun groceryGeniusBenchmarkCompilationFull() = benchmark(CompilationMode.Full()) 25 | 26 | private fun benchmark(compilationMode: CompilationMode) = benchmarkRule.measureRepeated( 27 | packageName = "com.rendox.grocerygenius", 28 | metrics = listOf(FrameTimingMetric()), 29 | iterations = 8, 30 | startupMode = StartupMode.WARM, 31 | setupBlock = { 32 | pressHome() 33 | startActivityAndWait() 34 | }, 35 | compilationMode = compilationMode, 36 | ) { 37 | val groceryListName = "TestGroceryList" 38 | createNewGroceryList(groceryListName) 39 | navigateToCategory() 40 | addGroceries() 41 | device.pressBack() 42 | device.waitForIdle() 43 | device.pressBack() 44 | navigateToSettings() 45 | device.pressBack() 46 | navigateToGroceryList(groceryListName) 47 | device.waitForIdle() 48 | deleteGroceryList() 49 | device.waitForIdle() 50 | } 51 | } -------------------------------------------------------------------------------- /baselineProfile/src/main/java/com/rendox/grocerygenius/StartupBaselineProfile.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.rendox.grocerygenius 18 | 19 | import androidx.benchmark.macro.junit4.BaselineProfileRule 20 | import androidx.test.platform.app.InstrumentationRegistry 21 | import org.junit.Rule 22 | import org.junit.Test 23 | 24 | /** 25 | * Baseline Profile for app startup. This profile also enables using [Dex Layout Optimizations](https://developer.android.com/topic/performance/baselineprofiles/dex-layout-optimizations) 26 | * via the `includeInStartupProfile` parameter. 27 | */ 28 | class StartupBaselineProfile { 29 | @get:Rule val baselineProfileRule = BaselineProfileRule() 30 | 31 | @Test 32 | fun generate() = baselineProfileRule.collect( 33 | packageName = InstrumentationRegistry.getArguments().getString("targetAppId") 34 | ?: throw Exception("targetAppId not passed as instrumentation runner arg"), 35 | includeInStartupProfile = true, 36 | profileBlock = { 37 | pressHome() 38 | startActivityAndWait() 39 | } 40 | ) 41 | } 42 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | plugins { 3 | alias(libs.plugins.androidApplication) apply false 4 | alias(libs.plugins.jetbrainsKotlin) apply false 5 | alias(libs.plugins.hiltPlugin) apply false 6 | alias(libs.plugins.roomPlugin) apply false 7 | alias(libs.plugins.androidTest) apply false 8 | alias(libs.plugins.baselineprofile) apply false 9 | alias(libs.plugins.ktlint) apply false 10 | } -------------------------------------------------------------------------------- /crowdin.yml: -------------------------------------------------------------------------------- 1 | files: 2 | - source: app/src/main/res/values/strings.xml 3 | translation: /app/src/main/res/values-%android_code%/strings.xml 4 | translate_attributes: 0 5 | content_segmentation: 0 6 | - source: /assets/category/categories.json 7 | translation: /assets/category/%locale_with_underscore%/categories.json 8 | - source: /assets/product/default_products.json 9 | translation: /assets/product/%locale_with_underscore%/default_products.json 10 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 10 | # When configured, Gradle will run in incubating parallel mode. 11 | # This option should only be used with decoupled projects. More details, visit 12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 13 | # org.gradle.parallel=true 14 | # AndroidX package structure to make it clearer which packages are bundled with the 15 | # Android operating system, and which are packaged with your app's APK 16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 17 | android.useAndroidX=true 18 | # Kotlin code style for this project: "official" or "obsolete": 19 | kotlin.code.style=official 20 | # Enables namespacing of each library's R class so that its R class includes only the 21 | # resources declared in the library itself and none from the library's dependencies, 22 | # thereby reducing the size of the R class for that library 23 | android.nonTransitiveRClass=true 24 | org.gradle.configuration-cache=true 25 | # Use this flag carefully, in case some of the plugins are not fully compatible. 26 | org.gradle.configuration-cache.problems=warn -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sun Mar 03 21:51:30 EET 2024 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /images/banners/banner_github.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/images/banners/banner_github.png -------------------------------------------------------------------------------- /images/banners/banner_google_play.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/images/banners/banner_google_play.png -------------------------------------------------------------------------------- /images/banners/banner_izzy_on_droid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/images/banners/banner_izzy_on_droid.png -------------------------------------------------------------------------------- /images/readme/feature_customization.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/images/readme/feature_customization.png -------------------------------------------------------------------------------- /images/readme/feature_search_groceries.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/images/readme/feature_search_groceries.png -------------------------------------------------------------------------------- /images/readme/feature_separate_lists.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/images/readme/feature_separate_lists.png -------------------------------------------------------------------------------- /images/readme/readme_cover_image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DanielRendox/GroceryGenius/dae0828bf9e6b99bac4371bd87ca948b80bb6966/images/readme/readme_cover_image.png -------------------------------------------------------------------------------- /lint.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | gradlePluginPortal() 6 | } 7 | } 8 | dependencyResolutionManagement { 9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 10 | repositories { 11 | google() 12 | mavenCentral() 13 | } 14 | } 15 | 16 | rootProject.name = "Grocery Genius" 17 | include(":app") 18 | include(":baselineprofile") 19 | --------------------------------------------------------------------------------