From 9ed43020a97f6bc0166e62a9b9090568ba1d98cb Mon Sep 17 00:00:00 2001 From: Krunal Patel Date: Mon, 1 May 2023 10:36:30 +0530 Subject: [PATCH 1/2] Add: UI Components Bump version - Custom View - Radio Button - Floating Action Button - Edit Text - Text Input Layout - Tab bar - View pager - App Bar - Relative Layout - Linear Layout - Frame Layout - Chip - Chip Group - Constraint Layout - Relatively - Circular - Chain - Bias - Weight - Guideline - Barrier - Time Picker - Date Picker - Time Picker - Progress bar - Seek Bar - Spinner - Custom drop-down - Data binding - View binding - merge, include with binding - Overview of livedata - observer & observable - Splash screen and restrictions on android 12 - Spannable text - Link text - Clickable text - Card screen design - Operations bottom sheet - Custom bottom sheet - Custom Time line view - Theme - Complete remaining screen - Coordinator layout - Navigation and fragments - KT screen - Market screen - Exchange screen - Custom divider - Add circular progress - App bar - Notification icon - Donut chart attrs - Complete design - Make button functional --- Demo/app/build.gradle | 21 +- Demo/app/src/main/AndroidManifest.xml | 22 +- .../java/com/krunal/demo/DemoApplication.kt | 22 + .../main/java/com/krunal/demo/MainActivity.kt | 9 +- .../com/krunal/demo/MainActivityViewModel.kt | 9 +- .../com/krunal/demo/UIComponentsActivity.kt | 31 ++ .../stackexchange/StackExchangeActivity.kt | 95 ++++ .../StackExchangeActivityViewModel.kt | 11 + .../exchange/ExchangeFragment.kt | 72 +++ .../exchange/ExchangeFragmentViewModel.kt | 75 +++ .../stackexchange/market/MarketFragment.kt | 49 ++ .../market/MarketFragmentViewModel.kt | 67 +++ .../stackexchange/models/ExchangeModel.kt | 5 + .../demo/stackexchange/models/ShareDetails.kt | 52 +++ .../stackexchange/models/TitleCardModel.kt | 5 + .../demo/stackexchange/models/Wallet.kt | 19 + .../demo/stackexchange/utils/Formatter.kt | 13 + .../demo/stackexchange/views/Divider.kt | 83 ++++ .../demo/stackexchange/views/DonutChart.kt | 123 +++++ .../views/HexagonFloatingActionButton.kt | 39 ++ .../stackexchange/wallet/WalletFragment.kt | 28 ++ .../wallet/WalletFragmentViewModel.kt | 34 ++ .../demo/uicomponents/AppBarFragment.kt | 36 ++ .../demo/uicomponents/ButtonFragment.kt | 57 +++ .../demo/uicomponents/CheckboxFragment.kt | 58 +++ .../krunal/demo/uicomponents/ChipFragment.kt | 55 +++ .../uicomponents/CoordinatorLayoutFragment.kt | 45 ++ .../demo/uicomponents/CustomViewFragment.kt | 6 + .../demo/uicomponents/EditTextFragment.kt | 67 +++ .../krunal/demo/uicomponents/FabFragment.kt | 69 +++ .../demo/uicomponents/FrameLayoutFragment.kt | 8 + .../demo/uicomponents/LinearLayoutFragment.kt | 8 + .../demo/uicomponents/ProgressBarFragment.kt | 41 ++ .../krunal/demo/uicomponents/RadioFragment.kt | 52 +++ .../uicomponents/RelativeLayoutFragment.kt | 8 + .../demo/uicomponents/SliderFragment.kt | 50 ++ .../demo/uicomponents/SnackBarFragment.kt | 69 +++ .../krunal/demo/uicomponents/SpanFragment.kt | 103 +++++ .../demo/uicomponents/SpinnerFragment.kt | 64 +++ .../demo/uicomponents/TabLayoutFragment.kt | 41 ++ .../krunal/demo/uicomponents/ThemeFragment.kt | 57 +++ .../krunal/demo/uicomponents/ToastFragment.kt | 74 +++ .../uicomponents/adapters/ThemeAdapter.kt | 33 ++ .../uicomponents/adapters/TimezoneAdapter.kt | 29 ++ .../adapters/ViewBindingAdapter.kt | 61 +++ .../uicomponents/adapters/ViewPagerAdapter.kt | 16 + .../binding/DataBindingFragment.kt | 26 ++ .../binding/DataBingingViewModel.kt | 34 ++ .../uicomponents/cardscreen/CardFragment.kt | 117 +++++ .../cardscreen/CardFragmentViewModel.kt | 29 ++ .../constraintLayouts/ChainBiasFragment.kt | 5 + .../constraintLayouts/CircularFragment.kt | 32 ++ .../GuidelineBarrierFragment.kt | 5 + .../constraintLayouts/RelativeFragment.kt | 6 + .../dialogs/MyDatePickerDialog.kt | 21 + .../extentions/ActivityExtentions.kt | 21 + .../extentions/LinearLayoutExtentions.kt | 29 ++ .../extentions/NumberExtentions.kt | 10 + .../extentions/TypedArrayExtentions.kt | 15 + .../uicomponents/extentions/ViewExtentions.kt | 10 + .../uicomponents/helpers/PreferenceHelper.kt | 69 +++ .../demo/uicomponents/helpers/ThemeHelper.kt | 79 ++++ .../demo/uicomponents/models/CardDetail.kt | 18 + .../uicomponents/models/DrawableResource.kt | 8 + .../krunal/demo/uicomponents/models/Name.kt | 3 + .../krunal/demo/uicomponents/models/Theme.kt | 11 + .../uicomponents/models/enums/AccentColor.kt | 5 + .../uicomponents/models/enums/CardType.kt | 11 + .../uicomponents/models/enums/ThemeMode.kt | 5 + .../uicomponents/picker/DatePickerFragment.kt | 31 ++ .../uicomponents/picker/TimePickerFragment.kt | 62 +++ .../sheets/OperationsBottomSheetFragment.kt | 35 ++ .../tablayoutfragments/CallsFragment.kt | 6 + .../tablayoutfragments/ChatFragment.kt | 11 + .../tablayoutfragments/HomeFragment.kt | 66 +++ .../demo/uicomponents/utils/PreferenceKeys.kt | 11 + .../demo/uicomponents/views/CustomView.kt | 41 ++ .../uicomponents/views/HistoryLineView.kt | 70 +++ .../uicomponents/views/MyClickableSpan.kt | 18 + Demo/app/src/main/res/color/chip_card.xml | 5 + .../app/src/main/res/color/chip_card_text.xml | 5 + .../main/res/color/chip_operations_time.xml | 5 + .../color/stack_exchange_chip_background.xml | 5 + .../main/res/drawable/calendar_banner.jpeg | Bin 0 -> 46789 bytes .../src/main/res/drawable/circle_image.xml | 5 + .../main/res/drawable/circular_progress.xml | 16 + .../src/main/res/drawable/dark_forest.jpeg | Bin 0 -> 207162 bytes .../src/main/res/drawable/gradient_button.xml | 13 + .../main/res/drawable/gradient_progress.xml | 24 + .../res/drawable/highlighted_background.xml | 5 + Demo/app/src/main/res/drawable/ic_add.xml | 5 + .../src/main/res/drawable/ic_add_filled.xml | 5 + Demo/app/src/main/res/drawable/ic_android.xml | 5 + .../src/main/res/drawable/ic_avax_logo.xml | 9 + Demo/app/src/main/res/drawable/ic_back.xml | 5 + .../app/src/main/res/drawable/ic_calendar.xml | 5 + Demo/app/src/main/res/drawable/ic_call.xml | 5 + Demo/app/src/main/res/drawable/ic_card.xml | 5 + Demo/app/src/main/res/drawable/ic_chat.xml | 5 + Demo/app/src/main/res/drawable/ic_check.xml | 10 + Demo/app/src/main/res/drawable/ic_circle.xml | 10 + Demo/app/src/main/res/drawable/ic_cross.xml | 5 + .../src/main/res/drawable/ic_down_arrow.xml | 5 + .../res/drawable/ic_down_arrow_outlined.xml | 5 + Demo/app/src/main/res/drawable/ic_email.xml | 5 + .../app/src/main/res/drawable/ic_exchange.xml | 9 + Demo/app/src/main/res/drawable/ic_feed.xml | 5 + Demo/app/src/main/res/drawable/ic_female.xml | 9 + Demo/app/src/main/res/drawable/ic_food.xml | 38 ++ .../app/src/main/res/drawable/ic_gasoline.xml | 6 + .../main/res/drawable/ic_heart_outlined.xml | 4 + Demo/app/src/main/res/drawable/ic_home.xml | 5 + Demo/app/src/main/res/drawable/ic_image.xml | 5 + Demo/app/src/main/res/drawable/ic_info.xml | 5 + .../src/main/res/drawable/ic_line_shader.xml | 4 + .../src/main/res/drawable/ic_link_logo.xml | 4 + Demo/app/src/main/res/drawable/ic_male.xml | 12 + Demo/app/src/main/res/drawable/ic_market.xml | 4 + .../src/main/res/drawable/ic_mastercard.xml | 11 + Demo/app/src/main/res/drawable/ic_menu.xml | 5 + Demo/app/src/main/res/drawable/ic_next.xml | 5 + .../src/main/res/drawable/ic_notification.xml | 4 + .../src/main/res/drawable/ic_other_gender.xml | 9 + .../src/main/res/drawable/ic_paperplane.xml | 5 + Demo/app/src/main/res/drawable/ic_pause.xml | 5 + .../main/res/drawable/ic_pause_outlined.xml | 5 + Demo/app/src/main/res/drawable/ic_pen.xml | 5 + Demo/app/src/main/res/drawable/ic_play.xml | 5 + .../main/res/drawable/ic_play_outlined.xml | 5 + .../app/src/main/res/drawable/ic_previous.xml | 5 + Demo/app/src/main/res/drawable/ic_profile.xml | 4 + Demo/app/src/main/res/drawable/ic_remove.xml | 5 + .../src/main/res/drawable/ic_right_arrow.xml | 5 + .../src/main/res/drawable/ic_rose_logo.xml | 18 + Demo/app/src/main/res/drawable/ic_search.xml | 10 + Demo/app/src/main/res/drawable/ic_splash.xml | 54 +++ Demo/app/src/main/res/drawable/ic_swap.xml | 5 + .../app/src/main/res/drawable/ic_timezone.xml | 5 + .../src/main/res/drawable/ic_two_circle.xml | 4 + .../app/src/main/res/drawable/ic_up_arrow.xml | 5 + Demo/app/src/main/res/drawable/ic_wallet.xml | 5 + .../src/main/res/drawable/image_toggle.xml | 5 + Demo/app/src/main/res/drawable/img.png | Bin 0 -> 721000 bytes Demo/app/src/main/res/drawable/profile.jpeg | Bin 0 -> 496125 bytes .../main/res/drawable/progress_vertical.xml | 20 + .../res/drawable/progress_vertical_graph.xml | 20 + .../main/res/drawable/rounded_bottom_bar.xml | 7 + .../res/drawable/rounded_bottom_sheet.xml | 13 + .../src/main/res/drawable/rounded_image.xml | 6 + .../main/res/drawable/rounded_tab_layout.xml | 7 + .../src/main/res/drawable/rounded_toast.xml | 11 + .../rounded_transparent_background.xml | 5 + .../res/drawable/running_up_that_hill.png | Bin 0 -> 2007498 bytes .../src/main/res/drawable/selected_theme.xml | 20 + .../res/drawable/tab_selected_background.xml | 5 + .../src/main/res/drawable/time_banner.jpeg | Bin 0 -> 51038 bytes .../app/src/main/res/drawable/trade_graph.png | Bin 0 -> 5528 bytes .../app/src/main/res/layout/activity_main.xml | 44 +- .../res/layout/activity_stack_exchange.xml | 87 ++++ .../main/res/layout/activity_uicomponents.xml | 18 + Demo/app/src/main/res/layout/card_chip.xml | 7 + Demo/app/src/main/res/layout/card_layout.xml | 55 +++ .../main/res/layout/company_amount_card.xml | 78 ++++ Demo/app/src/main/res/layout/custom_toast.xml | 23 + .../app/src/main/res/layout/exchange_card.xml | 130 ++++++ .../src/main/res/layout/exchange_layout.xml | 98 ++++ .../src/main/res/layout/fragment_app_bar.xml | 54 +++ .../src/main/res/layout/fragment_button.xml | 149 ++++++ .../src/main/res/layout/fragment_calls.xml | 17 + .../app/src/main/res/layout/fragment_card.xml | 112 +++++ .../app/src/main/res/layout/fragment_chat.xml | 17 + .../src/main/res/layout/fragment_checkbox.xml | 55 +++ .../app/src/main/res/layout/fragment_chip.xml | 286 ++++++++++++ .../res/layout/fragment_cl_chain_bias.xml | 75 +++ .../main/res/layout/fragment_cl_circular.xml | 119 +++++ .../layout/fragment_cl_guideline_barrier.xml | 65 +++ .../main/res/layout/fragment_cl_relative.xml | 118 +++++ .../layout/fragment_coordinator_layout.xml | 80 ++++ .../main/res/layout/fragment_custom_view.xml | 17 + .../main/res/layout/fragment_data_binding.xml | 44 ++ .../main/res/layout/fragment_date_picker.xml | 24 + .../main/res/layout/fragment_edit_text.xml | 120 +++++ .../src/main/res/layout/fragment_exchange.xml | 134 ++++++ Demo/app/src/main/res/layout/fragment_fab.xml | 99 ++++ .../main/res/layout/fragment_frame_layout.xml | 79 ++++ .../app/src/main/res/layout/fragment_home.xml | 97 ++++ .../res/layout/fragment_linear_layout.xml | 104 +++++ .../src/main/res/layout/fragment_market.xml | 299 ++++++++++++ .../fragment_operations_bottom_sheet.xml | 247 ++++++++++ .../main/res/layout/fragment_progress_bar.xml | 128 +++++ .../src/main/res/layout/fragment_radio.xml | 110 +++++ .../res/layout/fragment_relative_layout.xml | 122 +++++ .../src/main/res/layout/fragment_slider.xml | 118 +++++ .../src/main/res/layout/fragment_snackbar.xml | 57 +++ .../app/src/main/res/layout/fragment_span.xml | 33 ++ .../src/main/res/layout/fragment_spinner.xml | 119 +++++ .../main/res/layout/fragment_tab_layout.xml | 27 ++ .../src/main/res/layout/fragment_theme.xml | 35 ++ .../main/res/layout/fragment_time_picker.xml | 54 +++ .../src/main/res/layout/fragment_toast.xml | 35 ++ .../src/main/res/layout/fragment_wallet.xml | 252 ++++++++++ Demo/app/src/main/res/layout/name_layout.xml | 62 +++ .../src/main/res/layout/name_merge_layout.xml | 62 +++ .../main/res/layout/notification_layout.xml | 26 ++ .../res/layout/operations_card_layout.xml | 314 +++++++++++++ .../src/main/res/layout/portfolio_layout.xml | 102 ++++ Demo/app/src/main/res/layout/price_layout.xml | 140 ++++++ .../res/layout/recent_operation_layout.xml | 51 ++ .../main/res/layout/share_details_layout.xml | 204 ++++++++ Demo/app/src/main/res/layout/theme_layout.xml | 29 ++ .../src/main/res/layout/timezone_spinner.xml | 23 + Demo/app/src/main/res/layout/title_card.xml | 65 +++ .../main/res/layout/total_price_layout.xml | 156 +++++++ .../res/menu/card_bottom_navigation_items.xml | 15 + .../res/menu/stack_exchange_app_bar_menu.xml | 11 + .../res/menu/stack_exchange_bottom_menu.xml | 14 + Demo/app/src/main/res/menu/toolbar_menu.xml | 28 ++ .../src/main/res/menu/toolbar_search_menu.xml | 20 + Demo/app/src/main/res/values-night/themes.xml | 253 ++++++++++ Demo/app/src/main/res/values/arrays.xml | 90 ++++ Demo/app/src/main/res/values/attrs.xml | 32 ++ Demo/app/src/main/res/values/colors.xml | 437 ++++++++++++++++++ Demo/app/src/main/res/values/dimens.xml | 14 + Demo/app/src/main/res/values/splash.xml | 9 + Demo/app/src/main/res/values/strings.xml | 178 +++++++ Demo/app/src/main/res/values/styles.xml | 142 ++++++ Demo/app/src/main/res/values/themes.xml | 305 +++++++++++- Demo/build.gradle | 4 +- Demo/gradle/wrapper/gradle-wrapper.properties | 6 +- 229 files changed, 10294 insertions(+), 40 deletions(-) create mode 100644 Demo/app/src/main/java/com/krunal/demo/DemoApplication.kt create mode 100644 Demo/app/src/main/java/com/krunal/demo/UIComponentsActivity.kt create mode 100644 Demo/app/src/main/java/com/krunal/demo/stackexchange/StackExchangeActivity.kt create mode 100644 Demo/app/src/main/java/com/krunal/demo/stackexchange/StackExchangeActivityViewModel.kt create mode 100644 Demo/app/src/main/java/com/krunal/demo/stackexchange/exchange/ExchangeFragment.kt create mode 100644 Demo/app/src/main/java/com/krunal/demo/stackexchange/exchange/ExchangeFragmentViewModel.kt create mode 100644 Demo/app/src/main/java/com/krunal/demo/stackexchange/market/MarketFragment.kt create mode 100644 Demo/app/src/main/java/com/krunal/demo/stackexchange/market/MarketFragmentViewModel.kt create mode 100644 Demo/app/src/main/java/com/krunal/demo/stackexchange/models/ExchangeModel.kt create mode 100644 Demo/app/src/main/java/com/krunal/demo/stackexchange/models/ShareDetails.kt create mode 100644 Demo/app/src/main/java/com/krunal/demo/stackexchange/models/TitleCardModel.kt create mode 100644 Demo/app/src/main/java/com/krunal/demo/stackexchange/models/Wallet.kt create mode 100644 Demo/app/src/main/java/com/krunal/demo/stackexchange/utils/Formatter.kt create mode 100644 Demo/app/src/main/java/com/krunal/demo/stackexchange/views/Divider.kt create mode 100644 Demo/app/src/main/java/com/krunal/demo/stackexchange/views/DonutChart.kt create mode 100644 Demo/app/src/main/java/com/krunal/demo/stackexchange/views/HexagonFloatingActionButton.kt create mode 100644 Demo/app/src/main/java/com/krunal/demo/stackexchange/wallet/WalletFragment.kt create mode 100644 Demo/app/src/main/java/com/krunal/demo/stackexchange/wallet/WalletFragmentViewModel.kt create mode 100644 Demo/app/src/main/java/com/krunal/demo/uicomponents/AppBarFragment.kt create mode 100644 Demo/app/src/main/java/com/krunal/demo/uicomponents/ButtonFragment.kt create mode 100644 Demo/app/src/main/java/com/krunal/demo/uicomponents/CheckboxFragment.kt create mode 100644 Demo/app/src/main/java/com/krunal/demo/uicomponents/ChipFragment.kt create mode 100644 Demo/app/src/main/java/com/krunal/demo/uicomponents/CoordinatorLayoutFragment.kt create mode 100644 Demo/app/src/main/java/com/krunal/demo/uicomponents/CustomViewFragment.kt create mode 100644 Demo/app/src/main/java/com/krunal/demo/uicomponents/EditTextFragment.kt create mode 100644 Demo/app/src/main/java/com/krunal/demo/uicomponents/FabFragment.kt create mode 100644 Demo/app/src/main/java/com/krunal/demo/uicomponents/FrameLayoutFragment.kt create mode 100644 Demo/app/src/main/java/com/krunal/demo/uicomponents/LinearLayoutFragment.kt create mode 100644 Demo/app/src/main/java/com/krunal/demo/uicomponents/ProgressBarFragment.kt create mode 100644 Demo/app/src/main/java/com/krunal/demo/uicomponents/RadioFragment.kt create mode 100644 Demo/app/src/main/java/com/krunal/demo/uicomponents/RelativeLayoutFragment.kt create mode 100644 Demo/app/src/main/java/com/krunal/demo/uicomponents/SliderFragment.kt create mode 100644 Demo/app/src/main/java/com/krunal/demo/uicomponents/SnackBarFragment.kt create mode 100644 Demo/app/src/main/java/com/krunal/demo/uicomponents/SpanFragment.kt create mode 100644 Demo/app/src/main/java/com/krunal/demo/uicomponents/SpinnerFragment.kt create mode 100644 Demo/app/src/main/java/com/krunal/demo/uicomponents/TabLayoutFragment.kt create mode 100644 Demo/app/src/main/java/com/krunal/demo/uicomponents/ThemeFragment.kt create mode 100644 Demo/app/src/main/java/com/krunal/demo/uicomponents/ToastFragment.kt create mode 100644 Demo/app/src/main/java/com/krunal/demo/uicomponents/adapters/ThemeAdapter.kt create mode 100644 Demo/app/src/main/java/com/krunal/demo/uicomponents/adapters/TimezoneAdapter.kt create mode 100644 Demo/app/src/main/java/com/krunal/demo/uicomponents/adapters/ViewBindingAdapter.kt create mode 100644 Demo/app/src/main/java/com/krunal/demo/uicomponents/adapters/ViewPagerAdapter.kt create mode 100644 Demo/app/src/main/java/com/krunal/demo/uicomponents/binding/DataBindingFragment.kt create mode 100644 Demo/app/src/main/java/com/krunal/demo/uicomponents/binding/DataBingingViewModel.kt create mode 100644 Demo/app/src/main/java/com/krunal/demo/uicomponents/cardscreen/CardFragment.kt create mode 100644 Demo/app/src/main/java/com/krunal/demo/uicomponents/cardscreen/CardFragmentViewModel.kt create mode 100644 Demo/app/src/main/java/com/krunal/demo/uicomponents/constraintLayouts/ChainBiasFragment.kt create mode 100644 Demo/app/src/main/java/com/krunal/demo/uicomponents/constraintLayouts/CircularFragment.kt create mode 100644 Demo/app/src/main/java/com/krunal/demo/uicomponents/constraintLayouts/GuidelineBarrierFragment.kt create mode 100644 Demo/app/src/main/java/com/krunal/demo/uicomponents/constraintLayouts/RelativeFragment.kt create mode 100644 Demo/app/src/main/java/com/krunal/demo/uicomponents/dialogs/MyDatePickerDialog.kt create mode 100644 Demo/app/src/main/java/com/krunal/demo/uicomponents/extentions/ActivityExtentions.kt create mode 100644 Demo/app/src/main/java/com/krunal/demo/uicomponents/extentions/LinearLayoutExtentions.kt create mode 100644 Demo/app/src/main/java/com/krunal/demo/uicomponents/extentions/NumberExtentions.kt create mode 100644 Demo/app/src/main/java/com/krunal/demo/uicomponents/extentions/TypedArrayExtentions.kt create mode 100644 Demo/app/src/main/java/com/krunal/demo/uicomponents/extentions/ViewExtentions.kt create mode 100644 Demo/app/src/main/java/com/krunal/demo/uicomponents/helpers/PreferenceHelper.kt create mode 100644 Demo/app/src/main/java/com/krunal/demo/uicomponents/helpers/ThemeHelper.kt create mode 100644 Demo/app/src/main/java/com/krunal/demo/uicomponents/models/CardDetail.kt create mode 100644 Demo/app/src/main/java/com/krunal/demo/uicomponents/models/DrawableResource.kt create mode 100644 Demo/app/src/main/java/com/krunal/demo/uicomponents/models/Name.kt create mode 100644 Demo/app/src/main/java/com/krunal/demo/uicomponents/models/Theme.kt create mode 100644 Demo/app/src/main/java/com/krunal/demo/uicomponents/models/enums/AccentColor.kt create mode 100644 Demo/app/src/main/java/com/krunal/demo/uicomponents/models/enums/CardType.kt create mode 100644 Demo/app/src/main/java/com/krunal/demo/uicomponents/models/enums/ThemeMode.kt create mode 100644 Demo/app/src/main/java/com/krunal/demo/uicomponents/picker/DatePickerFragment.kt create mode 100644 Demo/app/src/main/java/com/krunal/demo/uicomponents/picker/TimePickerFragment.kt create mode 100644 Demo/app/src/main/java/com/krunal/demo/uicomponents/sheets/OperationsBottomSheetFragment.kt create mode 100644 Demo/app/src/main/java/com/krunal/demo/uicomponents/tablayoutfragments/CallsFragment.kt create mode 100644 Demo/app/src/main/java/com/krunal/demo/uicomponents/tablayoutfragments/ChatFragment.kt create mode 100644 Demo/app/src/main/java/com/krunal/demo/uicomponents/tablayoutfragments/HomeFragment.kt create mode 100644 Demo/app/src/main/java/com/krunal/demo/uicomponents/utils/PreferenceKeys.kt create mode 100644 Demo/app/src/main/java/com/krunal/demo/uicomponents/views/CustomView.kt create mode 100644 Demo/app/src/main/java/com/krunal/demo/uicomponents/views/HistoryLineView.kt create mode 100644 Demo/app/src/main/java/com/krunal/demo/uicomponents/views/MyClickableSpan.kt create mode 100644 Demo/app/src/main/res/color/chip_card.xml create mode 100644 Demo/app/src/main/res/color/chip_card_text.xml create mode 100644 Demo/app/src/main/res/color/chip_operations_time.xml create mode 100644 Demo/app/src/main/res/color/stack_exchange_chip_background.xml create mode 100644 Demo/app/src/main/res/drawable/calendar_banner.jpeg create mode 100644 Demo/app/src/main/res/drawable/circle_image.xml create mode 100644 Demo/app/src/main/res/drawable/circular_progress.xml create mode 100644 Demo/app/src/main/res/drawable/dark_forest.jpeg create mode 100644 Demo/app/src/main/res/drawable/gradient_button.xml create mode 100644 Demo/app/src/main/res/drawable/gradient_progress.xml create mode 100644 Demo/app/src/main/res/drawable/highlighted_background.xml create mode 100644 Demo/app/src/main/res/drawable/ic_add.xml create mode 100644 Demo/app/src/main/res/drawable/ic_add_filled.xml create mode 100644 Demo/app/src/main/res/drawable/ic_android.xml create mode 100644 Demo/app/src/main/res/drawable/ic_avax_logo.xml create mode 100644 Demo/app/src/main/res/drawable/ic_back.xml create mode 100644 Demo/app/src/main/res/drawable/ic_calendar.xml create mode 100644 Demo/app/src/main/res/drawable/ic_call.xml create mode 100644 Demo/app/src/main/res/drawable/ic_card.xml create mode 100644 Demo/app/src/main/res/drawable/ic_chat.xml create mode 100644 Demo/app/src/main/res/drawable/ic_check.xml create mode 100644 Demo/app/src/main/res/drawable/ic_circle.xml create mode 100644 Demo/app/src/main/res/drawable/ic_cross.xml create mode 100644 Demo/app/src/main/res/drawable/ic_down_arrow.xml create mode 100644 Demo/app/src/main/res/drawable/ic_down_arrow_outlined.xml create mode 100644 Demo/app/src/main/res/drawable/ic_email.xml create mode 100644 Demo/app/src/main/res/drawable/ic_exchange.xml create mode 100644 Demo/app/src/main/res/drawable/ic_feed.xml create mode 100644 Demo/app/src/main/res/drawable/ic_female.xml create mode 100644 Demo/app/src/main/res/drawable/ic_food.xml create mode 100644 Demo/app/src/main/res/drawable/ic_gasoline.xml create mode 100644 Demo/app/src/main/res/drawable/ic_heart_outlined.xml create mode 100644 Demo/app/src/main/res/drawable/ic_home.xml create mode 100644 Demo/app/src/main/res/drawable/ic_image.xml create mode 100644 Demo/app/src/main/res/drawable/ic_info.xml create mode 100644 Demo/app/src/main/res/drawable/ic_line_shader.xml create mode 100644 Demo/app/src/main/res/drawable/ic_link_logo.xml create mode 100644 Demo/app/src/main/res/drawable/ic_male.xml create mode 100644 Demo/app/src/main/res/drawable/ic_market.xml create mode 100644 Demo/app/src/main/res/drawable/ic_mastercard.xml create mode 100644 Demo/app/src/main/res/drawable/ic_menu.xml create mode 100644 Demo/app/src/main/res/drawable/ic_next.xml create mode 100644 Demo/app/src/main/res/drawable/ic_notification.xml create mode 100644 Demo/app/src/main/res/drawable/ic_other_gender.xml create mode 100644 Demo/app/src/main/res/drawable/ic_paperplane.xml create mode 100644 Demo/app/src/main/res/drawable/ic_pause.xml create mode 100644 Demo/app/src/main/res/drawable/ic_pause_outlined.xml create mode 100644 Demo/app/src/main/res/drawable/ic_pen.xml create mode 100644 Demo/app/src/main/res/drawable/ic_play.xml create mode 100644 Demo/app/src/main/res/drawable/ic_play_outlined.xml create mode 100644 Demo/app/src/main/res/drawable/ic_previous.xml create mode 100644 Demo/app/src/main/res/drawable/ic_profile.xml create mode 100644 Demo/app/src/main/res/drawable/ic_remove.xml create mode 100644 Demo/app/src/main/res/drawable/ic_right_arrow.xml create mode 100644 Demo/app/src/main/res/drawable/ic_rose_logo.xml create mode 100644 Demo/app/src/main/res/drawable/ic_search.xml create mode 100644 Demo/app/src/main/res/drawable/ic_splash.xml create mode 100644 Demo/app/src/main/res/drawable/ic_swap.xml create mode 100644 Demo/app/src/main/res/drawable/ic_timezone.xml create mode 100644 Demo/app/src/main/res/drawable/ic_two_circle.xml create mode 100644 Demo/app/src/main/res/drawable/ic_up_arrow.xml create mode 100644 Demo/app/src/main/res/drawable/ic_wallet.xml create mode 100644 Demo/app/src/main/res/drawable/image_toggle.xml create mode 100644 Demo/app/src/main/res/drawable/img.png create mode 100644 Demo/app/src/main/res/drawable/profile.jpeg create mode 100644 Demo/app/src/main/res/drawable/progress_vertical.xml create mode 100644 Demo/app/src/main/res/drawable/progress_vertical_graph.xml create mode 100644 Demo/app/src/main/res/drawable/rounded_bottom_bar.xml create mode 100644 Demo/app/src/main/res/drawable/rounded_bottom_sheet.xml create mode 100644 Demo/app/src/main/res/drawable/rounded_image.xml create mode 100644 Demo/app/src/main/res/drawable/rounded_tab_layout.xml create mode 100644 Demo/app/src/main/res/drawable/rounded_toast.xml create mode 100644 Demo/app/src/main/res/drawable/rounded_transparent_background.xml create mode 100644 Demo/app/src/main/res/drawable/running_up_that_hill.png create mode 100644 Demo/app/src/main/res/drawable/selected_theme.xml create mode 100644 Demo/app/src/main/res/drawable/tab_selected_background.xml create mode 100644 Demo/app/src/main/res/drawable/time_banner.jpeg create mode 100644 Demo/app/src/main/res/drawable/trade_graph.png create mode 100644 Demo/app/src/main/res/layout/activity_stack_exchange.xml create mode 100644 Demo/app/src/main/res/layout/activity_uicomponents.xml create mode 100644 Demo/app/src/main/res/layout/card_chip.xml create mode 100644 Demo/app/src/main/res/layout/card_layout.xml create mode 100644 Demo/app/src/main/res/layout/company_amount_card.xml create mode 100644 Demo/app/src/main/res/layout/custom_toast.xml create mode 100644 Demo/app/src/main/res/layout/exchange_card.xml create mode 100644 Demo/app/src/main/res/layout/exchange_layout.xml create mode 100644 Demo/app/src/main/res/layout/fragment_app_bar.xml create mode 100644 Demo/app/src/main/res/layout/fragment_button.xml create mode 100644 Demo/app/src/main/res/layout/fragment_calls.xml create mode 100644 Demo/app/src/main/res/layout/fragment_card.xml create mode 100644 Demo/app/src/main/res/layout/fragment_chat.xml create mode 100644 Demo/app/src/main/res/layout/fragment_checkbox.xml create mode 100644 Demo/app/src/main/res/layout/fragment_chip.xml create mode 100644 Demo/app/src/main/res/layout/fragment_cl_chain_bias.xml create mode 100644 Demo/app/src/main/res/layout/fragment_cl_circular.xml create mode 100644 Demo/app/src/main/res/layout/fragment_cl_guideline_barrier.xml create mode 100644 Demo/app/src/main/res/layout/fragment_cl_relative.xml create mode 100644 Demo/app/src/main/res/layout/fragment_coordinator_layout.xml create mode 100644 Demo/app/src/main/res/layout/fragment_custom_view.xml create mode 100644 Demo/app/src/main/res/layout/fragment_data_binding.xml create mode 100644 Demo/app/src/main/res/layout/fragment_date_picker.xml create mode 100644 Demo/app/src/main/res/layout/fragment_edit_text.xml create mode 100644 Demo/app/src/main/res/layout/fragment_exchange.xml create mode 100644 Demo/app/src/main/res/layout/fragment_fab.xml create mode 100644 Demo/app/src/main/res/layout/fragment_frame_layout.xml create mode 100644 Demo/app/src/main/res/layout/fragment_home.xml create mode 100644 Demo/app/src/main/res/layout/fragment_linear_layout.xml create mode 100644 Demo/app/src/main/res/layout/fragment_market.xml create mode 100644 Demo/app/src/main/res/layout/fragment_operations_bottom_sheet.xml create mode 100644 Demo/app/src/main/res/layout/fragment_progress_bar.xml create mode 100644 Demo/app/src/main/res/layout/fragment_radio.xml create mode 100644 Demo/app/src/main/res/layout/fragment_relative_layout.xml create mode 100644 Demo/app/src/main/res/layout/fragment_slider.xml create mode 100644 Demo/app/src/main/res/layout/fragment_snackbar.xml create mode 100644 Demo/app/src/main/res/layout/fragment_span.xml create mode 100644 Demo/app/src/main/res/layout/fragment_spinner.xml create mode 100644 Demo/app/src/main/res/layout/fragment_tab_layout.xml create mode 100644 Demo/app/src/main/res/layout/fragment_theme.xml create mode 100644 Demo/app/src/main/res/layout/fragment_time_picker.xml create mode 100644 Demo/app/src/main/res/layout/fragment_toast.xml create mode 100644 Demo/app/src/main/res/layout/fragment_wallet.xml create mode 100644 Demo/app/src/main/res/layout/name_layout.xml create mode 100644 Demo/app/src/main/res/layout/name_merge_layout.xml create mode 100644 Demo/app/src/main/res/layout/notification_layout.xml create mode 100644 Demo/app/src/main/res/layout/operations_card_layout.xml create mode 100644 Demo/app/src/main/res/layout/portfolio_layout.xml create mode 100644 Demo/app/src/main/res/layout/price_layout.xml create mode 100644 Demo/app/src/main/res/layout/recent_operation_layout.xml create mode 100644 Demo/app/src/main/res/layout/share_details_layout.xml create mode 100644 Demo/app/src/main/res/layout/theme_layout.xml create mode 100644 Demo/app/src/main/res/layout/timezone_spinner.xml create mode 100644 Demo/app/src/main/res/layout/title_card.xml create mode 100644 Demo/app/src/main/res/layout/total_price_layout.xml create mode 100644 Demo/app/src/main/res/menu/card_bottom_navigation_items.xml create mode 100644 Demo/app/src/main/res/menu/stack_exchange_app_bar_menu.xml create mode 100644 Demo/app/src/main/res/menu/stack_exchange_bottom_menu.xml create mode 100644 Demo/app/src/main/res/menu/toolbar_menu.xml create mode 100644 Demo/app/src/main/res/menu/toolbar_search_menu.xml create mode 100644 Demo/app/src/main/res/values/arrays.xml create mode 100644 Demo/app/src/main/res/values/attrs.xml create mode 100644 Demo/app/src/main/res/values/dimens.xml create mode 100644 Demo/app/src/main/res/values/splash.xml create mode 100644 Demo/app/src/main/res/values/styles.xml diff --git a/Demo/app/build.gradle b/Demo/app/build.gradle index 806d1aa..8382565 100644 --- a/Demo/app/build.gradle +++ b/Demo/app/build.gradle @@ -1,6 +1,7 @@ plugins { id 'com.android.application' id 'org.jetbrains.kotlin.android' + id 'kotlin-kapt' } android { @@ -24,26 +25,30 @@ android { } } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 } kotlinOptions { - jvmTarget = '1.8' + jvmTarget = '17' } buildFeatures { viewBinding true + dataBinding true } } dependencies { - - implementation 'androidx.core:core-ktx:1.9.0' + def lifecycle_version = "2.6.1" + implementation 'androidx.core:core-ktx:1.10.1' implementation 'androidx.appcompat:appcompat:1.6.1' - implementation 'com.google.android.material:material:1.8.0' + implementation 'com.google.android.material:material:1.9.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' - implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1" - implementation 'androidx.fragment:fragment-ktx:1.5.5' + implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version" + implementation 'androidx.fragment:fragment-ktx:1.5.7' + implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version" + implementation "androidx.core:core-splashscreen:1.0.1" + implementation "androidx.preference:preference-ktx:1.2.0" testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.5' diff --git a/Demo/app/src/main/AndroidManifest.xml b/Demo/app/src/main/AndroidManifest.xml index 3410a78..4858a5c 100644 --- a/Demo/app/src/main/AndroidManifest.xml +++ b/Demo/app/src/main/AndroidManifest.xml @@ -3,14 +3,34 @@ xmlns:tools="http://schemas.android.com/tools"> + + + + + + + + + + + + + + diff --git a/Demo/app/src/main/java/com/krunal/demo/DemoApplication.kt b/Demo/app/src/main/java/com/krunal/demo/DemoApplication.kt new file mode 100644 index 0000000..45e7964 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/DemoApplication.kt @@ -0,0 +1,22 @@ +package com.krunal.demo + +import android.app.Application +import com.krunal.demo.uicomponents.helpers.PreferenceHelper + +class DemoApplication: Application() { + + override fun onCreate() { + super.onCreate() + + instance = this + + /** + * Initialize [PreferenceHelper] + */ + PreferenceHelper.initialize(applicationContext) + } + + companion object { + lateinit var instance: Application + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/MainActivity.kt b/Demo/app/src/main/java/com/krunal/demo/MainActivity.kt index bd7b820..56439e4 100644 --- a/Demo/app/src/main/java/com/krunal/demo/MainActivity.kt +++ b/Demo/app/src/main/java/com/krunal/demo/MainActivity.kt @@ -12,15 +12,18 @@ import kotlinx.coroutines.launch class MainActivity : AppCompatActivity() { - lateinit var binding: ActivityMainBinding - lateinit var mPlayer: MediaPlayer + private lateinit var binding: ActivityMainBinding + private lateinit var mPlayer: MediaPlayer private val viewModel: MainActivityViewModel by viewModels() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) + + binding.mainViewModel = viewModel + + // Update time without data binding CoroutineScope(Dispatchers.IO).launch { viewModel.timeFlow.collectLatest { time -> runOnUiThread { diff --git a/Demo/app/src/main/java/com/krunal/demo/MainActivityViewModel.kt b/Demo/app/src/main/java/com/krunal/demo/MainActivityViewModel.kt index 31952be..2730fe0 100644 --- a/Demo/app/src/main/java/com/krunal/demo/MainActivityViewModel.kt +++ b/Demo/app/src/main/java/com/krunal/demo/MainActivityViewModel.kt @@ -2,15 +2,16 @@ package com.krunal.demo import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope -import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.launch import java.text.SimpleDateFormat -import java.util.* +import java.util.Date class MainActivityViewModel : ViewModel() { - private val _timeFlow = MutableSharedFlow() - val timeFlow: MutableSharedFlow = _timeFlow + private val _timeFlow = MutableStateFlow(null) + val timeFlow: StateFlow = _timeFlow init { start() diff --git a/Demo/app/src/main/java/com/krunal/demo/UIComponentsActivity.kt b/Demo/app/src/main/java/com/krunal/demo/UIComponentsActivity.kt new file mode 100644 index 0000000..d03fa42 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/UIComponentsActivity.kt @@ -0,0 +1,31 @@ +package com.krunal.demo + +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import androidx.core.splashscreen.SplashScreen.Companion.installSplashScreen +import com.krunal.demo.uicomponents.ButtonFragment +import com.krunal.demo.uicomponents.CoordinatorLayoutFragment +import com.krunal.demo.uicomponents.ThemeFragment +import com.krunal.demo.uicomponents.cardscreen.CardFragment +import com.krunal.demo.uicomponents.helpers.ThemeHelper + +class UIComponentsActivity : AppCompatActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + installSplashScreen() + setupTheme() + setContentView(R.layout.activity_uicomponents) + setupFragment() + } + + private fun setupTheme() { + setTheme(ThemeHelper.getThemeResource(ThemeHelper.getThemeAccent())) + } + + private fun setupFragment() { + supportFragmentManager.beginTransaction() + .replace(R.id.uiComponentsFragment, CoordinatorLayoutFragment()) + .commit() + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/stackexchange/StackExchangeActivity.kt b/Demo/app/src/main/java/com/krunal/demo/stackexchange/StackExchangeActivity.kt new file mode 100644 index 0000000..9e483a5 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/stackexchange/StackExchangeActivity.kt @@ -0,0 +1,95 @@ +package com.krunal.demo.stackexchange + +import android.graphics.Color +import android.os.Bundle +import android.view.ViewTreeObserver.OnGlobalLayoutListener +import androidx.activity.viewModels +import androidx.appcompat.app.AppCompatActivity +import androidx.fragment.app.Fragment +import androidx.lifecycle.lifecycleScope +import com.google.android.material.badge.BadgeDrawable +import com.google.android.material.badge.BadgeUtils +import com.google.android.material.badge.ExperimentalBadgeUtils +import com.krunal.demo.R +import com.krunal.demo.databinding.ActivityStackExchangeBinding +import com.krunal.demo.stackexchange.exchange.ExchangeFragment +import com.krunal.demo.stackexchange.market.MarketFragment +import com.krunal.demo.stackexchange.wallet.WalletFragment +import com.krunal.demo.uicomponents.extentions.getThemeColor +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch + + +class StackExchangeActivity : AppCompatActivity() { + + private lateinit var binding: ActivityStackExchangeBinding + private val viewModel: StackExchangeActivityViewModel by viewModels() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityStackExchangeBinding.inflate(layoutInflater) + setContentView(binding.root) + binding.viewModel = viewModel + setupUI() + setupBottomNavigation() + } + + private fun setupUI() { + + setupAppBar() + + binding.apply { + fabExchange.setOnClickListener { + bottomNavigation.selectedItemId = R.id.actionExchange + } + } + } + + @androidx.annotation.OptIn(ExperimentalBadgeUtils::class) + private fun setupAppBar() { + setSupportActionBar(binding.toolbar) + val badge = BadgeDrawable.create(this).apply { + badgeTextColor = getThemeColor(com.google.android.material.R.attr.colorOnSecondary) + backgroundColor = getThemeColor(com.google.android.material.R.attr.colorSecondary) + } + + binding.notificationView.apply { + clipToOutline = false + bringToFront() + viewTreeObserver.addOnGlobalLayoutListener { + BadgeUtils.attachBadgeDrawable( + badge, + binding.notificationView + ) + } + } + + lifecycleScope.launch { + viewModel.notificationCount.collectLatest { + if (it == 0) { + badge.isVisible = false + } else { + badge.number = it + } + } + } + } + + private fun setupBottomNavigation() { + changeFragment(MarketFragment()) + + binding.bottomNavigation.setOnItemSelectedListener { menuItem -> + val fragment = when (menuItem.itemId) { + R.id.actionMarket -> MarketFragment() + R.id.actionExchange -> ExchangeFragment() + else -> WalletFragment() + } + changeFragment(fragment) + return@setOnItemSelectedListener true + } + } + + private fun changeFragment(fragment: Fragment) { + supportFragmentManager.beginTransaction().replace(R.id.fragmentContainer, fragment).commit() + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/stackexchange/StackExchangeActivityViewModel.kt b/Demo/app/src/main/java/com/krunal/demo/stackexchange/StackExchangeActivityViewModel.kt new file mode 100644 index 0000000..fc60938 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/stackexchange/StackExchangeActivityViewModel.kt @@ -0,0 +1,11 @@ +package com.krunal.demo.stackexchange + +import androidx.lifecycle.ViewModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow + +class StackExchangeActivityViewModel : ViewModel() { + + private val _notificationCount: MutableStateFlow = MutableStateFlow(6) + val notificationCount: StateFlow = _notificationCount +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/stackexchange/exchange/ExchangeFragment.kt b/Demo/app/src/main/java/com/krunal/demo/stackexchange/exchange/ExchangeFragment.kt new file mode 100644 index 0000000..d62a663 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/stackexchange/exchange/ExchangeFragment.kt @@ -0,0 +1,72 @@ +package com.krunal.demo.stackexchange.exchange + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import com.krunal.demo.databinding.FragmentExchangeBinding + +class ExchangeFragment : Fragment() { + + private lateinit var binding: FragmentExchangeBinding + private val viewModel: ExchangeFragmentViewModel by viewModels() + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View { + binding = FragmentExchangeBinding.inflate(layoutInflater) + binding.lifecycleOwner = viewLifecycleOwner + binding.viewModel = viewModel + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupListener() + + } + + private fun setupListener() { + binding.cardExchange.exchangeView.cardAmount.apply { + imgBtnAdd.setOnClickListener { + viewModel.changeExchangeAmount(0.1F) + } + + imgBtnMinus.setOnClickListener { + viewModel.changeExchangeAmount(-0.1F) + } + + imgBtnAdd.setOnLongClickListener { + viewModel.changeExchangeAmount(1F) + true + } + + imgBtnMinus.setOnLongClickListener { + viewModel.changeExchangeAmount(-1F) + true + } + } + + binding.cardExchange.receiverView.cardAmount.apply { + imgBtnAdd.setOnClickListener { + viewModel.changeReceiveAmount(0.1F) + } + + imgBtnMinus.setOnClickListener { + viewModel.changeReceiveAmount(-0.1F) + } + + imgBtnAdd.setOnLongClickListener { + viewModel.changeReceiveAmount(1F) + true + } + + imgBtnMinus.setOnLongClickListener { + viewModel.changeReceiveAmount(-1F) + true + } + } + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/stackexchange/exchange/ExchangeFragmentViewModel.kt b/Demo/app/src/main/java/com/krunal/demo/stackexchange/exchange/ExchangeFragmentViewModel.kt new file mode 100644 index 0000000..f548197 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/stackexchange/exchange/ExchangeFragmentViewModel.kt @@ -0,0 +1,75 @@ +package com.krunal.demo.stackexchange.exchange + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.krunal.demo.stackexchange.models.ExchangeModel +import com.krunal.demo.stackexchange.models.ShareDetails +import com.krunal.demo.stackexchange.models.TitleCardModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.launch + +class ExchangeFragmentViewModel : ViewModel() { + + private val _dateTime: MutableStateFlow = MutableStateFlow("") + val dateTime: StateFlow = _dateTime + + private val _titleCardModel: MutableStateFlow = MutableStateFlow(null) + val titleCardModel: StateFlow = _titleCardModel + + private val _exchangeModel: MutableStateFlow = MutableStateFlow(null) + val exchangeModel: StateFlow = _exchangeModel + + init { + setupInitialValues() + } + + private fun setupInitialValues() { + viewModelScope.launch { + _dateTime.emit("OCTOBER 18, TUESDAY 3:19:23 PM") + + _exchangeModel.emit( + ExchangeModel( + ShareDetails.dummyData[0], ShareDetails.dummyData[2], 32.80F + ) + ) + + exchangeModel.collectLatest { + it?.receive?.let { share -> + _titleCardModel.emit( + TitleCardModel("EXCHANGE FOR", share.value, share.name) + ) + } + } + } + } + + fun changeExchangeAmount(number: Float) { + viewModelScope.launch { + exchangeModel.value?.exchange?.let { exchange -> + _exchangeModel.emit( + exchangeModel.value?.copy( + exchange = exchange.copy( + value = exchange.value + number + ) + ) + ) + } + } + } + + fun changeReceiveAmount(number: Float) { + viewModelScope.launch { + exchangeModel.value?.receive?.let { receive -> + _exchangeModel.emit( + exchangeModel.value?.copy( + receive = receive.copy( + value = receive.value + number + ) + ) + ) + } + } + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/stackexchange/market/MarketFragment.kt b/Demo/app/src/main/java/com/krunal/demo/stackexchange/market/MarketFragment.kt new file mode 100644 index 0000000..f1f9f6d --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/stackexchange/market/MarketFragment.kt @@ -0,0 +1,49 @@ +package com.krunal.demo.stackexchange.market + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import com.krunal.demo.databinding.FragmentMarketBinding +import java.text.NumberFormat +import java.util.Locale + +class MarketFragment : Fragment() { + + private lateinit var binding: FragmentMarketBinding + private val viewModel: MarketFragmentViewModel by viewModels() + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View { + binding = FragmentMarketBinding.inflate(layoutInflater) + binding.lifecycleOwner = viewLifecycleOwner + binding.viewModel = viewModel + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupUI() + } + + private fun setupUI() { + binding.priceLayout1.imgBtnAdd.setOnClickListener { + viewModel.changeYourPriceValue(true) + } + + binding.priceLayout1.imgBtnMinus.setOnClickListener { + viewModel.changeYourPriceValue(false) + } + + binding.priceLayout2.imgBtnAdd.setOnClickListener { + viewModel.changeAmountValue(true) + } + + binding.priceLayout2.imgBtnMinus.setOnClickListener { + viewModel.changeAmountValue(false) + } + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/stackexchange/market/MarketFragmentViewModel.kt b/Demo/app/src/main/java/com/krunal/demo/stackexchange/market/MarketFragmentViewModel.kt new file mode 100644 index 0000000..db70d0a --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/stackexchange/market/MarketFragmentViewModel.kt @@ -0,0 +1,67 @@ +package com.krunal.demo.stackexchange.market + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.krunal.demo.stackexchange.models.ShareDetails +import com.krunal.demo.stackexchange.models.TitleCardModel +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch + +class MarketFragmentViewModel : ViewModel() { + + private val _titleCardModel: MutableStateFlow = MutableStateFlow(null) + val titleCardModel: StateFlow = _titleCardModel + + private val _shareDetails: MutableStateFlow?> = MutableStateFlow(null) + val shareDetails: StateFlow?> = _shareDetails + + private val _yourPrice: MutableStateFlow = MutableStateFlow(0F) + val yourPrice: StateFlow = _yourPrice + + private val _amount: MutableStateFlow = MutableStateFlow(0F) + val amount: StateFlow = _amount + + + init { + setupInitialValues() + } + + private fun setupInitialValues() { + viewModelScope.launch { + _titleCardModel.emit( + TitleCardModel("BUY", 50000F, "SHIB") + ) + + _shareDetails.emit(ShareDetails.dummyData) + + _yourPrice.emit(0.268800F) + _amount.emit(50000F) + } + } + + fun changeYourPriceValue(increase: Boolean = false) { + viewModelScope.launch { + _yourPrice.emit( + if (increase) { + yourPrice.value + 0.1F + } else { + yourPrice.value - 0.1F + } + ) + } + } + + fun changeAmountValue(increase: Boolean = false) { + viewModelScope.launch { + _amount.emit( + if (increase) { + amount.value + 1 + } else { + amount.value - 1 + } + ) + } + } + +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/stackexchange/models/ExchangeModel.kt b/Demo/app/src/main/java/com/krunal/demo/stackexchange/models/ExchangeModel.kt new file mode 100644 index 0000000..4b61854 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/stackexchange/models/ExchangeModel.kt @@ -0,0 +1,5 @@ +package com.krunal.demo.stackexchange.models + +data class ExchangeModel( + val exchange: ShareDetails, val receive: ShareDetails, val transactionCost: Float +) diff --git a/Demo/app/src/main/java/com/krunal/demo/stackexchange/models/ShareDetails.kt b/Demo/app/src/main/java/com/krunal/demo/stackexchange/models/ShareDetails.kt new file mode 100644 index 0000000..e70fe15 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/stackexchange/models/ShareDetails.kt @@ -0,0 +1,52 @@ +package com.krunal.demo.stackexchange.models + +import androidx.annotation.DrawableRes +import com.krunal.demo.R + +data class ShareDetails( + val name: String, + val parentName: String, + @DrawableRes val logo: Int, + val amountDiff: Float, + val isPositive: Boolean = true, + val value: Float, + val price: Float, + val percentage: Int, + val dateTime: String +) { + companion object { + val dummyData: List = listOf( + ShareDetails( + "Avax", + "AVALANCHE", + R.drawable.ic_avax_logo, + 0.187200F, + true, + 47.38662F, + 6022.84F, + 37, + "2021-10-19 8:51" + ), ShareDetails( + "Rose", + "OASIS NETWORK", + R.drawable.ic_rose_logo, + 0.187200F, + false, + 47.38662F, + 6022.84F, + 72, + "2021-10-19 16:13" + ), ShareDetails( + "Link", + "CHAINLINK", + R.drawable.ic_link_logo, + 127.10F, + false, + 50.45872F, + 6022.84F, + 29, + "2021-10-19 10:00" + ) + ) + } +} diff --git a/Demo/app/src/main/java/com/krunal/demo/stackexchange/models/TitleCardModel.kt b/Demo/app/src/main/java/com/krunal/demo/stackexchange/models/TitleCardModel.kt new file mode 100644 index 0000000..6c6c0f5 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/stackexchange/models/TitleCardModel.kt @@ -0,0 +1,5 @@ +package com.krunal.demo.stackexchange.models + +data class TitleCardModel( + val textBeforeAmount: String, val amount: Float, val textAfterAmount: String +) \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/stackexchange/models/Wallet.kt b/Demo/app/src/main/java/com/krunal/demo/stackexchange/models/Wallet.kt new file mode 100644 index 0000000..86b7ee5 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/stackexchange/models/Wallet.kt @@ -0,0 +1,19 @@ +package com.krunal.demo.stackexchange.models + +data class Wallet( + val amount: String, + val amountDiff: String, + val diffPercentage: String, + val maxValue: String, + val isPositive: Boolean = true +) { + companion object { + val dummyWallet: Wallet + get() = Wallet( + "182.057,83", + "31.800,52", + "21,1", + "185.701,18" + ) + } +} diff --git a/Demo/app/src/main/java/com/krunal/demo/stackexchange/utils/Formatter.kt b/Demo/app/src/main/java/com/krunal/demo/stackexchange/utils/Formatter.kt new file mode 100644 index 0000000..5e47231 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/stackexchange/utils/Formatter.kt @@ -0,0 +1,13 @@ +package com.krunal.demo.stackexchange.utils + +import java.text.NumberFormat +import java.util.Locale + +object Formatter { + private val formatter = NumberFormat.getCurrencyInstance(Locale.GERMANY).apply { + minimumFractionDigits = 0 + } + + @JvmStatic + fun euroFormat(number: Float): String = formatter.format(number).removeSuffix("€") +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/stackexchange/views/Divider.kt b/Demo/app/src/main/java/com/krunal/demo/stackexchange/views/Divider.kt new file mode 100644 index 0000000..6638ed5 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/stackexchange/views/Divider.kt @@ -0,0 +1,83 @@ +package com.krunal.demo.stackexchange.views + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.Paint +import android.util.AttributeSet +import android.view.View +import com.krunal.demo.R +import com.krunal.demo.uicomponents.extentions.getThemeColor + +class Divider @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = R.attr.dividerStyle, +) : View(context, attrs, defStyleAttr) { + + private val progressPaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG) + private val backgroundPaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG) + private var lineHeight = 5F + private var progressColor = + Color.RED + private var backgroundColor = + context.getThemeColor(com.google.android.material.R.attr.colorSurfaceDim) + private var progress = 6F + private var centerPositionY: Float = 5F + + + init { + setupAttributes(attrs, defStyleAttr) + setupPaint() + } + + private fun setupAttributes(attrs: AttributeSet?, defStyle: Int) { + context.theme.obtainStyledAttributes(attrs, R.styleable.Divider, defStyle, 0).apply { + backgroundColor = getColor(R.styleable.Divider_backgroundColor, backgroundColor) + progressColor = getColor(R.styleable.Divider_progressColor, progressColor) + progress = getDimension(R.styleable.Divider_progress, progress) + lineHeight = getDimension(R.styleable.Divider_lineHeight, lineHeight) + recycle() + } + } + + private fun setupPaint() { + progressPaint.apply { + color = progressColor + strokeWidth = lineHeight + style = Paint.Style.STROKE + strokeCap = Paint.Cap.ROUND + } + + backgroundPaint.apply { + color = backgroundColor + strokeWidth = lineHeight + style = Paint.Style.STROKE + strokeCap = Paint.Cap.ROUND + } + } + + override fun onDraw(canvas: Canvas) { + centerPositionY = height / 2F + drawBackground(canvas) + drawProgress(canvas) + + super.onDraw(canvas) + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + setMeasuredDimension(widthMeasureSpec, lineHeight.toInt()) + } + + private fun drawBackground(canvas: Canvas) { + canvas.drawLine(0f, centerPositionY, width.toFloat(), centerPositionY, backgroundPaint) + } + + private fun drawProgress(canvas: Canvas) { + // Draw left progress + canvas.drawLine(0f, centerPositionY, progress, centerPositionY, progressPaint) + + // Draw right progress + canvas.drawLine(width - progress, centerPositionY, width.toFloat(), centerPositionY, progressPaint) + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/stackexchange/views/DonutChart.kt b/Demo/app/src/main/java/com/krunal/demo/stackexchange/views/DonutChart.kt new file mode 100644 index 0000000..88230bc --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/stackexchange/views/DonutChart.kt @@ -0,0 +1,123 @@ +package com.krunal.demo.stackexchange.views + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.Paint +import android.graphics.RectF +import android.util.AttributeSet +import android.view.View +import com.krunal.demo.R +import com.krunal.demo.uicomponents.extentions.toFloat +import com.krunal.demo.uicomponents.extentions.toInt +import kotlin.math.min + +class DonutChart @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, + defStyleAttr: Int = 0, +) : View(context, attrs, defStyleAttr) { + + private var colors: List = + listOf(Color.YELLOW, Color.RED, Color.GREEN, Color.CYAN)//emptyList() + private var values: List = listOf(25F, 30F, 10F, 35F)//emptyList() + private var donutSpacing = 5F + private val angles by lazy { values.map { it.toAngle() - donutSpacing } } + private var lineWidth = 15F + private var totalValue = 100 + private val linePaint = Paint(Paint.ANTI_ALIAS_FLAG) + private val rectF = RectF() + private var currentAngle = 270f // Make initial angle top + private var radius = -1F + + init { + setupAttributes(attrs, defStyleAttr) + setupPaint() + } + + private fun setupAttributes(attrs: AttributeSet?, defStyleAttr: Int) { + context.theme.obtainStyledAttributes(attrs, R.styleable.DonutChart, defStyleAttr, 0).apply { + donutSpacing = getDimension(R.styleable.DonutChart_donutSpacing, donutSpacing) + lineWidth = getDimension(R.styleable.DonutChart_strokeWidth, lineWidth) + totalValue = getInt(R.styleable.DonutChart_totalValue, totalValue) + radius = getDimension(R.styleable.DonutChart_radius, radius) + + if (hasValue(R.styleable.DonutChart_values)) { + val valuesId = getResourceId(R.styleable.DonutChart_values, 0) + resources.obtainTypedArray(valuesId).also { typedArray -> + values = typedArray.toFloat() + typedArray.recycle() + } + } + + if (hasValue(R.styleable.DonutChart_colors)) { + val colorId = getResourceId(R.styleable.DonutChart_colors, 0) + resources.obtainTypedArray(colorId).also { typedArray -> + colors = typedArray.toInt() + typedArray.recycle() + } + } + recycle() + } + } + + private fun setupPaint() { + linePaint.apply { + strokeWidth = lineWidth + style = Paint.Style.STROKE + strokeCap = Paint.Cap.ROUND + } + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + val desiredWidth = suggestedMinimumWidth + paddingLeft + paddingRight + val desiredHeight = suggestedMinimumHeight + paddingTop + paddingBottom + + setMeasuredDimension( + resolveSize(desiredWidth, widthMeasureSpec), + resolveSize(desiredHeight, heightMeasureSpec) + ) + } + + override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { + radius = if (radius == DEFAULT_RADIUS) { + val minSize = min(height, width) + minSize / 2 - (minSize/10F) + } else { + radius + } + super.onLayout(changed, left, top, right, bottom) + } + + override fun onDraw(canvas: Canvas) { + setupRectF() + drawGraph(canvas) + super.onDraw(canvas) + } + + private fun setupRectF() { + val centerX = width / 2F + val centerY = height / 2F + rectF.set(centerX - radius, centerY - radius, centerX + radius, centerY + radius) + } + + private fun drawGraph(canvas: Canvas) { + angles.forEachIndexed { index, value -> + linePaint.color = colors[index] + drawDonut(canvas, linePaint, currentAngle, value) + currentAngle += value + donutSpacing + } + } + + private fun drawDonut(canvas: Canvas, paint: Paint, startAngle: Float, sweepAngle: Float) { + canvas.drawArc(rectF, startAngle, sweepAngle, false, paint) + } + + private fun Float.toAngle(): Float = ((this * 360F) / totalValue) + + companion object { + private const val TAG = "DonutChart" + private const val DEFAULT_RADIUS = -1F + private const val CHART_PADDING = 100F + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/stackexchange/views/HexagonFloatingActionButton.kt b/Demo/app/src/main/java/com/krunal/demo/stackexchange/views/HexagonFloatingActionButton.kt new file mode 100644 index 0000000..f13d4ad --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/stackexchange/views/HexagonFloatingActionButton.kt @@ -0,0 +1,39 @@ +package com.krunal.demo.stackexchange.views + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Outline +import android.graphics.Path +import android.util.AttributeSet +import android.view.View +import android.view.ViewOutlineProvider +import com.google.android.material.floatingactionbutton.FloatingActionButton + +class HexagonFloatingActionButton @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0): FloatingActionButton(context, attrs, defStyleAttr) { + + private val hexagonPath = Path() + + override fun onDraw(canvas: Canvas?) { +// clipHexagonShape(canvas) + super.onDraw(canvas) + } + + private fun clipHexagonShape(canvas: Canvas?) { + hexagonPath.apply { + moveTo(size / 2f, 0f) + lineTo(size.toFloat(), height / 3f) + lineTo(size.toFloat(), height * 2 / 3f) + lineTo(size / 2f, height.toFloat()) + lineTo(0f, height * 2 / 3f) + lineTo(0f, height / 3f) + close() + } + canvas?.clipPath(hexagonPath) + clipToOutline = true + outlineProvider = object : ViewOutlineProvider() { + override fun getOutline(view: View, outline: Outline) { + outline.setConvexPath(hexagonPath) + } + } + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/stackexchange/wallet/WalletFragment.kt b/Demo/app/src/main/java/com/krunal/demo/stackexchange/wallet/WalletFragment.kt new file mode 100644 index 0000000..27a9fcb --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/stackexchange/wallet/WalletFragment.kt @@ -0,0 +1,28 @@ +package com.krunal.demo.stackexchange.wallet + +import android.os.Bundle +import androidx.fragment.app.Fragment +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.viewModels +import com.krunal.demo.databinding.FragmentWalletBinding + +class WalletFragment : Fragment() { + + private lateinit var binding: FragmentWalletBinding + private val viewMode: WalletFragmentViewModel by viewModels() + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View { + binding = FragmentWalletBinding.inflate(layoutInflater) + binding.lifecycleOwner = viewLifecycleOwner + binding.viewModel = viewMode + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/stackexchange/wallet/WalletFragmentViewModel.kt b/Demo/app/src/main/java/com/krunal/demo/stackexchange/wallet/WalletFragmentViewModel.kt new file mode 100644 index 0000000..d478a5e --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/stackexchange/wallet/WalletFragmentViewModel.kt @@ -0,0 +1,34 @@ +package com.krunal.demo.stackexchange.wallet + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.krunal.demo.stackexchange.models.ShareDetails +import com.krunal.demo.stackexchange.models.Wallet +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch + +class WalletFragmentViewModel : ViewModel() { + + private val _portfolios: MutableStateFlow?> = MutableStateFlow(null) + val portfolios: StateFlow?> = _portfolios + + private val _wallet: MutableStateFlow = MutableStateFlow(null) + val wallet: StateFlow = _wallet + + init { + setupInitialValues() + } + + private fun setupInitialValues() { + viewModelScope.launch { + _portfolios.emit( + ShareDetails.dummyData + ) + + _wallet.emit( + Wallet.dummyWallet + ) + } + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/AppBarFragment.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/AppBarFragment.kt new file mode 100644 index 0000000..c7fe0e0 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/AppBarFragment.kt @@ -0,0 +1,36 @@ +package com.krunal.demo.uicomponents + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.appcompat.widget.SearchView +import androidx.fragment.app.Fragment +import com.google.android.material.color.MaterialColors +import com.krunal.demo.R +import com.krunal.demo.databinding.FragmentAppBarBinding +import com.krunal.demo.uicomponents.extentions.getThemeColor + +class AppBarFragment : Fragment(R.layout.fragment_app_bar) { + + private lateinit var binding: FragmentAppBarBinding + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View { + binding = FragmentAppBarBinding.inflate(layoutInflater) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupAppBars() + } + + private fun setupAppBars() { + binding.tbItems.inflateMenu(R.menu.toolbar_menu) + val searchItem = binding.tbSearch.menu.findItem(R.id.miSearch) + val searchView = searchItem.actionView as SearchView + searchView.isIconified = false + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/ButtonFragment.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/ButtonFragment.kt new file mode 100644 index 0000000..9d3dd7f --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/ButtonFragment.kt @@ -0,0 +1,57 @@ +package com.krunal.demo.uicomponents + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.View.OnClickListener +import android.view.ViewGroup +import android.widget.Toast +import androidx.core.view.children +import androidx.fragment.app.Fragment +import com.google.android.material.button.MaterialButton +import com.krunal.demo.R +import com.krunal.demo.databinding.FragmentButtonBinding + +class ButtonFragment : Fragment(R.layout.fragment_button), OnClickListener { + + private lateinit var binding: FragmentButtonBinding + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View { + binding = FragmentButtonBinding.inflate(layoutInflater) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupClickListener() + } + + override fun onClick(view: View?) { + (view as? MaterialButton)?.let { + Toast.makeText(requireContext(), "${it.text} clicked", Toast.LENGTH_SHORT).show() + } + } + + private fun setupClickListener() { + + binding.btnNormal.setOnClickListener(this) + binding.btnBordered.setOnClickListener(this) + binding.appCompatButton.setOnClickListener(this) + binding.btnDisabled.setOnClickListener(this) + binding.btnOutlined.setOnClickListener(this) + binding.btnText.setOnClickListener(this) + binding.imgBtnImage.setOnClickListener(this) + binding.btnInfo.setOnClickListener(this) + binding.btnGradient.setOnClickListener(this) + + binding.switchEnable.setOnCheckedChangeListener { btn, checked -> + binding.root.children + .filterNot { it == binding.switchEnable } + .forEach { it.isEnabled = checked } + btn.text = + if (checked) getString(R.string.enabled_switch) else getString(R.string.disabled_switch) + } + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/CheckboxFragment.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/CheckboxFragment.kt new file mode 100644 index 0000000..329c6dd --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/CheckboxFragment.kt @@ -0,0 +1,58 @@ +package com.krunal.demo.uicomponents + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.CompoundButton +import android.widget.CompoundButton.OnCheckedChangeListener +import android.widget.Toast +import androidx.fragment.app.Fragment +import com.krunal.demo.R +import com.krunal.demo.databinding.FragmentCheckboxBinding + +class CheckboxFragment : Fragment(R.layout.fragment_checkbox), OnCheckedChangeListener { + + private lateinit var binding: FragmentCheckboxBinding + private var selectedLanguages = mutableListOf() + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View { + binding = FragmentCheckboxBinding.inflate(layoutInflater) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupListeners() + } + + private fun setupListeners() { + selectedLanguages.add("English") + selectedLanguages.add("Hindi") + binding.cbEnglish.setOnCheckedChangeListener(this) + binding.cbHindi.setOnCheckedChangeListener(this) + binding.cbGujarati.setOnCheckedChangeListener(this) + binding.cbSpanish.setOnCheckedChangeListener(this) + binding.btnSaveChanges.setOnClickListener { + Toast.makeText(requireContext(), selectedLanguages.joinToString(), Toast.LENGTH_SHORT) + .show() + } + } + + override fun onCheckedChanged(btn: CompoundButton?, isChecked: Boolean) { + val language = when (btn) { + binding.cbEnglish -> "English" + binding.cbHindi -> "Hindi" + binding.cbGujarati -> "Gujarati" + binding.cbSpanish -> "Spanish" + else -> "" + } + if (isChecked) { + selectedLanguages.add(language) + } else { + selectedLanguages.remove(language) + } + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/ChipFragment.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/ChipFragment.kt new file mode 100644 index 0000000..e1986a4 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/ChipFragment.kt @@ -0,0 +1,55 @@ +package com.krunal.demo.uicomponents + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.core.widget.addTextChangedListener +import androidx.fragment.app.Fragment +import com.google.android.material.chip.Chip +import com.krunal.demo.R +import com.krunal.demo.databinding.FragmentChipBinding + +class ChipFragment : Fragment(R.layout.fragment_chip) { + + private lateinit var binding: FragmentChipBinding + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + binding = FragmentChipBinding.inflate(layoutInflater) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupChips() + } + + private fun setupChips() { + binding.etChip.addTextChangedListener { + binding.tilChip.isEndIconVisible = binding.etChip.text.isNullOrEmpty().not() + } + + binding.etChip.setOnEditorActionListener { _, _, _ -> + addChip() + true + } + + binding.tilChip.setEndIconOnClickListener { + addChip() + } + } + + private fun addChip() { + val chip = Chip(requireContext()) + chip.text = binding.etChip.text + chip.setOnCloseIconClickListener { binding.cgProgrammatically.removeView(it) } + chip.setCloseIconResource(R.drawable.ic_cross) + chip.isCloseIconVisible = true + binding.cgProgrammatically.addView(chip) + binding.etChip.text?.clear() + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/CoordinatorLayoutFragment.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/CoordinatorLayoutFragment.kt new file mode 100644 index 0000000..246e9ba --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/CoordinatorLayoutFragment.kt @@ -0,0 +1,45 @@ +package com.krunal.demo.uicomponents + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Button +import androidx.fragment.app.Fragment +import com.google.android.material.behavior.SwipeDismissBehavior +import com.google.android.material.snackbar.BaseTransientBottomBar +import com.google.android.material.snackbar.Snackbar +import com.krunal.demo.databinding.FragmentCoordinatorLayoutBinding + +class CoordinatorLayoutFragment : Fragment() { + + private lateinit var binding: FragmentCoordinatorLayoutBinding + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View { + binding = FragmentCoordinatorLayoutBinding.inflate(layoutInflater) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupUI() + } + + private fun setupUI() { + setupSnackBar() + } + + private fun setupSnackBar() { + binding.btnSwipeableSnackBar.setOnClickListener { + Snackbar.make(requireView(), (it as Button).text, Snackbar.LENGTH_LONG).apply { + behavior = BaseTransientBottomBar.Behavior() + animationMode = BaseTransientBottomBar.ANIMATION_MODE_SLIDE + behavior.setSwipeDirection(SwipeDismissBehavior.SWIPE_DIRECTION_ANY) + show() + } + } + } + +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/CustomViewFragment.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/CustomViewFragment.kt new file mode 100644 index 0000000..04fd5cf --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/CustomViewFragment.kt @@ -0,0 +1,6 @@ +package com.krunal.demo.uicomponents + +import androidx.fragment.app.Fragment +import com.krunal.demo.R + +class CustomViewFragment : Fragment(R.layout.fragment_custom_view) \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/EditTextFragment.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/EditTextFragment.kt new file mode 100644 index 0000000..a2eca4f --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/EditTextFragment.kt @@ -0,0 +1,67 @@ +package com.krunal.demo.uicomponents + +import android.os.Bundle +import android.util.Patterns +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.EditText +import androidx.core.view.children +import androidx.core.widget.addTextChangedListener +import androidx.fragment.app.Fragment +import com.google.android.material.textfield.TextInputLayout +import com.krunal.demo.R +import com.krunal.demo.databinding.FragmentEditTextBinding +import com.krunal.demo.uicomponents.extentions.hideKeyboard + + +class EditTextFragment : Fragment(R.layout.fragment_edit_text) { + + private lateinit var binding: FragmentEditTextBinding + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View { + binding = FragmentEditTextBinding.inflate(layoutInflater) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupListeners() + } + + private fun setupListeners() { + binding.root.setOnFocusChangeListener { view, isFocused -> + if (isFocused) view.hideKeyboard() + } + + binding.etEmail.setOnFocusChangeListener { emailView, isFocused -> + val email = binding.etEmail.text.toString() + binding.emailContainer.error = if (!isFocused && !isValidEmail(email)) { + "Invalid email address" + } else { + null + } + } + + binding.etEmail.addTextChangedListener { + if (it?.length == 0) binding.emailContainer.error = null + } + + binding.btnClear.setOnClickListener { + binding.root.children.forEach { + if (it is EditText) { + it.text.clear() + } else if (it is TextInputLayout) { + it.editText?.text?.clear() + } + } + } + } + + private fun isValidEmail(email: String): Boolean { + val pattern = Patterns.EMAIL_ADDRESS + return pattern.matcher(email).matches() + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/FabFragment.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/FabFragment.kt new file mode 100644 index 0000000..e935178 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/FabFragment.kt @@ -0,0 +1,69 @@ +package com.krunal.demo.uicomponents + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import com.google.android.material.snackbar.Snackbar +import com.krunal.demo.R +import com.krunal.demo.databinding.FragmentFabBinding + +class FabFragment : Fragment(R.layout.fragment_fab) { + + private lateinit var binding: FragmentFabBinding + private var visible: Boolean = false + private lateinit var snackBar: Snackbar + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View { + binding = FragmentFabBinding.inflate(layoutInflater) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding.fabAdd.setOnClickListener { + changeVisibility() + if (visible) snackBar.show() + } + setupExpandableFab() + setupSnackBar() + } + + private fun changeVisibility() { + visible = visible.not() + binding.fabAdd.setImageResource( + if (visible) R.drawable.ic_cross + else R.drawable.ic_add + ) + binding.fabEdit.visibility = if (visible) View.VISIBLE else View.INVISIBLE + binding.fabImage.visibility = if (visible) View.VISIBLE else View.INVISIBLE + } + + private fun setupExpandableFab() { + binding.fabExtended1.isChecked = true + binding.fabExtended2.isChecked = true + + binding.fabExtended1.addOnCheckedChangeListener { _, isChecked -> + binding.fabExtended1.isExtended = isChecked + } + + binding.fabExtended2.addOnCheckedChangeListener { _, isChecked -> + binding.fabExtended2.isExtended = isChecked + } + + binding.fabExtended3.setOnClickListener { + binding.fabExtended3.isExtended = binding.fabExtended3.isExtended.not() + } + } + + private fun setupSnackBar() { + snackBar = Snackbar.make(binding.root, "Fab button opened", Snackbar.LENGTH_SHORT) + snackBar.anchorView = binding.fabImage + snackBar.setAction("Close") { + changeVisibility() + } + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/FrameLayoutFragment.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/FrameLayoutFragment.kt new file mode 100644 index 0000000..c833914 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/FrameLayoutFragment.kt @@ -0,0 +1,8 @@ +package com.krunal.demo.uicomponents + +import androidx.fragment.app.Fragment +import com.krunal.demo.R + +class FrameLayoutFragment : Fragment(R.layout.fragment_frame_layout) { + +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/LinearLayoutFragment.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/LinearLayoutFragment.kt new file mode 100644 index 0000000..00d1a6b --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/LinearLayoutFragment.kt @@ -0,0 +1,8 @@ +package com.krunal.demo.uicomponents + +import androidx.fragment.app.Fragment +import com.krunal.demo.R + +class LinearLayoutFragment : Fragment(R.layout.fragment_linear_layout) { + +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/ProgressBarFragment.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/ProgressBarFragment.kt new file mode 100644 index 0000000..3c6a3cd --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/ProgressBarFragment.kt @@ -0,0 +1,41 @@ +package com.krunal.demo.uicomponents + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import com.krunal.demo.databinding.FragmentProgressBarBinding +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch + +class ProgressBarFragment : Fragment() { + + private lateinit var binding: FragmentProgressBarBinding + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View { + binding = FragmentProgressBarBinding.inflate(layoutInflater) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupProgress() + } + + private fun setupProgress() { + CoroutineScope(Dispatchers.Main).launch { + repeat(10) { + delay(1000) + binding.pbCircularDeterminate.incrementProgressBy(5) + binding.pbCircularIndeterminate1.incrementProgressBy(5) + binding.pbCircularIndeterminate2.incrementProgressBy(5) + binding.pbHorizontalDeterminate.incrementProgressBy(-5) + } + } + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/RadioFragment.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/RadioFragment.kt new file mode 100644 index 0000000..2b1e07c --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/RadioFragment.kt @@ -0,0 +1,52 @@ +package com.krunal.demo.uicomponents + +import android.os.Bundle +import android.view.Gravity +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Toast +import androidx.fragment.app.Fragment +import com.google.android.material.radiobutton.MaterialRadioButton +import com.krunal.demo.R +import com.krunal.demo.databinding.FragmentRadioBinding + +class RadioFragment : Fragment(R.layout.fragment_radio) { + + private lateinit var binding: FragmentRadioBinding + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View { + binding = FragmentRadioBinding.inflate(layoutInflater) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + showSelected() + } + + private fun showSelected() { + val toast = Toast(requireContext()) + toast.duration = Toast.LENGTH_SHORT + toast.setGravity(Gravity.CENTER_HORIZONTAL, 0, 0) + + binding.rgGender.setOnCheckedChangeListener { _, id -> + toast.setText( + when (id) { + binding.radioMale.id -> "Male" + binding.radioFemale.id -> "Female" + else -> "Other" + } + ) + toast.show() + } + binding.rgHouse.setOnCheckedChangeListener { _, id -> + toast.setText( + binding.root.findViewById(id).text + ) + toast.show() + } + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/RelativeLayoutFragment.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/RelativeLayoutFragment.kt new file mode 100644 index 0000000..871b5cf --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/RelativeLayoutFragment.kt @@ -0,0 +1,8 @@ +package com.krunal.demo.uicomponents + +import androidx.fragment.app.Fragment +import com.krunal.demo.R + +class RelativeLayoutFragment : Fragment(R.layout.fragment_relative_layout) { + +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/SliderFragment.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/SliderFragment.kt new file mode 100644 index 0000000..e79bbf7 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/SliderFragment.kt @@ -0,0 +1,50 @@ +package com.krunal.demo.uicomponents + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import com.krunal.demo.R +import com.krunal.demo.databinding.FragmentSliderBinding + +class SliderFragment : Fragment(R.layout.fragment_slider) { + + private lateinit var binding: FragmentSliderBinding + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View { + binding = FragmentSliderBinding.inflate(layoutInflater) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupBars() + } + + private fun setupBars() { + binding.sliderNormal.addOnChangeListener { _, value, _ -> + binding.tvSliderNormal.text = getString(R.string.slider_value, value) + } + + binding.sliderDiscrete.addOnChangeListener { _, value, _ -> + binding.tvSliderDiscrete.text = getString(R.string.slider_value, value) + } + + binding.sliderRange.addOnChangeListener { _, _, _ -> + val values = binding.sliderRange.values + binding.tvSliderRange.text = getString( + R.string.slider_range_value, values[0], values[1] + ) + } + + binding.sliderRangeDiscrete.addOnChangeListener { _, _, _ -> + val values = binding.sliderRangeDiscrete.values + binding.tvSliderRangeDiscrete.text = getString( + R.string.slider_range_value, values[0], values[1] + ) + } + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/SnackBarFragment.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/SnackBarFragment.kt new file mode 100644 index 0000000..269a034 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/SnackBarFragment.kt @@ -0,0 +1,69 @@ +package com.krunal.demo.uicomponents + +import android.graphics.Color +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Button +import androidx.fragment.app.Fragment +import com.google.android.material.behavior.SwipeDismissBehavior +import com.google.android.material.snackbar.BaseTransientBottomBar.ANIMATION_MODE_SLIDE +import com.google.android.material.snackbar.BaseTransientBottomBar.Behavior +import com.google.android.material.snackbar.Snackbar +import com.krunal.demo.R +import com.krunal.demo.databinding.FragmentSnackbarBinding + +class SnackBarFragment : Fragment(R.layout.fragment_snackbar) { + + private lateinit var binding: FragmentSnackbarBinding + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View { + binding = FragmentSnackbarBinding.inflate(layoutInflater) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupSnackBars() + } + + private fun setupSnackBars() { + binding.btnShortSnackBar.setOnClickListener { + Snackbar.make(requireView(), (it as Button).text, Snackbar.LENGTH_SHORT).show() + } + + binding.btnLongSnackBar.setOnClickListener { + Snackbar.make(requireView(), (it as Button).text, Snackbar.LENGTH_LONG).show() + } + + binding.btnMultiLineSnackBar.setOnClickListener { + Snackbar.make(requireView(), (it as Button).text, Snackbar.LENGTH_SHORT).show() + } + + binding.btnActionSnackBar.setOnClickListener { + val snackBar = Snackbar.make(requireView(), (it as Button).text, Snackbar.LENGTH_LONG) + .setBackgroundTint(Color.GRAY).setActionTextColor(Color.GREEN) + snackBar.setAction("Red") { + snackBar.setBackgroundTint(Color.RED) + } + snackBar.show() + } + + binding.btnAnchorSnackBar.setOnClickListener { + Snackbar.make(requireView(), (it as Button).text, Snackbar.LENGTH_LONG) + .setAnchorView(binding.btnShortSnackBar).show() + } + + binding.btnSwipeableSnackBar.setOnClickListener { + Snackbar.make(requireView(), (it as Button).text, Snackbar.LENGTH_LONG).apply { + behavior = Behavior() + animationMode = ANIMATION_MODE_SLIDE + behavior.setSwipeDirection(SwipeDismissBehavior.SWIPE_DIRECTION_ANY) + show() + } + } + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/SpanFragment.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/SpanFragment.kt new file mode 100644 index 0000000..c392e46 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/SpanFragment.kt @@ -0,0 +1,103 @@ +package com.krunal.demo.uicomponents + +import android.graphics.Color +import android.os.Bundle +import android.text.Spannable +import android.text.SpannableStringBuilder +import android.text.method.LinkMovementMethod +import android.text.style.ForegroundColorSpan +import android.text.style.StrikethroughSpan +import android.text.style.URLSpan +import android.text.style.UnderlineSpan +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.Toast +import androidx.core.text.bold +import androidx.core.text.italic +import androidx.core.text.toSpannable +import androidx.fragment.app.Fragment +import com.krunal.demo.R +import com.krunal.demo.databinding.FragmentSpanBinding +import com.krunal.demo.uicomponents.extentions.addTextView +import com.krunal.demo.uicomponents.views.MyClickableSpan + +class SpanFragment : Fragment() { + + private lateinit var binding: FragmentSpanBinding + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View { + binding = FragmentSpanBinding.inflate(layoutInflater) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupSpans() + } + + private fun setupSpans() { + // Normal foreground color span + val textNormal = binding.tvColorSpan.text + val spanNormal = textNormal.toSpannable().apply { + setSpan( + ForegroundColorSpan(Color.RED), 8, 11, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE + ) + setSpan( + ForegroundColorSpan(Color.GREEN), 16, 21, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE + ) + } + binding.tvColorSpan.text = spanNormal + + // Underlined and LineThrough + val textUnderlineLineThrough = binding.tvUnderlineLineThroughSpan.text + val spanUnderlineLineThrough = textUnderlineLineThrough.toSpannable().apply { + setSpan( + UnderlineSpan(), 8, 18, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE + ) + setSpan( + StrikethroughSpan(), 23, 34, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE + ) + } + binding.tvUnderlineLineThroughSpan.text = spanUnderlineLineThrough + + // Clickable Link + val spanLink = SpannableStringBuilder("This is my ").bold { + italic { + append( + "Portfolio", + URLSpan("https://krunalpatel.me"), + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE + ) + } + }.append(" or you can visit my ").bold { + italic { + append( + "Blog", + URLSpan("https://blog.krunalpatel.me"), + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE + ) + } + } + binding.tvLink.text = spanLink + binding.tvLink.movementMethod = LinkMovementMethod.getInstance() + + // Clickable span + val clickableText = "You can click on any word" + + SpannableStringBuilder().also { spanBuilder -> + clickableText.split(" ").forEach { word -> + spanBuilder.append("$word ", MyClickableSpan { + Toast.makeText( + requireContext(), + getString(R.string.click_toast_text, word), + Toast.LENGTH_SHORT + ).show() + }, Spannable.SPAN_INCLUSIVE_EXCLUSIVE) + } + binding.root.addTextView(spanBuilder) + } + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/SpinnerFragment.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/SpinnerFragment.kt new file mode 100644 index 0000000..3f4b669 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/SpinnerFragment.kt @@ -0,0 +1,64 @@ +package com.krunal.demo.uicomponents + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.AdapterView +import android.widget.ArrayAdapter +import android.widget.Toast +import androidx.fragment.app.Fragment +import com.krunal.demo.R +import com.krunal.demo.databinding.FragmentSpinnerBinding +import com.krunal.demo.uicomponents.adapters.TimezoneAdapter + +class SpinnerFragment : Fragment(), AdapterView.OnItemSelectedListener { + + private lateinit var binding: FragmentSpinnerBinding + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View { + binding = FragmentSpinnerBinding.inflate(layoutInflater) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupSpinners() + } + + private fun setupSpinners() { + ArrayAdapter.createFromResource( + requireContext(), R.array.timezones, android.R.layout.simple_spinner_item + ).also { adapter -> + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item) + binding.spinnerNormal.adapter = adapter + binding.spinnerDialog.adapter = adapter + binding.spinnerUnderlined.adapter = adapter + binding.spinnerBordered.adapter = adapter + binding.autoCompleteTimezone.setAdapter(adapter) + binding.spinnerDisabled.adapter = adapter + } + + binding.spinnerBordered.setSelection(4, true) + binding.spinnerNormal.onItemSelectedListener = this + binding.spinnerDisabled.isEnabled = false + + val adapter = TimezoneAdapter(requireContext(), resources.getStringArray(R.array.timezones)) + binding.spinnerCustom.adapter = adapter + } + + override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) { + Toast.makeText( + requireContext(), + binding.spinnerNormal.selectedItem.toString(), + Toast.LENGTH_SHORT + ).show() + } + + override fun onNothingSelected(parent: AdapterView<*>?) { + Toast.makeText(requireContext(), getString(R.string.nothing_selected), Toast.LENGTH_SHORT) + .show() + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/TabLayoutFragment.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/TabLayoutFragment.kt new file mode 100644 index 0000000..838a944 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/TabLayoutFragment.kt @@ -0,0 +1,41 @@ +package com.krunal.demo.uicomponents + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import com.google.android.material.tabs.TabLayout +import com.google.android.material.tabs.TabLayoutMediator +import com.krunal.demo.R +import com.krunal.demo.databinding.FragmentTabLayoutBinding +import com.krunal.demo.uicomponents.adapters.ViewPagerAdapter + +class TabLayoutFragment : Fragment(R.layout.fragment_tab_layout) { + + private lateinit var binding: FragmentTabLayoutBinding + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View { + binding = FragmentTabLayoutBinding.inflate(layoutInflater) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupTabsWithPager() + } + + private fun setupTabsWithPager() { + val viewPagerAdapter = ViewPagerAdapter(requireActivity()) + binding.viewPager.adapter = viewPagerAdapter + TabLayoutMediator(binding.tabLayout, binding.viewPager) { tab, position -> + tab.text = when (position) { + 0 -> "Home" + 1 -> "Chat" + else -> "Call" + } + }.attach() + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/ThemeFragment.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/ThemeFragment.kt new file mode 100644 index 0000000..7fc27ed --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/ThemeFragment.kt @@ -0,0 +1,57 @@ +package com.krunal.demo.uicomponents + +import android.os.Bundle +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.lifecycle.lifecycleScope +import com.krunal.demo.databinding.FragmentThemeBinding +import com.krunal.demo.uicomponents.adapters.ThemeAdapter +import com.krunal.demo.uicomponents.extentions.isDarkMode +import com.krunal.demo.uicomponents.helpers.ThemeHelper +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch + +class ThemeFragment : Fragment() { + + private lateinit var binding: FragmentThemeBinding + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View { + binding = FragmentThemeBinding.inflate(layoutInflater) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupTheme() + } + + private fun setupTheme() { + val themes = ThemeHelper.getThemes(requireContext(), requireActivity().isDarkMode) + val themeAdapter = ThemeAdapter( + requireContext(), themes + ) + + binding.gvTheme.adapter = themeAdapter + binding.gvTheme.setItemChecked(ThemeHelper.getThemeAccent().ordinal, true) + lifecycleScope.launch { + delay(100) + + binding.gvTheme.setItemChecked(ThemeHelper.getThemeAccent().ordinal, true) + binding.gvTheme.setSelection(ThemeHelper.getThemeAccent().ordinal) + Log.d("Tag", ThemeHelper.getThemeAccent().ordinal.toString()) + Log.d("Tag", binding.gvTheme.selectedItemPosition.toString()) + } + // binding.gvTheme.selectedView.performClick() // selectedView is null + + binding.gvTheme.setOnItemClickListener { _, _, position, _ -> + ThemeHelper.setThemeAccent(themes[position].accentColor) + // TODO: Restart activity +// activity?.recreate() + } + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/ToastFragment.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/ToastFragment.kt new file mode 100644 index 0000000..b6822f5 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/ToastFragment.kt @@ -0,0 +1,74 @@ +package com.krunal.demo.uicomponents + +import android.graphics.Color +import android.os.Bundle +import android.view.Gravity +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.TextView +import android.widget.Toast +import androidx.core.content.res.ResourcesCompat +import androidx.fragment.app.Fragment +import com.krunal.demo.R +import com.krunal.demo.databinding.FragmentToastBinding + +class ToastFragment : Fragment(R.layout.fragment_toast) { + private lateinit var binding: FragmentToastBinding + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + binding = FragmentToastBinding.inflate(layoutInflater) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupButtons() + } + + // Set click listeners for buttons to show toast + private fun setupButtons() { + // Short toast + binding.btnShortToast.setOnClickListener { + Toast.makeText(requireContext(), "This is short Toast", Toast.LENGTH_SHORT).show() + } + + // Long toast + binding.btnLongToast.setOnClickListener { + Toast.makeText(requireContext(), "This is long Toast", Toast.LENGTH_LONG).show() + } + + // Custom short success toast + binding.btnSuccessToast.setOnClickListener { + showToast(R.drawable.ic_check, "Custom success toast", backgroundColor = Color.GREEN) + } + + // Custom long error toast + binding.btnErrorToast.setOnClickListener { + showToast(R.drawable.ic_cross, "Custom error toast", Toast.LENGTH_LONG, Color.RED) + } + } + + // Make and show custom toast + private fun showToast(iconId: Int, text: String, length: Int = Toast.LENGTH_SHORT, backgroundColor: Int = Color.GRAY) { + val toastView = layoutInflater.inflate(R.layout.custom_toast, requireActivity().findViewById(R.id.customToast)) + val imgViewStatus = toastView.findViewById(R.id.imgViewStatus) + val tvMessage = toastView.findViewById(R.id.tvMessage) + + toastView.setBackgroundColor(backgroundColor) + imgViewStatus.setImageResource(iconId) + tvMessage.text = text + + Toast(requireContext()).apply { + setGravity(Gravity.CENTER, 0, 0) + duration = length + view = toastView + show() + } + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/adapters/ThemeAdapter.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/adapters/ThemeAdapter.kt new file mode 100644 index 0000000..81716d3 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/adapters/ThemeAdapter.kt @@ -0,0 +1,33 @@ +package com.krunal.demo.uicomponents.adapters + +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.BaseAdapter +import android.widget.TextView +import com.google.android.material.card.MaterialCardView +import com.krunal.demo.R +import com.krunal.demo.uicomponents.models.Theme + +class ThemeAdapter( + private val context: Context, + private val themes: List +) : BaseAdapter() { + override fun getCount(): Int = themes.size + + override fun getItem(position: Int): Any? = null + + override fun getItemId(position: Int): Long = 0 + + override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View { + val inflater = LayoutInflater.from(context) + val view = inflater.inflate(R.layout.theme_layout, null) + val tvName = view.findViewById(R.id.tvThemeName) + val viewTheme = view.findViewById(R.id.cardThemeColor) + + tvName.text = themes[position].name + viewTheme.setCardBackgroundColor(themes[position].color) + return view + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/adapters/TimezoneAdapter.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/adapters/TimezoneAdapter.kt new file mode 100644 index 0000000..4f445da --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/adapters/TimezoneAdapter.kt @@ -0,0 +1,29 @@ +package com.krunal.demo.uicomponents.adapters + +import android.annotation.SuppressLint +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.BaseAdapter +import android.widget.TextView +import com.krunal.demo.R + +class TimezoneAdapter(private val context: Context, private val timezones: Array): BaseAdapter() { + + override fun getCount() = timezones.size + + override fun getItem(position: Int) = null + + override fun getItemId(position: Int): Long = 0 + + @SuppressLint("ViewHolder") + override fun getView(position: Int, convertView: View?, parent: ViewGroup?): View { + val inflater = LayoutInflater.from(context) + val view = inflater.inflate(R.layout.timezone_spinner, null) + val tvName = view.findViewById(R.id.tvTimezone) + tvName.text = timezones[position] + + return view + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/adapters/ViewBindingAdapter.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/adapters/ViewBindingAdapter.kt new file mode 100644 index 0000000..548048c --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/adapters/ViewBindingAdapter.kt @@ -0,0 +1,61 @@ +package com.krunal.demo.uicomponents.adapters + +import android.graphics.Color +import android.graphics.drawable.GradientDrawable +import android.view.View +import android.widget.ImageView +import android.widget.TextView +import androidx.annotation.ColorRes +import androidx.annotation.DrawableRes +import androidx.appcompat.content.res.AppCompatResources +import androidx.core.graphics.ColorUtils +import androidx.databinding.BindingAdapter + +@BindingAdapter("drawableResource") +fun ImageView.setDrawableResource(@DrawableRes drawableResource: Int) { + setImageResource(drawableResource) +} + +@BindingAdapter("drawableResource") +fun TextView.setStartDrawableResource(@DrawableRes drawableResource: Int) { + setCompoundDrawablesRelativeWithIntrinsicBounds( + AppCompatResources.getDrawable(context, drawableResource), null, null, null + ) +} + +@BindingAdapter("drawableColorResource") +fun TextView.setStartColorResource(@ColorRes colorResource: Int) { + compoundDrawablesRelative.filterNotNull().forEach { drawable -> + drawable.mutate().setTint(resources.getColor(colorResource, context.theme)) + } +} + +@BindingAdapter("textColorResource") +fun TextView.setTextColorResource(@ColorRes colorResource: Int) { + setTextColor(resources.getColor(colorResource, context.theme)) +} + +@BindingAdapter("textColor") +fun TextView.setTextColor(color: Int) { + setTextColor(color) +} + +@BindingAdapter("backgroundColor") +fun View.setViewBackgroundColor(color: Int) { + setBackgroundColor(color) +} + +@BindingAdapter("gradientStartColor", "gradientEndColor", "angle", requireAll = false) +fun View.setGradientBackground(gradientStartColor: Int?, gradientEndColor: Int?, angle: Int?) { + gradientStartColor?.let { it -> + ColorUtils.setAlphaComponent(it, 0x4f).let { color -> + GradientDrawable( + GradientDrawable.Orientation.TR_BL, + intArrayOf(color, gradientEndColor ?: Color.TRANSPARENT) + ).also { + it.cornerRadius = 0f + background = it + } + } + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/adapters/ViewPagerAdapter.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/adapters/ViewPagerAdapter.kt new file mode 100644 index 0000000..b88918c --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/adapters/ViewPagerAdapter.kt @@ -0,0 +1,16 @@ +package com.krunal.demo.uicomponents.adapters + +import androidx.fragment.app.FragmentActivity +import androidx.viewpager2.adapter.FragmentStateAdapter +import com.krunal.demo.uicomponents.tablayoutfragments.CallsFragment +import com.krunal.demo.uicomponents.tablayoutfragments.ChatFragment +import com.krunal.demo.uicomponents.tablayoutfragments.HomeFragment + +class ViewPagerAdapter(fa: FragmentActivity): FragmentStateAdapter(fa) { + + private val fragments = listOf(HomeFragment(), ChatFragment(), CallsFragment()) + + override fun getItemCount() = fragments.count() + + override fun createFragment(position: Int) = fragments[position] +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/binding/DataBindingFragment.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/binding/DataBindingFragment.kt new file mode 100644 index 0000000..c89d361 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/binding/DataBindingFragment.kt @@ -0,0 +1,26 @@ +package com.krunal.demo.uicomponents.binding + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import com.krunal.demo.databinding.FragmentDataBindingBinding + +class DataBindingFragment : Fragment() { + + private lateinit var binding: FragmentDataBindingBinding + private val viewModel: DataBingingViewModel by viewModels() + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View { + binding = FragmentDataBindingBinding.inflate(inflater) + binding.viewModel = viewModel + binding.lifecycleOwner = viewLifecycleOwner + return binding.root + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/binding/DataBingingViewModel.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/binding/DataBingingViewModel.kt new file mode 100644 index 0000000..ced1218 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/binding/DataBingingViewModel.kt @@ -0,0 +1,34 @@ +package com.krunal.demo.uicomponents.binding + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.krunal.demo.uicomponents.models.Name +import kotlinx.coroutines.delay +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.launch + +class DataBingingViewModel : ViewModel() { + + private val _nameFlow = MutableStateFlow(null) + val nameFlow: StateFlow = _nameFlow + + init { + randomName() + } + + /** Generate and emmit random names to [nameFlow] */ + private fun randomName() { + // List of random names + val names = listOf( + Name("Harry", "Potter") + ) + + viewModelScope.launch { + while (true) { + delay(3000) + _nameFlow.emit(names.first()) + } + } + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/cardscreen/CardFragment.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/cardscreen/CardFragment.kt new file mode 100644 index 0000000..f296686 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/cardscreen/CardFragment.kt @@ -0,0 +1,117 @@ +package com.krunal.demo.uicomponents.cardscreen + +import android.graphics.Color +import android.graphics.drawable.ColorDrawable +import android.os.Bundle +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.LinearLayout +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import androidx.lifecycle.lifecycleScope +import com.google.android.material.chip.Chip +import com.google.android.material.navigation.NavigationBarView +import com.krunal.demo.R +import com.krunal.demo.UIComponentsActivity +import com.krunal.demo.databinding.CardLayoutBinding +import com.krunal.demo.databinding.FragmentCardBinding +import com.krunal.demo.uicomponents.dialogs.MyDatePickerDialog +import com.krunal.demo.uicomponents.extentions.dpFormat +import com.krunal.demo.uicomponents.models.CardDetail +import com.krunal.demo.uicomponents.sheets.OperationsBottomSheetFragment +import kotlinx.coroutines.launch + +class CardFragment : Fragment() { + + private lateinit var binding: FragmentCardBinding + private val viewModel: CardFragmentViewModel by viewModels() + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View { + binding = FragmentCardBinding.inflate(layoutInflater) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupUI() + } + + private fun setupUI() { + setupBottomBar() + (activity as? UIComponentsActivity)?.supportActionBar?.apply { + title = getString(R.string.my_cards) + setBackgroundDrawable(ColorDrawable(Color.parseColor("#2F7CEF"))) + elevation = 0f + // TODO: Make title center + } + + setupCardChips() + + setupCard() + + binding.cardOperations.btnDetails.setOnClickListener { + OperationsBottomSheetFragment().show(childFragmentManager, null) + } + + binding.cardOperations.btnCalendar.setOnClickListener { + MyDatePickerDialog().show(childFragmentManager, null) + } + } + + private fun setupCardChips() { + viewModel.dummyCardDetails.forEach { card -> + (layoutInflater.inflate(R.layout.card_chip, null) as? Chip)?.apply { + text = getString(R.string.card_name, card.type, card.number) + isChecked = card.isSelected + setOnCheckedChangeListener { _, isChecked -> + viewModel.updateCardSelection( + card, + isChecked + ) + } + }.also { cardChip -> + binding.cgCard.addView(cardChip) + } + } + } + + private fun setupCard() { + addSelectedCards(viewModel.dummyCardDetails) + lifecycleScope.launch { + viewModel.selectedCards.collect { cardList -> + binding.llCards.removeViews(1, binding.llCards.childCount - 1) + addSelectedCards(cardList) + } + } + } + + private fun addSelectedCards(cardList: List) { + cardList + .filter { it.isSelected } + .forEach { card -> + val cardBinding = CardLayoutBinding.inflate(layoutInflater) + cardBinding.card = card + + val layoutParams = LinearLayout.LayoutParams( + 300.dpFormat(requireContext()), + 200.dpFormat(requireContext()), + ).apply { + marginEnd = 18.dpFormat(requireContext()) + } + + cardBinding.root.layoutParams = layoutParams + binding.llCards.addView(cardBinding.root) + } + } + + private fun setupBottomBar() { + binding.bottomNavigation.apply { + selectedItemId = menu.getItem(1).itemId + labelVisibilityMode = NavigationBarView.LABEL_VISIBILITY_SELECTED + } + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/cardscreen/CardFragmentViewModel.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/cardscreen/CardFragmentViewModel.kt new file mode 100644 index 0000000..2c76f8e --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/cardscreen/CardFragmentViewModel.kt @@ -0,0 +1,29 @@ +package com.krunal.demo.uicomponents.cardscreen + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.krunal.demo.uicomponents.models.CardDetail +import com.krunal.demo.uicomponents.models.enums.CardType +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.SharedFlow +import kotlinx.coroutines.launch + +class CardFragmentViewModel : ViewModel() { + + private val _selectedCards = MutableSharedFlow>() + val selectedCards: SharedFlow> = _selectedCards + + var dummyCardDetails: List = buildList { + add(CardDetail(CardType.DEBIT, 1963, "$2,983.78", true)) + add(CardDetail(CardType.DEBIT, 1822, "$1,002.02")) + add(CardDetail(CardType.CREDIT, 2291, "$540.00")) + } + + fun updateCardSelection(card: CardDetail, isSelected: Boolean) { + dummyCardDetails.find { it.number == card.number }?.isSelected = + isSelected + viewModelScope.launch { + _selectedCards.emit(dummyCardDetails) + } + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/constraintLayouts/ChainBiasFragment.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/constraintLayouts/ChainBiasFragment.kt new file mode 100644 index 0000000..dfb8893 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/constraintLayouts/ChainBiasFragment.kt @@ -0,0 +1,5 @@ +package com.krunal.demo.uicomponents.constraintLayouts + +import androidx.fragment.app.Fragment + +class ChainBiasFragment : Fragment() \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/constraintLayouts/CircularFragment.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/constraintLayouts/CircularFragment.kt new file mode 100644 index 0000000..2f8fbe6 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/constraintLayouts/CircularFragment.kt @@ -0,0 +1,32 @@ +package com.krunal.demo.uicomponents.constraintLayouts + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import com.krunal.demo.R +import com.krunal.demo.databinding.FragmentClCircularBinding + +class CircularFragment : Fragment(R.layout.fragment_cl_circular) { + + private lateinit var binding: FragmentClCircularBinding + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View { + binding = FragmentClCircularBinding.inflate(layoutInflater) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupUI() + } + + private fun setupUI() { + binding.imgBtnMain.setOnCheckedChangeListener { _, isChecked -> + binding.groupEmojis.visibility = if (isChecked) View.VISIBLE else View.INVISIBLE + } + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/constraintLayouts/GuidelineBarrierFragment.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/constraintLayouts/GuidelineBarrierFragment.kt new file mode 100644 index 0000000..64d7c05 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/constraintLayouts/GuidelineBarrierFragment.kt @@ -0,0 +1,5 @@ +package com.krunal.demo.uicomponents.constraintLayouts + +import androidx.fragment.app.Fragment + +class GuidelineBarrierFragment : Fragment() \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/constraintLayouts/RelativeFragment.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/constraintLayouts/RelativeFragment.kt new file mode 100644 index 0000000..18dac3a --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/constraintLayouts/RelativeFragment.kt @@ -0,0 +1,6 @@ +package com.krunal.demo.uicomponents.constraintLayouts + +import androidx.fragment.app.Fragment +import com.krunal.demo.R + +class RelativeFragment : Fragment(R.layout.fragment_cl_relative) \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/dialogs/MyDatePickerDialog.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/dialogs/MyDatePickerDialog.kt new file mode 100644 index 0000000..369e675 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/dialogs/MyDatePickerDialog.kt @@ -0,0 +1,21 @@ +package com.krunal.demo.uicomponents.dialogs + +import android.app.DatePickerDialog +import android.app.Dialog +import android.os.Bundle +import androidx.fragment.app.DialogFragment +import java.util.Calendar + +class MyDatePickerDialog : DialogFragment() { + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + val mCalendar: Calendar = Calendar.getInstance() + val year = mCalendar.get(Calendar.YEAR) + val month = mCalendar.get(Calendar.MONTH) + val dayOfMonth = mCalendar.get(Calendar.DAY_OF_MONTH) + + return DatePickerDialog(requireActivity(), null, year, month, dayOfMonth).apply { + datePicker.maxDate = Calendar.getInstance().timeInMillis + } + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/extentions/ActivityExtentions.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/extentions/ActivityExtentions.kt new file mode 100644 index 0000000..36b32d9 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/extentions/ActivityExtentions.kt @@ -0,0 +1,21 @@ +package com.krunal.demo.uicomponents.extentions + +import android.app.Activity +import android.content.Context +import android.content.res.Configuration +import android.util.TypedValue + + +fun Context.getThemeColor(resId: Int): Int { + val typedValue = TypedValue() + theme.resolveAttribute(resId, typedValue, true) + return typedValue.data +} + +val Activity.isDarkMode: Boolean + get() = when (resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) { + Configuration.UI_MODE_NIGHT_YES -> true + Configuration.UI_MODE_NIGHT_NO -> false + Configuration.UI_MODE_NIGHT_UNDEFINED -> false + else -> false + } \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/extentions/LinearLayoutExtentions.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/extentions/LinearLayoutExtentions.kt new file mode 100644 index 0000000..4ee254f --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/extentions/LinearLayoutExtentions.kt @@ -0,0 +1,29 @@ +package com.krunal.demo.uicomponents.extentions + +import android.text.Spanned +import android.text.method.LinkMovementMethod +import android.widget.LinearLayout +import android.widget.TextView +import androidx.core.text.toSpanned + +// Add text to the LinearLayout by creating new TextView +fun LinearLayout.addTextView(text: String) { + addTextView(text.toSpanned()) +} + +// Add spanned text to the LinearLayout by creating new TextView +fun LinearLayout.addTextView(spanText: Spanned) { + TextView(context).apply { + layoutParams = LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.WRAP_CONTENT, + ).apply { + topMargin = 24.dpFormat(context) + } + text = spanText + textSize = 18f + movementMethod = LinkMovementMethod.getInstance() + }.also { textView -> + addView(textView) + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/extentions/NumberExtentions.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/extentions/NumberExtentions.kt new file mode 100644 index 0000000..43bc8c3 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/extentions/NumberExtentions.kt @@ -0,0 +1,10 @@ +package com.krunal.demo.uicomponents.extentions + +import android.content.Context +import android.util.DisplayMetrics +import kotlin.math.roundToInt + +fun Int.dpFormat(context: Context): Int { + val displayMetrics = context.resources.displayMetrics + return (this * (displayMetrics.xdpi / DisplayMetrics.DENSITY_DEFAULT)).roundToInt() +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/extentions/TypedArrayExtentions.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/extentions/TypedArrayExtentions.kt new file mode 100644 index 0000000..9e6af51 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/extentions/TypedArrayExtentions.kt @@ -0,0 +1,15 @@ +package com.krunal.demo.uicomponents.extentions + +import android.content.res.TypedArray + +fun TypedArray.toFloat(): List = buildList { + for (i in 0 until length()) { + add(getFloat(i, -1f)) + } +} + +fun TypedArray.toInt(): List = buildList { + for (i in 0 until length()) { + add(getInt(i, -1)) + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/extentions/ViewExtentions.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/extentions/ViewExtentions.kt new file mode 100644 index 0000000..856ac34 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/extentions/ViewExtentions.kt @@ -0,0 +1,10 @@ +package com.krunal.demo.uicomponents.extentions + +import android.app.Activity +import android.view.View +import android.view.inputmethod.InputMethodManager + +fun View.hideKeyboard() { + val inputMethodManager = context.getSystemService(Activity.INPUT_METHOD_SERVICE) as InputMethodManager + inputMethodManager.hideSoftInputFromWindow(windowToken, 0) +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/helpers/PreferenceHelper.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/helpers/PreferenceHelper.kt new file mode 100644 index 0000000..4c7cca8 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/helpers/PreferenceHelper.kt @@ -0,0 +1,69 @@ +package com.krunal.demo.uicomponents.helpers + +import android.content.Context +import android.content.SharedPreferences +import androidx.preference.PreferenceManager + +object PreferenceHelper { + + private lateinit var preferences: SharedPreferences + private lateinit var editor: SharedPreferences.Editor + + /** + * set the context that is being used to access the shared preferences + */ + fun initialize(context: Context) { + preferences = getDefaultSharedPreferences(context) + editor = preferences.edit() + } + + fun putString(key: String, value: String) { + editor.putString(key, value).commit() + } + + fun putBoolean(key: String, value: Boolean) { + editor.putBoolean(key, value).commit() + } + + fun putInt(key: String, value: Int) { + editor.putInt(key, value).commit() + } + + fun putFloat(key: String, value: Float) { + editor.putFloat(key, value).commit() + } + + fun putLong(key: String, value: Long) { + editor.putLong(key, value).commit() + } + + fun getString(key: String?, defValue: String): String { + return preferences.getString(key, defValue) ?: defValue + } + + fun getBoolean(key: String?, defValue: Boolean): Boolean { + return preferences.getBoolean(key, defValue) + } + + fun getInt(key: String?, defValue: Int): Int { + return runCatching { + preferences.getInt(key, defValue) + }.getOrElse { preferences.getLong(key, defValue.toLong()).toInt() } + } + + fun getLong(key: String?, defValue: Long): Long { + return preferences.getLong(key, defValue) + } + + fun getFloat(key: String?, defValue: Float): Float { + return preferences.getFloat(key, defValue) + } + + fun clearPreferences() { + editor.clear().apply() + } + + private fun getDefaultSharedPreferences(context: Context): SharedPreferences { + return PreferenceManager.getDefaultSharedPreferences(context) + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/helpers/ThemeHelper.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/helpers/ThemeHelper.kt new file mode 100644 index 0000000..2bedaae --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/helpers/ThemeHelper.kt @@ -0,0 +1,79 @@ +package com.krunal.demo.uicomponents.helpers + +import android.content.Context +import com.krunal.demo.R +import com.krunal.demo.uicomponents.models.Theme +import com.krunal.demo.uicomponents.models.enums.AccentColor +import com.krunal.demo.uicomponents.models.enums.ThemeMode +import com.krunal.demo.uicomponents.utils.PreferenceKeys + +object ThemeHelper { + fun getThemes(context: Context, isDark: Boolean = false): List { + val accentValues = AccentColor.values() + val accent = context.resources.getStringArray(R.array.accents) + + return buildList { + for (i in accentValues.indices) { + add( + Theme( + accent[i], accentValues[i], getAccentColor(context, accentValues[i], isDark) + ) + ) + } + } + } + + fun getThemeResource(accentColor: AccentColor): Int { + return when (accentColor) { + AccentColor.RED -> R.style.Theme_Red + AccentColor.BLUE -> R.style.Theme_Blue + AccentColor.YELLOW -> R.style.Theme_Yellow + AccentColor.GREEN -> R.style.Theme_Green + AccentColor.PURPLE -> R.style.Theme_Purple + AccentColor.VIOLET -> R.style.Theme_Violet + } + } + + fun getThemeMode(): ThemeMode { + return ThemeMode.valueOf( + PreferenceHelper.getString( + PreferenceKeys.THEME_MODE, + ThemeMode.AUTO.name + ) + ) + } + + fun setThemeMode(mode: ThemeMode) { + PreferenceHelper.putString(PreferenceKeys.THEME_MODE, mode.name) + } + + fun getThemeAccent(): AccentColor { + return AccentColor.valueOf( + PreferenceHelper.getString( + PreferenceKeys.ACCENT_COLOR, + AccentColor.VIOLET.name + ) + ) + } + + fun setThemeAccent(color: AccentColor) { + PreferenceHelper.putString(PreferenceKeys.ACCENT_COLOR, color.name) + } + + private fun getAccentColor( + context: Context, + accentColor: AccentColor, + isDarkMode: Boolean + ): Int { + return context.getColor( + when (accentColor) { + AccentColor.RED -> if (isDarkMode) R.color.red_md_theme_dark_primary else R.color.red_md_theme_light_primary + AccentColor.BLUE -> if (isDarkMode) R.color.blue_md_theme_dark_primary else R.color.blue_md_theme_light_primary + AccentColor.YELLOW -> if (isDarkMode) R.color.yellow_md_theme_dark_primary else R.color.yellow_md_theme_light_primary + AccentColor.GREEN -> if (isDarkMode) R.color.green_md_theme_dark_primary else R.color.green_md_theme_light_primary + AccentColor.PURPLE -> if (isDarkMode) R.color.purple_md_theme_dark_primary else R.color.purple_md_theme_light_primary + AccentColor.VIOLET -> if (isDarkMode) R.color.violet_theme_dark_primary else R.color.violet_theme_light_primary + } + ) + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/models/CardDetail.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/models/CardDetail.kt new file mode 100644 index 0000000..d910dbf --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/models/CardDetail.kt @@ -0,0 +1,18 @@ +package com.krunal.demo.uicomponents.models + +import com.krunal.demo.uicomponents.models.enums.CardType + +data class CardDetail( + val type: CardType, val number: Int, val amount: String = "$0" +) { + + var isSelected: Boolean = false + + constructor(type: CardType, number: Int, amount: String, isSelected: Boolean) : this( + type, + number, + amount + ) { + this.isSelected = isSelected + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/models/DrawableResource.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/models/DrawableResource.kt new file mode 100644 index 0000000..9f3f4fb --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/models/DrawableResource.kt @@ -0,0 +1,8 @@ +package com.krunal.demo.uicomponents.models + +import android.graphics.Bitmap as AndroidBitmap + +sealed interface DrawableResource { + data class Drawable(val id: Int): DrawableResource + data class Bitmap(val bitmap: AndroidBitmap): DrawableResource +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/models/Name.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/models/Name.kt new file mode 100644 index 0000000..cb56627 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/models/Name.kt @@ -0,0 +1,3 @@ +package com.krunal.demo.uicomponents.models + +data class Name(val firstName: String, val lastName: String) diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/models/Theme.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/models/Theme.kt new file mode 100644 index 0000000..400d7fa --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/models/Theme.kt @@ -0,0 +1,11 @@ +package com.krunal.demo.uicomponents.models + +import androidx.annotation.ColorRes +import com.krunal.demo.uicomponents.models.enums.AccentColor + +data class Theme( + val name: String, + val accentColor: AccentColor, + val color: Int, + val isDarkMode: Boolean = false +) diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/models/enums/AccentColor.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/models/enums/AccentColor.kt new file mode 100644 index 0000000..944286c --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/models/enums/AccentColor.kt @@ -0,0 +1,5 @@ +package com.krunal.demo.uicomponents.models.enums + +enum class AccentColor { + RED, BLUE, YELLOW, GREEN, PURPLE, VIOLET +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/models/enums/CardType.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/models/enums/CardType.kt new file mode 100644 index 0000000..d3ac2a3 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/models/enums/CardType.kt @@ -0,0 +1,11 @@ +package com.krunal.demo.uicomponents.models.enums + +import java.util.Locale + +enum class CardType { + DEBIT, CREDIT; + + override fun toString(): String { + return name.replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.ROOT) else it.toString() } + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/models/enums/ThemeMode.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/models/enums/ThemeMode.kt new file mode 100644 index 0000000..4b7b2f5 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/models/enums/ThemeMode.kt @@ -0,0 +1,5 @@ +package com.krunal.demo.uicomponents.models.enums + +enum class ThemeMode { + AUTO, DARK, LIGHT +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/picker/DatePickerFragment.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/picker/DatePickerFragment.kt new file mode 100644 index 0000000..0c78ada --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/picker/DatePickerFragment.kt @@ -0,0 +1,31 @@ +package com.krunal.demo.uicomponents.picker + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import com.krunal.demo.databinding.FragmentDatePickerBinding +import java.util.Date + +class DatePickerFragment : Fragment() { + + private lateinit var binding: FragmentDatePickerBinding + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View { + binding = FragmentDatePickerBinding.inflate(layoutInflater) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupPicker() + } + + private fun setupPicker() { + binding.dpCalender.maxDate = Date().time + binding.dpCalender.updateDate(2023, 1, 1) + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/picker/TimePickerFragment.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/picker/TimePickerFragment.kt new file mode 100644 index 0000000..8f99181 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/picker/TimePickerFragment.kt @@ -0,0 +1,62 @@ +package com.krunal.demo.uicomponents.picker + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.TimePicker +import android.widget.TimePicker.OnTimeChangedListener +import androidx.fragment.app.Fragment +import com.krunal.demo.R +import com.krunal.demo.databinding.FragmentTimePickerBinding + +class TimePickerFragment : Fragment(), OnTimeChangedListener { + + private lateinit var binding: FragmentTimePickerBinding + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View { + binding = FragmentTimePickerBinding.inflate(layoutInflater) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupPicker() + } + + private fun setupPicker() { + // Set is 24 hour format + binding.switch24hour.setOnCheckedChangeListener { _, isChecked -> + binding.tpClock.setIs24HourView(isChecked) + binding.tpSpinner.setIs24HourView(isChecked) + } + + binding.tpClock.setOnTimeChangedListener(this) + binding.tpSpinner.setOnTimeChangedListener(this) + } + + override fun onTimeChanged(timePicker: TimePicker, hourOfDay: Int, minute: Int) { + // Sync both time pickers + if (timePicker == binding.tpClock) { + binding.tpSpinner.hour = hourOfDay + binding.tpSpinner.minute = minute + } else { + binding.tpClock.hour = hourOfDay + binding.tpClock.minute = minute + } + + // Update time text view + binding.tvTime.text = if (timePicker.is24HourView) { + getString(R.string.time, hourOfDay, minute, "") + } else { + getString( + R.string.time, + if (hourOfDay % 12 == 0) 12 else hourOfDay, + minute, + if (hourOfDay < 12) "AM" else "PM" + ) + } + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/sheets/OperationsBottomSheetFragment.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/sheets/OperationsBottomSheetFragment.kt new file mode 100644 index 0000000..693b651 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/sheets/OperationsBottomSheetFragment.kt @@ -0,0 +1,35 @@ +package com.krunal.demo.uicomponents.sheets + +import android.annotation.SuppressLint +import android.app.Dialog +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.google.android.material.bottomsheet.BottomSheetBehavior +import com.google.android.material.bottomsheet.BottomSheetDialog +import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import com.krunal.demo.databinding.FragmentOperationsBottomSheetBinding + +class OperationsBottomSheetFragment : BottomSheetDialogFragment() { + + private lateinit var binding: FragmentOperationsBottomSheetBinding + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View { + binding = FragmentOperationsBottomSheetBinding.inflate(layoutInflater) + return binding.root + } + + @SuppressLint("RestrictedApi", "VisibleForTests") + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + val dialog = super.onCreateDialog(savedInstanceState) + + (dialog as BottomSheetDialog).behavior.apply { + isDraggable = true + state = BottomSheetBehavior.STATE_EXPANDED + } + return dialog + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/tablayoutfragments/CallsFragment.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/tablayoutfragments/CallsFragment.kt new file mode 100644 index 0000000..9c9e82c --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/tablayoutfragments/CallsFragment.kt @@ -0,0 +1,6 @@ +package com.krunal.demo.uicomponents.tablayoutfragments + +import androidx.fragment.app.Fragment +import com.krunal.demo.R + +class CallsFragment : Fragment(R.layout.fragment_calls) \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/tablayoutfragments/ChatFragment.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/tablayoutfragments/ChatFragment.kt new file mode 100644 index 0000000..9d9f346 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/tablayoutfragments/ChatFragment.kt @@ -0,0 +1,11 @@ +package com.krunal.demo.uicomponents.tablayoutfragments + +import android.os.Bundle +import androidx.fragment.app.Fragment +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.krunal.demo.R + +class ChatFragment : Fragment(R.layout.fragment_chat) { +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/tablayoutfragments/HomeFragment.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/tablayoutfragments/HomeFragment.kt new file mode 100644 index 0000000..8d124ab --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/tablayoutfragments/HomeFragment.kt @@ -0,0 +1,66 @@ +package com.krunal.demo.uicomponents.tablayoutfragments + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import com.google.android.material.tabs.TabLayout +import com.google.android.material.tabs.TabLayout.OnTabSelectedListener +import com.krunal.demo.R +import com.krunal.demo.databinding.FragmentHomeBinding + +class HomeFragment : Fragment(R.layout.fragment_home) { + + private lateinit var binding: FragmentHomeBinding + + override fun onCreateView( + inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? + ): View { + binding = FragmentHomeBinding.inflate(layoutInflater) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupTabs() + } + + private fun setupTabs() { + // Text tabs programmatically + listOf("Tab1", "Tab2", "Tab3", "Tab4").forEach { + binding.tabLayoutText.apply { + addTab(newTab().setText(it)) + } + } + + // Show/Hide lable + binding.tabLayoutSelectedText.apply { + for (i in 0..tabCount) { + if (i != selectedTabPosition) { + getTabAt(i)?.tabLabelVisibility = TabLayout.TAB_LABEL_VISIBILITY_UNLABELED + } + } + } + binding.tabLayoutSelectedText.addOnTabSelectedListener(object : OnTabSelectedListener { + override fun onTabSelected(tab: TabLayout.Tab?) { + tab?.tabLabelVisibility = TabLayout.TAB_LABEL_VISIBILITY_LABELED + } + + override fun onTabUnselected(tab: TabLayout.Tab?) { + tab?.tabLabelVisibility = TabLayout.TAB_LABEL_VISIBILITY_UNLABELED + } + + override fun onTabReselected(tab: TabLayout.Tab?) { + + } + }) + + // Scrollable tabs + listOf("Tab1", "Tab2", "Tab3").forEach { + binding.tabLayoutScrollable.apply { + addTab(newTab().setText(it)) + } + } + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/utils/PreferenceKeys.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/utils/PreferenceKeys.kt new file mode 100644 index 0000000..70f4061 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/utils/PreferenceKeys.kt @@ -0,0 +1,11 @@ +package com.krunal.demo.uicomponents.utils + +object PreferenceKeys { + + /** + * Appearance + */ + const val THEME_MODE = "theme_mode" + const val ACCENT_COLOR = "accent_color" + +} diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/views/CustomView.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/views/CustomView.kt new file mode 100644 index 0000000..02e24ce --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/views/CustomView.kt @@ -0,0 +1,41 @@ +package com.krunal.demo.uicomponents.views + +import android.content.Context +import android.content.res.TypedArray +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.Paint +import android.util.AttributeSet +import android.view.View +import com.krunal.demo.R + +class CustomView(context: Context, attrs: AttributeSet): View(context, attrs) { + + private val paint = Paint() + private var centerOfX = 340F + private var centerOfY = 340F + private val radiusOfCircleView = 140F + private var isCenter = false + + init { + val attributeArray: TypedArray = context.theme.obtainStyledAttributes( + attrs, R.styleable.CustomButton, 0, 0 + ) + + paint.apply { + color = attributeArray.getColor(R.styleable.CustomButton_circleColor, Color.RED) + strokeWidth = attributeArray.getFloat(R.styleable.CustomButton_strokeSize, 20f) + style = Paint.Style.STROKE + } + isCenter = attributeArray.getBoolean(R.styleable.CustomButton_onCenter, isCenter) + } + + override fun onDraw(canvas: Canvas?) { + if (isCenter) { + centerOfX = (width / 2).toFloat() + centerOfY = (height / 2).toFloat() + } + canvas?.drawCircle(centerOfX, centerOfY, radiusOfCircleView, paint) + super.onDraw(canvas) + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/views/HistoryLineView.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/views/HistoryLineView.kt new file mode 100644 index 0000000..3fd28d1 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/views/HistoryLineView.kt @@ -0,0 +1,70 @@ +package com.krunal.demo.uicomponents.views + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Color +import android.graphics.Paint +import android.util.AttributeSet +import android.view.View +import com.krunal.demo.R + +class HistoryLineView(context: Context, attrs: AttributeSet) : View(context, attrs) { + + private val thumbPaint = Paint(Paint.ANTI_ALIAS_FLAG) + private val linePaint = Paint(Paint.ANTI_ALIAS_FLAG) + private var thumbColor = Color.BLUE + private var lineColor = thumbColor + private var thumbRadius = 10F + private var lineWidth = 2F + + init { + + setupAttributes(attrs) + setupPaint() + } + + private fun setupAttributes(attrs: AttributeSet) { + val typedArray = + context.theme.obtainStyledAttributes(attrs, R.styleable.HistoryLineView, 0, 0) + thumbColor = typedArray.getColor(R.styleable.HistoryLineView_thumbColor, thumbColor) + lineColor = typedArray.getColor(R.styleable.HistoryLineView_lineColor, lineColor) + thumbRadius = typedArray.getDimension(R.styleable.HistoryLineView_thumbRadius, thumbRadius) + lineWidth = typedArray.getDimension(R.styleable.HistoryLineView_lineWidth, lineWidth) + +// typedArray.recycle() + } + + private fun setupPaint() { + thumbPaint.apply { + color = thumbColor + style = Paint.Style.FILL + } + + linePaint.apply { + color = lineColor + strokeWidth = lineWidth + style = Paint.Style.STROKE + strokeCap = Paint.Cap.ROUND + } + } + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + + drawThumb(canvas) + drawLine(canvas) + } + + private fun drawThumb(canvas: Canvas) { + canvas.drawCircle((width / 2).toFloat(), thumbRadius, thumbRadius, thumbPaint) + } + + private fun drawLine(canvas: Canvas) { + val xPosition = (width / 2).toFloat() + val yStartPosition = (thumbRadius * 2) + lineWidth + val yEndPosition = height - yStartPosition + canvas.drawLine( + xPosition, yStartPosition, xPosition, yEndPosition, linePaint + ) + } +} \ No newline at end of file diff --git a/Demo/app/src/main/java/com/krunal/demo/uicomponents/views/MyClickableSpan.kt b/Demo/app/src/main/java/com/krunal/demo/uicomponents/views/MyClickableSpan.kt new file mode 100644 index 0000000..81d9176 --- /dev/null +++ b/Demo/app/src/main/java/com/krunal/demo/uicomponents/views/MyClickableSpan.kt @@ -0,0 +1,18 @@ +package com.krunal.demo.uicomponents.views + +import android.text.TextPaint +import android.text.style.ClickableSpan +import android.view.View + +class MyClickableSpan( + private val onTextClick: () -> Unit +) : ClickableSpan() { + override fun onClick(view: View) { + onTextClick() + } + + override fun updateDrawState(ds: TextPaint) { + super.updateDrawState(ds) + ds.isUnderlineText = false + } +} \ No newline at end of file diff --git a/Demo/app/src/main/res/color/chip_card.xml b/Demo/app/src/main/res/color/chip_card.xml new file mode 100644 index 0000000..ed1c356 --- /dev/null +++ b/Demo/app/src/main/res/color/chip_card.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/Demo/app/src/main/res/color/chip_card_text.xml b/Demo/app/src/main/res/color/chip_card_text.xml new file mode 100644 index 0000000..447acf5 --- /dev/null +++ b/Demo/app/src/main/res/color/chip_card_text.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/Demo/app/src/main/res/color/chip_operations_time.xml b/Demo/app/src/main/res/color/chip_operations_time.xml new file mode 100644 index 0000000..cd4dbcd --- /dev/null +++ b/Demo/app/src/main/res/color/chip_operations_time.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/Demo/app/src/main/res/color/stack_exchange_chip_background.xml b/Demo/app/src/main/res/color/stack_exchange_chip_background.xml new file mode 100644 index 0000000..b4b0d3b --- /dev/null +++ b/Demo/app/src/main/res/color/stack_exchange_chip_background.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/Demo/app/src/main/res/drawable/calendar_banner.jpeg b/Demo/app/src/main/res/drawable/calendar_banner.jpeg new file mode 100644 index 0000000000000000000000000000000000000000..6b784d4eadca2ad7ac8e0a72e61c2bd720e7d6a3 GIT binary patch literal 46789 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