From 5eee6f324724e5083761da08e987e67c93a55e0b Mon Sep 17 00:00:00 2001 From: Vitalii Markus Date: Tue, 26 Apr 2022 23:52:33 +0200 Subject: [PATCH 1/8] Initial commit --- .gitignore | 85 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..56cc6425e --- /dev/null +++ b/.gitignore @@ -0,0 +1,85 @@ +# Built application files +*.apk +*.aar +*.ap_ +*.aab + +# Files for the ART/Dalvik VM +*.dex + +# Java class files +*.class + +# Generated files +bin/ +gen/ +out/ +# Uncomment the following line in case you need and you don't have the release build type files in your app +# release/ + +# Gradle files +.gradle/ +build/ + +# Local configuration file (sdk path, etc) +local.properties + +# Proguard folder generated by Eclipse +proguard/ + +# Log Files +*.log + +# Android Studio Navigation editor temp files +.navigation/ + +# Android Studio captures folder +captures/ + +# IntelliJ +*.iml +.idea/workspace.xml +.idea/tasks.xml +.idea/gradle.xml +.idea/assetWizardSettings.xml +.idea/dictionaries +.idea/libraries +# Android Studio 3 in .gitignore file. +.idea/caches +.idea/modules.xml +# Comment next line if keeping position of elements in Navigation Editor is relevant for you +.idea/navEditor.xml + +# Keystore files +# Uncomment the following lines if you do not want to check your keystore files in. +#*.jks +#*.keystore + +# External native build folder generated in Android Studio 2.2 and later +.externalNativeBuild +.cxx/ + +# Google Services (e.g. APIs or Firebase) +# google-services.json + +# Freeline +freeline.py +freeline/ +freeline_project_description.json + +# fastlane +fastlane/report.xml +fastlane/Preview.html +fastlane/screenshots +fastlane/test_output +fastlane/readme.md + +# Version control +vcs.xml + +# lint +lint/intermediates/ +lint/generated/ +lint/outputs/ +lint/tmp/ +# lint/reports/ From 2e1d1bb954118e80fbb03c705af9e571b3c00124 Mon Sep 17 00:00:00 2001 From: Vitalii Markus Date: Wed, 4 May 2022 20:47:13 +0200 Subject: [PATCH 2/8] Android contacts --- .gitignore | 1 + app/.gitignore | 1 + app/build.gradle | 46 +++ app/proguard-rules.pro | 21 ++ .../contacts/ExampleInstrumentedTest.java | 26 ++ app/src/main/AndroidManifest.xml | 32 ++ app/src/main/ic_launcher-playstore.png | Bin 0 -> 3968 bytes .../practicum/contacts/SplashActivity.java | 80 +++++ .../contacts/interactor/ContactMerger.java | 63 ++++ .../contacts/mapper/ContactUiMapper.java | 30 ++ .../practicum/contacts/model/Address.java | 48 +++ .../practicum/contacts/model/Contact.java | 141 +++++++++ .../contacts/model/ContactSource.java | 48 +++ .../practicum/contacts/model/ContactType.java | 11 + .../practicum/contacts/model/Email.java | 48 +++ .../contacts/model/MergedContact.java | 100 ++++++ .../practicum/contacts/model/PhoneNumber.java | 56 ++++ .../base/BaseBottomSheetDialogFragment.java | 65 ++++ .../base/BaseBottomSheetViewModel.java | 8 + .../filter/FilterContactTypeAdapter.java | 107 +++++++ .../FilterContactTypeDialogFragment.java | 84 +++++ .../filter/FilterContactTypeViewModel.java | 103 +++++++ .../filter/model/FilterContactType.java | 12 + .../filter/model/FilterContactTypeUi.java | 40 +++ .../presentation/main/ContactAdapter.java | 114 +++++++ .../contacts/presentation/main/ContactUi.java | 65 ++++ .../presentation/main/MainActivity.java | 197 ++++++++++++ .../contacts/presentation/main/MainState.java | 70 +++++ .../presentation/main/MainViewModel.java | 290 ++++++++++++++++++ .../presentation/main/model/MenuClick.java | 5 + .../presentation/sort/SortDialogFragment.java | 80 +++++ .../presentation/sort/SortTypeAdapter.java | 110 +++++++ .../presentation/sort/SortTypeUI.java | 42 +++ .../presentation/sort/SortViewModel.java | 74 +++++ .../presentation/sort/model/SortType.java | 8 + .../repository/ContactRepository.java | 233 ++++++++++++++ .../repository/ContactSourceRepository.java | 154 ++++++++++ .../ui/widget/ContactTypeImageView.java | 35 +++ .../ui/widget/DividerItemDecoration.java | 133 ++++++++ .../contacts/ui/widget/StackImageView.java | 113 +++++++ .../practicum/contacts/utils/Constants.java | 28 ++ .../contacts/utils/android/ContextUtils.java | 55 ++++ .../contacts/utils/android/CursorUtils.java | 25 ++ .../contacts/utils/android/Debouncer.java | 42 +++ .../contacts/utils/java/ThreadUtils.java | 25 ++ .../utils/model/ContactTypeUtils.java | 74 +++++ .../contacts/utils/model/ContactUtils.java | 42 +++ .../utils/model/FilterContactTypeUtils.java | 57 ++++ .../utils/model/MergedContactUtils.java | 52 ++++ .../contacts/utils/model/PhoneUtils.java | 14 + .../contacts/utils/widget/EditTextUtils.java | 49 +++ app/src/main/res/color/text_button_blue.xml | 5 + app/src/main/res/color/text_button_white.xml | 5 + app/src/main/res/drawable/bg_button_blue.xml | 29 ++ app/src/main/res/drawable/bg_button_white.xml | 35 +++ .../res/drawable/bg_image_view_borger.xml | 13 + app/src/main/res/drawable/bg_search_box.xml | 6 + app/src/main/res/drawable/bg_splash.xml | 5 + app/src/main/res/drawable/ic_avatar.xml | 9 + app/src/main/res/drawable/ic_close.xml | 9 + app/src/main/res/drawable/ic_done.xml | 9 + .../res/drawable/ic_launcher_background.xml | 12 + .../res/drawable/ic_launcher_foreground.xml | 15 + app/src/main/res/drawable/ic_menu_filter.xml | 9 + app/src/main/res/drawable/ic_menu_search.xml | 9 + app/src/main/res/drawable/ic_menu_sort.xml | 9 + app/src/main/res/drawable/ic_type_email.xml | 12 + app/src/main/res/drawable/ic_type_phone.xml | 12 + app/src/main/res/drawable/ic_type_signal.xml | 12 + .../main/res/drawable/ic_type_telegram.xml | 18 ++ app/src/main/res/drawable/ic_type_threema.xml | 21 ++ app/src/main/res/drawable/ic_type_viber.xml | 30 ++ .../main/res/drawable/ic_type_whatsapp.xml | 12 + .../res/drawable/item_decoration_16dp.xml | 9 + .../res/drawable/item_decoration_72dp.xml | 9 + app/src/main/res/font/ys_display_medium.ttf | Bin 0 -> 131604 bytes app/src/main/res/font/ys_display_regular.ttf | Bin 0 -> 129484 bytes app/src/main/res/layout/activity_main.xml | 58 ++++ .../main/res/layout/fragment_bottom_sheet.xml | 51 +++ app/src/main/res/layout/item_contact.xml | 58 ++++ app/src/main/res/layout/item_filter.xml | 50 +++ app/src/main/res/layout/item_sort.xml | 35 +++ app/src/main/res/layout/layout_search.xml | 64 ++++ app/src/main/res/layout/splash_activity.xml | 37 +++ app/src/main/res/menu/menu_main.xml | 26 ++ .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 + app/src/main/res/mipmap-hdpi/ic_launcher.png | Bin 0 -> 907 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 2729 bytes app/src/main/res/mipmap-mdpi/ic_launcher.png | Bin 0 -> 701 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 1742 bytes app/src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 0 -> 1220 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 3917 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 0 -> 1884 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 6259 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 0 -> 2816 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 9284 bytes app/src/main/res/values/attrs.xml | 9 + app/src/main/res/values/colors.xml | 17 + app/src/main/res/values/dimens.xml | 7 + app/src/main/res/values/strings.xml | 28 ++ app/src/main/res/values/themes.xml | 55 ++++ .../practicum/contacts/ExampleUnitTest.java | 17 + build.gradle | 10 + gradle.properties | 21 ++ gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 59203 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 185 +++++++++++ gradlew.bat | 89 ++++++ settings.gradle | 16 + 110 files changed, 4520 insertions(+) create mode 100644 app/.gitignore create mode 100644 app/build.gradle create mode 100644 app/proguard-rules.pro create mode 100644 app/src/androidTest/java/ru/yandex/practicum/contacts/ExampleInstrumentedTest.java create mode 100644 app/src/main/AndroidManifest.xml create mode 100644 app/src/main/ic_launcher-playstore.png create mode 100644 app/src/main/java/ru/yandex/practicum/contacts/SplashActivity.java create mode 100644 app/src/main/java/ru/yandex/practicum/contacts/interactor/ContactMerger.java create mode 100644 app/src/main/java/ru/yandex/practicum/contacts/mapper/ContactUiMapper.java create mode 100644 app/src/main/java/ru/yandex/practicum/contacts/model/Address.java create mode 100644 app/src/main/java/ru/yandex/practicum/contacts/model/Contact.java create mode 100644 app/src/main/java/ru/yandex/practicum/contacts/model/ContactSource.java create mode 100644 app/src/main/java/ru/yandex/practicum/contacts/model/ContactType.java create mode 100644 app/src/main/java/ru/yandex/practicum/contacts/model/Email.java create mode 100644 app/src/main/java/ru/yandex/practicum/contacts/model/MergedContact.java create mode 100644 app/src/main/java/ru/yandex/practicum/contacts/model/PhoneNumber.java create mode 100644 app/src/main/java/ru/yandex/practicum/contacts/presentation/base/BaseBottomSheetDialogFragment.java create mode 100644 app/src/main/java/ru/yandex/practicum/contacts/presentation/base/BaseBottomSheetViewModel.java create mode 100644 app/src/main/java/ru/yandex/practicum/contacts/presentation/filter/FilterContactTypeAdapter.java create mode 100644 app/src/main/java/ru/yandex/practicum/contacts/presentation/filter/FilterContactTypeDialogFragment.java create mode 100644 app/src/main/java/ru/yandex/practicum/contacts/presentation/filter/FilterContactTypeViewModel.java create mode 100644 app/src/main/java/ru/yandex/practicum/contacts/presentation/filter/model/FilterContactType.java create mode 100644 app/src/main/java/ru/yandex/practicum/contacts/presentation/filter/model/FilterContactTypeUi.java create mode 100644 app/src/main/java/ru/yandex/practicum/contacts/presentation/main/ContactAdapter.java create mode 100644 app/src/main/java/ru/yandex/practicum/contacts/presentation/main/ContactUi.java create mode 100644 app/src/main/java/ru/yandex/practicum/contacts/presentation/main/MainActivity.java create mode 100644 app/src/main/java/ru/yandex/practicum/contacts/presentation/main/MainState.java create mode 100644 app/src/main/java/ru/yandex/practicum/contacts/presentation/main/MainViewModel.java create mode 100644 app/src/main/java/ru/yandex/practicum/contacts/presentation/main/model/MenuClick.java create mode 100644 app/src/main/java/ru/yandex/practicum/contacts/presentation/sort/SortDialogFragment.java create mode 100644 app/src/main/java/ru/yandex/practicum/contacts/presentation/sort/SortTypeAdapter.java create mode 100644 app/src/main/java/ru/yandex/practicum/contacts/presentation/sort/SortTypeUI.java create mode 100644 app/src/main/java/ru/yandex/practicum/contacts/presentation/sort/SortViewModel.java create mode 100644 app/src/main/java/ru/yandex/practicum/contacts/presentation/sort/model/SortType.java create mode 100644 app/src/main/java/ru/yandex/practicum/contacts/repository/ContactRepository.java create mode 100644 app/src/main/java/ru/yandex/practicum/contacts/repository/ContactSourceRepository.java create mode 100644 app/src/main/java/ru/yandex/practicum/contacts/ui/widget/ContactTypeImageView.java create mode 100644 app/src/main/java/ru/yandex/practicum/contacts/ui/widget/DividerItemDecoration.java create mode 100644 app/src/main/java/ru/yandex/practicum/contacts/ui/widget/StackImageView.java create mode 100644 app/src/main/java/ru/yandex/practicum/contacts/utils/Constants.java create mode 100644 app/src/main/java/ru/yandex/practicum/contacts/utils/android/ContextUtils.java create mode 100644 app/src/main/java/ru/yandex/practicum/contacts/utils/android/CursorUtils.java create mode 100644 app/src/main/java/ru/yandex/practicum/contacts/utils/android/Debouncer.java create mode 100644 app/src/main/java/ru/yandex/practicum/contacts/utils/java/ThreadUtils.java create mode 100644 app/src/main/java/ru/yandex/practicum/contacts/utils/model/ContactTypeUtils.java create mode 100644 app/src/main/java/ru/yandex/practicum/contacts/utils/model/ContactUtils.java create mode 100644 app/src/main/java/ru/yandex/practicum/contacts/utils/model/FilterContactTypeUtils.java create mode 100644 app/src/main/java/ru/yandex/practicum/contacts/utils/model/MergedContactUtils.java create mode 100644 app/src/main/java/ru/yandex/practicum/contacts/utils/model/PhoneUtils.java create mode 100644 app/src/main/java/ru/yandex/practicum/contacts/utils/widget/EditTextUtils.java create mode 100644 app/src/main/res/color/text_button_blue.xml create mode 100644 app/src/main/res/color/text_button_white.xml create mode 100644 app/src/main/res/drawable/bg_button_blue.xml create mode 100644 app/src/main/res/drawable/bg_button_white.xml create mode 100644 app/src/main/res/drawable/bg_image_view_borger.xml create mode 100644 app/src/main/res/drawable/bg_search_box.xml create mode 100644 app/src/main/res/drawable/bg_splash.xml create mode 100644 app/src/main/res/drawable/ic_avatar.xml create mode 100644 app/src/main/res/drawable/ic_close.xml create mode 100644 app/src/main/res/drawable/ic_done.xml create mode 100644 app/src/main/res/drawable/ic_launcher_background.xml create mode 100644 app/src/main/res/drawable/ic_launcher_foreground.xml create mode 100644 app/src/main/res/drawable/ic_menu_filter.xml create mode 100644 app/src/main/res/drawable/ic_menu_search.xml create mode 100644 app/src/main/res/drawable/ic_menu_sort.xml create mode 100644 app/src/main/res/drawable/ic_type_email.xml create mode 100644 app/src/main/res/drawable/ic_type_phone.xml create mode 100644 app/src/main/res/drawable/ic_type_signal.xml create mode 100644 app/src/main/res/drawable/ic_type_telegram.xml create mode 100644 app/src/main/res/drawable/ic_type_threema.xml create mode 100644 app/src/main/res/drawable/ic_type_viber.xml create mode 100644 app/src/main/res/drawable/ic_type_whatsapp.xml create mode 100644 app/src/main/res/drawable/item_decoration_16dp.xml create mode 100644 app/src/main/res/drawable/item_decoration_72dp.xml create mode 100644 app/src/main/res/font/ys_display_medium.ttf create mode 100644 app/src/main/res/font/ys_display_regular.ttf create mode 100644 app/src/main/res/layout/activity_main.xml create mode 100644 app/src/main/res/layout/fragment_bottom_sheet.xml create mode 100644 app/src/main/res/layout/item_contact.xml create mode 100644 app/src/main/res/layout/item_filter.xml create mode 100644 app/src/main/res/layout/item_sort.xml create mode 100644 app/src/main/res/layout/layout_search.xml create mode 100644 app/src/main/res/layout/splash_activity.xml create mode 100644 app/src/main/res/menu/menu_main.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/values/attrs.xml create mode 100644 app/src/main/res/values/colors.xml create mode 100644 app/src/main/res/values/dimens.xml create mode 100644 app/src/main/res/values/strings.xml create mode 100644 app/src/main/res/values/themes.xml create mode 100644 app/src/test/java/ru/yandex/practicum/contacts/ExampleUnitTest.java create mode 100644 build.gradle create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle diff --git a/.gitignore b/.gitignore index 56cc6425e..587723fa2 100644 --- a/.gitignore +++ b/.gitignore @@ -38,6 +38,7 @@ captures/ # IntelliJ *.iml +.idea/ .idea/workspace.xml .idea/tasks.xml .idea/gradle.xml diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 000000000..42afabfd2 --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 000000000..5100b95a6 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,46 @@ +plugins { + id 'com.android.application' + id 'org.jetbrains.kotlin.android' +} + +android { + compileSdk 31 + + defaultConfig { + applicationId "ru.yandex.practicum.contacts" + minSdk 24 + targetSdk 31 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + buildFeatures { + viewBinding true + } +} + +dependencies { + + implementation 'androidx.appcompat:appcompat:1.3.0' + implementation 'com.google.android.material:material:1.4.0' + implementation 'androidx.constraintlayout:constraintlayout:2.0.4' + implementation 'androidx.navigation:navigation-fragment:2.3.5' + implementation 'androidx.navigation:navigation-ui:2.3.5' + implementation 'com.github.bumptech.glide:glide:4.13.0' + annotationProcessor 'com.github.bumptech.glide:compiler:4.13.0' + testImplementation 'junit:junit:4.13.2' + androidTestImplementation 'androidx.test.ext:junit:1.1.3' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 000000000..481bb4348 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/androidTest/java/ru/yandex/practicum/contacts/ExampleInstrumentedTest.java b/app/src/androidTest/java/ru/yandex/practicum/contacts/ExampleInstrumentedTest.java new file mode 100644 index 000000000..8988aa556 --- /dev/null +++ b/app/src/androidTest/java/ru/yandex/practicum/contacts/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package ru.yandex.practicum.contacts; + +import android.content.Context; + +import androidx.test.platform.app.InstrumentationRegistry; +import androidx.test.ext.junit.runners.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumented test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); + assertEquals("ru.yandex.practicum.contacts", appContext.getPackageName()); + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 000000000..5f2186e36 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/ic_launcher-playstore.png b/app/src/main/ic_launcher-playstore.png new file mode 100644 index 0000000000000000000000000000000000000000..e7826997028c51242936a1b2ebc09dec4bf6b8c0 GIT binary patch literal 3968 zcmeAS@N?(olHy`uVBq!ia0y~yU;;9k7&zE~)R&4YzZe+!-923#Ln`9l-td3@2l0oU_?9?h};^K_2=KAz9E{~uZASA^HGfABcM zAa476cJaTz^B>>o`u#fl`JR_2|L{GS*ukhL#E^HHlVM%gD2x7K@ax_BbCo~eKi~6- zf5!XP>E|mS@BVDv;I72B;{|iP^>br;tLK~T+kV=8-1~>^!Ng8Rh8g;73<>)r7#e;i zF)%#qriMVH*}Xpxi|>7(T5R`gZToBG`!>%z^`G2sy#D;{e7on)_fO=%QT+Mf`uWP& z$3MTd2{62H?6zI$x%2hq=e*b3#pv%VI>$fn{C4K`=hgSupUeM0<^B!lPp_w+tNb4h zRSrt1zwPI<*Z%+Y-SYh23Hj`C^UvE?pS%CBQtr#%U$_4M`}Oz#?aBp73wRkBsyP@= z9d(dqV)!P&Fr~ZEjD_KiB7=rFb1nzN4QGY`J+@l{3>(@R7R2yuQDjKqXLQ&pk>Sj6 zK#r-Q$nZiNLxU{~!_!fNM#E?{9gSv@(PCt@gdD9!Mk`1Hjfnp5Hnrz2+gm@M8E+F4 z|LdjY`wFf5=a@yAbN4(=eqQrV{dvvv>HOEX&$oVl+5XIS=7Px4e$&wDHdg#+wX*e^ VJFDyPQ((`U!PC{xWt~$(6970w)X4w< literal 0 HcmV?d00001 diff --git a/app/src/main/java/ru/yandex/practicum/contacts/SplashActivity.java b/app/src/main/java/ru/yandex/practicum/contacts/SplashActivity.java new file mode 100644 index 000000000..dcab69b5e --- /dev/null +++ b/app/src/main/java/ru/yandex/practicum/contacts/SplashActivity.java @@ -0,0 +1,80 @@ +package ru.yandex.practicum.contacts; + +import android.Manifest; +import android.annotation.SuppressLint; +import android.content.Intent; +import android.graphics.drawable.TransitionDrawable; +import android.net.Uri; +import android.os.Bundle; +import android.provider.Settings; +import android.view.View; + +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AppCompatActivity; + +import ru.yandex.practicum.contacts.databinding.SplashActivityBinding; +import ru.yandex.practicum.contacts.presentation.main.MainActivity; +import ru.yandex.practicum.contacts.utils.android.ContextUtils; + +@SuppressLint("CustomSplashScreen") +public class SplashActivity extends AppCompatActivity { + + private static final int ANIMATION_TIME = 250; + + private final ActivityResultLauncher requestPermissionLauncher = + registerForActivityResult(new ActivityResultContracts.RequestPermission(), this::onPermissionResult); + + private SplashActivityBinding binding; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + binding = SplashActivityBinding.inflate(getLayoutInflater()); + setContentView(binding.getRoot()); + binding.settingsButton.setOnClickListener(view -> navigateToSettings()); + + if (ContextUtils.hasContactPermissions(this)) { + navigateToMain(); + } else { + requestPermissionLauncher.launch(Manifest.permission.READ_CONTACTS); + } + } + + @Override + protected void onStart() { + super.onStart(); + if (ContextUtils.hasContactPermissions(this)) { + navigateToMain(); + } + } + + private void navigateToMain() { + final Intent intent = new Intent(this, MainActivity.class); + startActivity(intent); + finish(); + } + + private void showSettings() { + final TransitionDrawable drawable = (TransitionDrawable) binding.getRoot().getBackground(); + drawable.startTransition(ANIMATION_TIME); + binding.settingsButton.setVisibility(View.VISIBLE); + binding.logo.setVisibility(View.GONE); + } + + private void onPermissionResult(Boolean isGranted) { + if (isGranted) { + navigateToMain(); + } else { + showSettings(); + } + } + + private void navigateToSettings() { + final Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS); + final Uri uri = Uri.fromParts("package", getPackageName(), null); + intent.setData(uri); + startActivity(intent); + } +} diff --git a/app/src/main/java/ru/yandex/practicum/contacts/interactor/ContactMerger.java b/app/src/main/java/ru/yandex/practicum/contacts/interactor/ContactMerger.java new file mode 100644 index 000000000..484943d88 --- /dev/null +++ b/app/src/main/java/ru/yandex/practicum/contacts/interactor/ContactMerger.java @@ -0,0 +1,63 @@ +package ru.yandex.practicum.contacts.interactor; + +import android.text.TextUtils; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.function.Function; +import java.util.stream.Collectors; + +import ru.yandex.practicum.contacts.model.Contact; +import ru.yandex.practicum.contacts.model.ContactSource; +import ru.yandex.practicum.contacts.model.MergedContact; +import ru.yandex.practicum.contacts.utils.Constants; +import ru.yandex.practicum.contacts.utils.model.ContactUtils; + +public class ContactMerger { + + public List getMergedContacts(Collection contacts, Collection sources) { + final Map sourcesMap = sources.stream() + .collect(Collectors.toMap(ContactSource::getName, Function.identity(), this::resolveConflicts)); + final Map> contactsMap = contacts.stream() + .filter(contact -> !TextUtils.isEmpty(ContactUtils.getDisplayName(contact))) + .collect(Collectors.groupingBy(ContactUtils::getDisplayName)); + final List mergedContacts = contactsMap.entrySet().stream() + .map(entry -> mergeContact(entry, sourcesMap)) + .collect(Collectors.toList()); + return Collections.unmodifiableList(mergedContacts); + } + + private MergedContact mergeContact(Map.Entry> entry, Map sources) { + final List contacts = entry.getValue(); + final Contact contact = contacts.get(0); + final List contactSources = contacts.stream() + .map(each -> Objects.requireNonNull(sources.get(each.getSource())).getPublicName()) + .distinct() + .collect(Collectors.toList()); + return new MergedContact( + contact.getId(), + contact.getFirstName(), + contact.getMiddleName(), + contact.getSurname(), + ContactUtils.getFirstPhone(contact), + ContactUtils.getFirstNormalizedPhone(contact), + ContactUtils.getFirstEmail(contact), + contactSources, + contact.getPhotoUri() + ); + } + + private ContactSource resolveConflicts(ContactSource source1, ContactSource source2) { + if (Objects.equals(source1.getType(), Constants.StorageType.GOOGLE)) { + return source1; + } + if (Objects.equals(source2.getType(), Constants.StorageType.GOOGLE)) { + return source2; + } + return source1; + } + +} diff --git a/app/src/main/java/ru/yandex/practicum/contacts/mapper/ContactUiMapper.java b/app/src/main/java/ru/yandex/practicum/contacts/mapper/ContactUiMapper.java new file mode 100644 index 000000000..d59a47a54 --- /dev/null +++ b/app/src/main/java/ru/yandex/practicum/contacts/mapper/ContactUiMapper.java @@ -0,0 +1,30 @@ +package ru.yandex.practicum.contacts.mapper; + +import android.text.TextUtils; + +import ru.yandex.practicum.contacts.model.MergedContact; +import ru.yandex.practicum.contacts.presentation.main.ContactUi; +import ru.yandex.practicum.contacts.utils.model.MergedContactUtils; +import ru.yandex.practicum.contacts.utils.model.PhoneUtils; + +public class ContactUiMapper { + + public ContactUi map(MergedContact contact) { + String displayName = (contact.getFirstName() + " " + contact.getSurname()).trim(); + String phone = PhoneUtils.format(contact.getPhone()); + if (TextUtils.isEmpty(displayName)) { + if (!TextUtils.isEmpty(phone)) { + displayName = phone; + phone = ""; + } else { + displayName = contact.getEmail(); + } + } + return new ContactUi( + displayName, + phone, + contact.getPhotoUri(), + MergedContactUtils.getContactTypes(contact) + ); + } +} diff --git a/app/src/main/java/ru/yandex/practicum/contacts/model/Address.java b/app/src/main/java/ru/yandex/practicum/contacts/model/Address.java new file mode 100644 index 000000000..c7c13e58a --- /dev/null +++ b/app/src/main/java/ru/yandex/practicum/contacts/model/Address.java @@ -0,0 +1,48 @@ +package ru.yandex.practicum.contacts.model; + +import androidx.annotation.NonNull; + +public class Address { + + private final String value; + private final int type; + private final String label; + + public Address(@NonNull String value, int type, @NonNull String label) { + this.value = value; + this.type = type; + this.label = label; + } + + public String getValue() { + return value; + } + + public int getType() { + return type; + } + + public String getLabel() { + return label; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Address address = (Address) o; + + if (type != address.type) return false; + if (!value.equals(address.value)) return false; + return label.equals(address.label); + } + + @Override + public int hashCode() { + int result = value.hashCode(); + result = 31 * result + type; + result = 31 * result + label.hashCode(); + return result; + } +} diff --git a/app/src/main/java/ru/yandex/practicum/contacts/model/Contact.java b/app/src/main/java/ru/yandex/practicum/contacts/model/Contact.java new file mode 100644 index 000000000..8696a9a15 --- /dev/null +++ b/app/src/main/java/ru/yandex/practicum/contacts/model/Contact.java @@ -0,0 +1,141 @@ +package ru.yandex.practicum.contacts.model; + +import androidx.annotation.NonNull; + +import java.util.List; + +public class Contact { + + private final int id; + private final String prefix; + private final String firstName; + private final String middleName; + private final String surname; + private final String suffix; + private final String photoUri; + private final List phoneNumbers; + private final List emails; + private final List
addresses; + private final String source; + private final int starred; + private final int contactId; + private final String thumbnailUri; + + public Contact(int id, @NonNull String prefix, @NonNull String firstName, @NonNull String middleName, @NonNull String surname, + @NonNull String suffix, @NonNull String photoUri, @NonNull List phoneNumbers, @NonNull List emails, + @NonNull List
addresses, @NonNull String source, int starred, int contactId, @NonNull String thumbnailUri + ) { + this.id = id; + this.prefix = prefix; + this.firstName = firstName; + this.middleName = middleName; + this.surname = surname; + this.suffix = suffix; + this.photoUri = photoUri; + this.phoneNumbers = phoneNumbers; + this.emails = emails; + this.addresses = addresses; + this.source = source; + this.starred = starred; + this.contactId = contactId; + this.thumbnailUri = thumbnailUri; + } + + public int getId() { + return id; + } + + public String getPrefix() { + return prefix; + } + + public String getFirstName() { + return firstName; + } + + public String getMiddleName() { + return middleName; + } + + public String getSurname() { + return surname; + } + + public String getSuffix() { + return suffix; + } + + public String getPhotoUri() { + return photoUri; + } + + public List getPhoneNumbers() { + return phoneNumbers; + } + + public List getEmails() { + return emails; + } + + public List
getAddresses() { + return addresses; + } + + public String getSource() { + return source; + } + + public int getStarred() { + return starred; + } + + public int getContactId() { + return contactId; + } + + public String getThumbnailUri() { + return thumbnailUri; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Contact contact = (Contact) o; + + if (id != contact.id) return false; + if (starred != contact.starred) return false; + if (contactId != contact.contactId) return false; + if (!prefix.equals(contact.prefix)) return false; + if (!firstName.equals(contact.firstName)) return false; + if (!middleName.equals(contact.middleName)) return false; + if (!surname.equals(contact.surname)) return false; + if (!suffix.equals(contact.suffix)) return false; + if (!photoUri.equals(contact.photoUri)) return false; + if (!phoneNumbers.equals(contact.phoneNumbers)) return false; + if (!emails.equals(contact.emails)) return false; + if (!addresses.equals(contact.addresses)) return false; + if (!source.equals(contact.source)) return false; + return thumbnailUri.equals(contact.thumbnailUri); + } + + @Override + public int hashCode() { + int result = id; + result = 31 * result + prefix.hashCode(); + result = 31 * result + firstName.hashCode(); + result = 31 * result + middleName.hashCode(); + result = 31 * result + surname.hashCode(); + result = 31 * result + suffix.hashCode(); + result = 31 * result + photoUri.hashCode(); + result = 31 * result + phoneNumbers.hashCode(); + result = 31 * result + emails.hashCode(); + result = 31 * result + addresses.hashCode(); + result = 31 * result + source.hashCode(); + result = 31 * result + starred; + result = 31 * result + contactId; + result = 31 * result + thumbnailUri.hashCode(); + return result; + } +} diff --git a/app/src/main/java/ru/yandex/practicum/contacts/model/ContactSource.java b/app/src/main/java/ru/yandex/practicum/contacts/model/ContactSource.java new file mode 100644 index 000000000..46c0069af --- /dev/null +++ b/app/src/main/java/ru/yandex/practicum/contacts/model/ContactSource.java @@ -0,0 +1,48 @@ +package ru.yandex.practicum.contacts.model; + +import androidx.annotation.NonNull; + +public class ContactSource { + + private final String name; + private final String type; + private final String publicName; + + public ContactSource(@NonNull String name, @NonNull String type, @NonNull String publicName) { + this.name = name; + this.type = type; + this.publicName = publicName; + } + + public String getName() { + return name; + } + + public String getType() { + return type; + } + + public String getPublicName() { + return publicName; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ContactSource that = (ContactSource) o; + + if (!name.equals(that.name)) return false; + if (!type.equals(that.type)) return false; + return publicName.equals(that.publicName); + } + + @Override + public int hashCode() { + int result = name.hashCode(); + result = 31 * result + type.hashCode(); + result = 31 * result + publicName.hashCode(); + return result; + } +} diff --git a/app/src/main/java/ru/yandex/practicum/contacts/model/ContactType.java b/app/src/main/java/ru/yandex/practicum/contacts/model/ContactType.java new file mode 100644 index 000000000..fee0b6378 --- /dev/null +++ b/app/src/main/java/ru/yandex/practicum/contacts/model/ContactType.java @@ -0,0 +1,11 @@ +package ru.yandex.practicum.contacts.model; + +public enum ContactType { + TELEGRAM, + WHATS_APP, + VIBER, + SIGNAL, + THREEMA, + PHONE, + EMAIL +} diff --git a/app/src/main/java/ru/yandex/practicum/contacts/model/Email.java b/app/src/main/java/ru/yandex/practicum/contacts/model/Email.java new file mode 100644 index 000000000..7c77c7337 --- /dev/null +++ b/app/src/main/java/ru/yandex/practicum/contacts/model/Email.java @@ -0,0 +1,48 @@ +package ru.yandex.practicum.contacts.model; + +import androidx.annotation.NonNull; + +public class Email { + + private final String value; + private final int type; + private final String label; + + public Email(@NonNull String value, int type, @NonNull String label) { + this.value = value; + this.type = type; + this.label = label; + } + + public String getValue() { + return value; + } + + public int getType() { + return type; + } + + public String getLabel() { + return label; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + Email email = (Email) o; + + if (type != email.type) return false; + if (!value.equals(email.value)) return false; + return label.equals(email.label); + } + + @Override + public int hashCode() { + int result = value.hashCode(); + result = 31 * result + type; + result = 31 * result + label.hashCode(); + return result; + } +} diff --git a/app/src/main/java/ru/yandex/practicum/contacts/model/MergedContact.java b/app/src/main/java/ru/yandex/practicum/contacts/model/MergedContact.java new file mode 100644 index 000000000..f06fdcaae --- /dev/null +++ b/app/src/main/java/ru/yandex/practicum/contacts/model/MergedContact.java @@ -0,0 +1,100 @@ +package ru.yandex.practicum.contacts.model; + +import androidx.annotation.NonNull; + +import java.util.List; + +public class MergedContact { + + private final int id; + private final String firstName; + private final String middleName; + private final String surname; + private final String phone; + private final String normalizedNumber; + private final String email; + private final List contactTypes; + private final String photoUri; + + public MergedContact(int id, @NonNull String firstName, @NonNull String middleName, @NonNull String surname, @NonNull String phone, + @NonNull String normalizedNumber, @NonNull String email, @NonNull List contactTypes, @NonNull String photoUri + ) { + this.id = id; + this.firstName = firstName; + this.middleName = middleName; + this.surname = surname; + this.phone = phone; + this.normalizedNumber = normalizedNumber; + this.email = email; + this.contactTypes = contactTypes; + this.photoUri = photoUri; + } + + public int getId() { + return id; + } + + public String getFirstName() { + return firstName; + } + + public String getMiddleName() { + return middleName; + } + + public String getSurname() { + return surname; + } + + public String getPhone() { + return phone; + } + + public String getNormalizedNumber() { + return normalizedNumber; + } + + public String getEmail() { + return email; + } + + public List getContactTypes() { + return contactTypes; + } + + public String getPhotoUri() { + return photoUri; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + MergedContact that = (MergedContact) o; + + if (id != that.id) return false; + if (!firstName.equals(that.firstName)) return false; + if (!middleName.equals(that.middleName)) return false; + if (!surname.equals(that.surname)) return false; + if (!phone.equals(that.phone)) return false; + if (!normalizedNumber.equals(that.normalizedNumber)) return false; + if (!email.equals(that.email)) return false; + if (!contactTypes.equals(that.contactTypes)) return false; + return photoUri.equals(that.photoUri); + } + + @Override + public int hashCode() { + int result = id; + result = 31 * result + firstName.hashCode(); + result = 31 * result + middleName.hashCode(); + result = 31 * result + surname.hashCode(); + result = 31 * result + phone.hashCode(); + result = 31 * result + normalizedNumber.hashCode(); + result = 31 * result + email.hashCode(); + result = 31 * result + contactTypes.hashCode(); + result = 31 * result + photoUri.hashCode(); + return result; + } +} diff --git a/app/src/main/java/ru/yandex/practicum/contacts/model/PhoneNumber.java b/app/src/main/java/ru/yandex/practicum/contacts/model/PhoneNumber.java new file mode 100644 index 000000000..d515a756c --- /dev/null +++ b/app/src/main/java/ru/yandex/practicum/contacts/model/PhoneNumber.java @@ -0,0 +1,56 @@ +package ru.yandex.practicum.contacts.model; + +import androidx.annotation.NonNull; + +public class PhoneNumber { + + private final String value; + private final int type; + private final String label; + private final String normalizedNumber; + + public PhoneNumber(@NonNull String value, int type, @NonNull String label, @NonNull String normalizedNumber) { + this.value = value; + this.type = type; + this.label = label; + this.normalizedNumber = normalizedNumber; + } + + public String getValue() { + return value; + } + + public int getType() { + return type; + } + + public String getLabel() { + return label; + } + + public String getNormalizedNumber() { + return normalizedNumber; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + PhoneNumber that = (PhoneNumber) o; + + if (type != that.type) return false; + if (!value.equals(that.value)) return false; + if (!label.equals(that.label)) return false; + return normalizedNumber.equals(that.normalizedNumber); + } + + @Override + public int hashCode() { + int result = value.hashCode(); + result = 31 * result + type; + result = 31 * result + label.hashCode(); + result = 31 * result + normalizedNumber.hashCode(); + return result; + } +} diff --git a/app/src/main/java/ru/yandex/practicum/contacts/presentation/base/BaseBottomSheetDialogFragment.java b/app/src/main/java/ru/yandex/practicum/contacts/presentation/base/BaseBottomSheetDialogFragment.java new file mode 100644 index 000000000..465b111ca --- /dev/null +++ b/app/src/main/java/ru/yandex/practicum/contacts/presentation/base/BaseBottomSheetDialogFragment.java @@ -0,0 +1,65 @@ +package ru.yandex.practicum.contacts.presentation.base; + +import android.app.Dialog; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.lifecycle.ViewModelProvider; + +import com.google.android.material.bottomsheet.BottomSheetBehavior; +import com.google.android.material.bottomsheet.BottomSheetDialog; +import com.google.android.material.bottomsheet.BottomSheetDialogFragment; + +import ru.yandex.practicum.contacts.databinding.FragmentBottomSheetBinding; + +public abstract class BaseBottomSheetDialogFragment extends BottomSheetDialogFragment { + + private final Class viewModelClass; + + protected FragmentBottomSheetBinding binding; + protected T viewModel; + + public BaseBottomSheetDialogFragment(Class viewModelClass) { + this.viewModelClass = viewModelClass; + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + binding = FragmentBottomSheetBinding.inflate(inflater, container, false); + return binding.getRoot(); + } + + @NonNull + @Override + public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { + final Dialog dialog = super.onCreateDialog(savedInstanceState); + dialog.setOnShowListener(dialogInterface -> { + final BottomSheetDialog bottomSheetDialog = (BottomSheetDialog) dialogInterface; + final View bottomSheet = bottomSheetDialog.findViewById(com.google.android.material.R.id.design_bottom_sheet); + if (bottomSheet != null) { + final BottomSheetBehavior bottomSheetBehavior = BottomSheetBehavior.from(bottomSheet); + bottomSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED); + } + }); + return dialog; + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + viewModel = new ViewModelProvider(this).get(viewModelClass); + binding.applyButton.setOnClickListener(v -> viewModel.onApplyClick()); + binding.resetButton.setOnClickListener(v -> viewModel.onResetClick()); + } + + @Override + public void onDestroy() { + binding = null; + super.onDestroy(); + } +} diff --git a/app/src/main/java/ru/yandex/practicum/contacts/presentation/base/BaseBottomSheetViewModel.java b/app/src/main/java/ru/yandex/practicum/contacts/presentation/base/BaseBottomSheetViewModel.java new file mode 100644 index 000000000..bd0e5ce63 --- /dev/null +++ b/app/src/main/java/ru/yandex/practicum/contacts/presentation/base/BaseBottomSheetViewModel.java @@ -0,0 +1,8 @@ +package ru.yandex.practicum.contacts.presentation.base; + +import androidx.lifecycle.ViewModel; + +public abstract class BaseBottomSheetViewModel extends ViewModel { + abstract public void onApplyClick(); + abstract public void onResetClick(); +} diff --git a/app/src/main/java/ru/yandex/practicum/contacts/presentation/filter/FilterContactTypeAdapter.java b/app/src/main/java/ru/yandex/practicum/contacts/presentation/filter/FilterContactTypeAdapter.java new file mode 100644 index 000000000..4203bc297 --- /dev/null +++ b/app/src/main/java/ru/yandex/practicum/contacts/presentation/filter/FilterContactTypeAdapter.java @@ -0,0 +1,107 @@ +package ru.yandex.practicum.contacts.presentation.filter; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.recyclerview.widget.AdapterListUpdateCallback; +import androidx.recyclerview.widget.AsyncDifferConfig; +import androidx.recyclerview.widget.AsyncListDiffer; +import androidx.recyclerview.widget.DiffUtil; +import androidx.recyclerview.widget.RecyclerView; + +import java.util.List; +import java.util.function.Consumer; + +import ru.yandex.practicum.contacts.databinding.ItemFilterBinding; +import ru.yandex.practicum.contacts.model.ContactType; +import ru.yandex.practicum.contacts.presentation.filter.model.FilterContactType; +import ru.yandex.practicum.contacts.presentation.filter.model.FilterContactTypeUi; +import ru.yandex.practicum.contacts.utils.model.ContactTypeUtils; +import ru.yandex.practicum.contacts.utils.model.FilterContactTypeUtils; + +public class FilterContactTypeAdapter extends RecyclerView.Adapter { + + private final AsyncListDiffer differ = new AsyncListDiffer<>( + new AdapterListUpdateCallback(this), + new AsyncDifferConfig.Builder<>(new ListDiffCallback()).build() + ); + + private final Consumer clickListener; + + public FilterContactTypeAdapter(Consumer clickListener) { + this.clickListener = clickListener; + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + final LayoutInflater inflater = LayoutInflater.from(parent.getContext()); + final ItemFilterBinding binding = ItemFilterBinding.inflate(inflater, parent, false); + return new ViewHolder(binding, clickListener); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + holder.bind(differ.getCurrentList().get(position)); + } + + @Override + public int getItemCount() { + return differ.getCurrentList().size(); + } + + public void setItems(List items) { + differ.submitList(items); + } + + static class ViewHolder extends RecyclerView.ViewHolder { + + private final ItemFilterBinding binding; + + private FilterContactTypeUi data; + + public ViewHolder(@NonNull ItemFilterBinding binding, Consumer clickListener) { + super(binding.getRoot()); + this.binding = binding; + this.binding.getRoot().setOnClickListener(v -> clickListener.accept(data)); + this.binding.selected.setOnClickListener(v -> clickListener.accept(data)); + } + + public void bind(FilterContactTypeUi data) { + this.data = data; + final int sortResId = FilterContactTypeUtils.getStringRes(data.getContactType()); + binding.text.setText(sortResId); + binding.selected.setChecked(data.isSelected()); + if (data.getContactType() == FilterContactType.ALL){ + binding.logo.setVisibility(View.GONE); + } else { + final ContactType contactType = FilterContactTypeUtils.toContactType(data.getContactType()); + final int iconRes = ContactTypeUtils.getIconRes(contactType); + binding.logo.setVisibility(View.VISIBLE); + binding.logo.setImageResource(iconRes); + } + } + } + + static class ListDiffCallback extends DiffUtil.ItemCallback { + + @Override + public boolean areItemsTheSame(@NonNull FilterContactTypeUi oldItem, @NonNull FilterContactTypeUi newItem) { + return oldItem.getContactType() == newItem.getContactType(); + } + + @Override + public boolean areContentsTheSame(@NonNull FilterContactTypeUi oldItem, @NonNull FilterContactTypeUi newItem) { + return oldItem.equals(newItem); + } + + @Nullable + @Override + public Object getChangePayload(@NonNull FilterContactTypeUi oldItem, @NonNull FilterContactTypeUi newItem) { + return newItem; + } + } +} diff --git a/app/src/main/java/ru/yandex/practicum/contacts/presentation/filter/FilterContactTypeDialogFragment.java b/app/src/main/java/ru/yandex/practicum/contacts/presentation/filter/FilterContactTypeDialogFragment.java new file mode 100644 index 000000000..7e5f7599a --- /dev/null +++ b/app/src/main/java/ru/yandex/practicum/contacts/presentation/filter/FilterContactTypeDialogFragment.java @@ -0,0 +1,84 @@ +package ru.yandex.practicum.contacts.presentation.filter; + +import android.os.Bundle; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import ru.yandex.practicum.contacts.R; +import ru.yandex.practicum.contacts.model.ContactType; +import ru.yandex.practicum.contacts.presentation.base.BaseBottomSheetDialogFragment; +import ru.yandex.practicum.contacts.presentation.filter.model.FilterContactTypeUi; +import ru.yandex.practicum.contacts.ui.widget.DividerItemDecoration; + +public class FilterContactTypeDialogFragment extends BaseBottomSheetDialogFragment { + + public static final String REQUEST_KEY = "REQUEST_KEY_FILTER"; + public static final String ARG_SELECTED_FILTER_CONTACT_TYPE = "ARG_SELECTED_FILTER_CONTACT_TYPE"; + + private FilterContactTypeAdapter adapter; + + private FilterContactTypeDialogFragment() { + super(FilterContactTypeViewModel.class); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + iniViewModel(); + adapter = new FilterContactTypeAdapter(viewModel::onFilterTypeItemClick); + binding.recycler.setAdapter(adapter); + + final DividerItemDecoration decoration = new DividerItemDecoration( + requireActivity(), R.drawable.item_decoration_16dp, R.drawable.item_decoration_72dp, DividerItemDecoration.VERTICAL + ); + binding.recycler.addItemDecoration(decoration); + + viewModel.getFilterContactTypesLiveDate().observe(this, this::updateFilterContactTypes); + viewModel.getUiStateLiveDate().observe(this, this::updateState); + } + + private void iniViewModel() { + final Set defaultFilterContactTypes = from(getArguments()); + viewModel.init(defaultFilterContactTypes); + } + + private void updateFilterContactTypes(List filterTypes) { + adapter.setItems(filterTypes); + } + + private void updateState(FilterContactTypeViewModel.UiState state) { + binding.applyButton.setEnabled(state.isApplyEnable); + + if (!state.newSelectedContactTypes.isEmpty()) { + getParentFragmentManager().setFragmentResult(REQUEST_KEY, createBundle(state.newSelectedContactTypes)); + dismiss(); + } + } + + public static FilterContactTypeDialogFragment newInstance(Set selectedContactTypes) { + final FilterContactTypeDialogFragment fragment = new FilterContactTypeDialogFragment(); + fragment.setArguments(createBundle(selectedContactTypes)); + return fragment; + } + + @SuppressWarnings("unchecked") + public static Set from(@Nullable Bundle bundle) { + if (bundle == null) { + return Collections.emptySet(); + } + return (Set) bundle.getSerializable(ARG_SELECTED_FILTER_CONTACT_TYPE); + } + + private static Bundle createBundle(Set contactTypes) { + final Bundle bundle = new Bundle(); + bundle.putSerializable(ARG_SELECTED_FILTER_CONTACT_TYPE, new HashSet<>(contactTypes)); + return bundle; + } +} diff --git a/app/src/main/java/ru/yandex/practicum/contacts/presentation/filter/FilterContactTypeViewModel.java b/app/src/main/java/ru/yandex/practicum/contacts/presentation/filter/FilterContactTypeViewModel.java new file mode 100644 index 000000000..48c8c805b --- /dev/null +++ b/app/src/main/java/ru/yandex/practicum/contacts/presentation/filter/FilterContactTypeViewModel.java @@ -0,0 +1,103 @@ +package ru.yandex.practicum.contacts.presentation.filter; + +import androidx.lifecycle.MutableLiveData; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import ru.yandex.practicum.contacts.model.ContactType; +import ru.yandex.practicum.contacts.presentation.base.BaseBottomSheetViewModel; +import ru.yandex.practicum.contacts.presentation.filter.model.FilterContactType; +import ru.yandex.practicum.contacts.presentation.filter.model.FilterContactTypeUi; +import ru.yandex.practicum.contacts.utils.model.ContactTypeUtils; +import ru.yandex.practicum.contacts.utils.model.FilterContactTypeUtils; + +public class FilterContactTypeViewModel extends BaseBottomSheetViewModel { + + private final UiState uiState = new UiState(); + private final MutableLiveData> filterContactTypesLiveDate = new MutableLiveData<>(); + private final MutableLiveData uiStateLiveDate = new MutableLiveData<>(); + + private Set defaultFilterContactTypes; + private Set selectedFilterContactTypes; + + public void init(Set defaultFilterContactTypes) { + this.defaultFilterContactTypes = new HashSet<>(defaultFilterContactTypes); + this.selectedFilterContactTypes = new HashSet<>(defaultFilterContactTypes); + updateFilterContactTypes(); + updateUiState(); + } + + public void onFilterTypeItemClick(FilterContactTypeUi filterContactType) { + updateSelectedContactTypes(filterContactType.getContactType()); + updateFilterContactTypes(); + updateUiState(); + } + + @Override + public void onApplyClick() { + uiState.newSelectedContactTypes = selectedFilterContactTypes; + updateUiState(); + } + + @Override + public void onResetClick() { + selectedFilterContactTypes = new HashSet<>(defaultFilterContactTypes); + updateFilterContactTypes(); + updateUiState(); + } + + public MutableLiveData> getFilterContactTypesLiveDate() { + return filterContactTypesLiveDate; + } + + public MutableLiveData getUiStateLiveDate() { + return uiStateLiveDate; + } + + private void updateFilterContactTypes() { + final List filterContactTypesUi = new ArrayList<>(); + final boolean allSelected = selectedFilterContactTypes.size() == ContactType.values().length; + filterContactTypesUi.add(new FilterContactTypeUi(FilterContactType.ALL, allSelected)); + final List collect = Arrays.stream(ContactType.values()) + .map(contactType -> new FilterContactTypeUi( + ContactTypeUtils.toFilterContactType(contactType), + selectedFilterContactTypes.contains(contactType) + )) + .collect(Collectors.toList()); + filterContactTypesUi.addAll(collect); + filterContactTypesLiveDate.setValue(filterContactTypesUi); + } + + private void updateUiState() { + uiState.isApplyEnable = !defaultFilterContactTypes.equals(selectedFilterContactTypes) && !selectedFilterContactTypes.isEmpty(); + uiStateLiveDate.setValue(uiState); + } + + private void updateSelectedContactTypes(FilterContactType type) { + if (type == FilterContactType.ALL) { + if (selectedFilterContactTypes.size() == ContactType.values().length) { + selectedFilterContactTypes.clear(); + } else { + selectedFilterContactTypes.addAll(Arrays.asList(ContactType.values())); + } + return; + } + final ContactType contactType = FilterContactTypeUtils.toContactType(type); + if (selectedFilterContactTypes.contains(contactType)) { + selectedFilterContactTypes.remove(contactType); + } else { + selectedFilterContactTypes.add(contactType); + } + } + + static class UiState { + public boolean isApplyEnable = false; + public Set newSelectedContactTypes = Collections.emptySet(); + } +} diff --git a/app/src/main/java/ru/yandex/practicum/contacts/presentation/filter/model/FilterContactType.java b/app/src/main/java/ru/yandex/practicum/contacts/presentation/filter/model/FilterContactType.java new file mode 100644 index 000000000..6fca0ba88 --- /dev/null +++ b/app/src/main/java/ru/yandex/practicum/contacts/presentation/filter/model/FilterContactType.java @@ -0,0 +1,12 @@ +package ru.yandex.practicum.contacts.presentation.filter.model; + +public enum FilterContactType { + ALL, + TELEGRAM, + WHATS_APP, + VIBER, + SIGNAL, + THREEMA, + PHONE, + EMAIL +} diff --git a/app/src/main/java/ru/yandex/practicum/contacts/presentation/filter/model/FilterContactTypeUi.java b/app/src/main/java/ru/yandex/practicum/contacts/presentation/filter/model/FilterContactTypeUi.java new file mode 100644 index 000000000..fbb187fdf --- /dev/null +++ b/app/src/main/java/ru/yandex/practicum/contacts/presentation/filter/model/FilterContactTypeUi.java @@ -0,0 +1,40 @@ +package ru.yandex.practicum.contacts.presentation.filter.model; + +import androidx.annotation.NonNull; + +public class FilterContactTypeUi { + + private final FilterContactType contactType; + private final boolean selected; + + public FilterContactTypeUi(@NonNull FilterContactType contactType, boolean selected) { + this.contactType = contactType; + this.selected = selected; + } + + public FilterContactType getContactType() { + return contactType; + } + + public boolean isSelected() { + return selected; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + FilterContactTypeUi that = (FilterContactTypeUi) o; + + if (selected != that.selected) return false; + return contactType == that.contactType; + } + + @Override + public int hashCode() { + int result = contactType.hashCode(); + result = 31 * result + (selected ? 1 : 0); + return result; + } +} diff --git a/app/src/main/java/ru/yandex/practicum/contacts/presentation/main/ContactAdapter.java b/app/src/main/java/ru/yandex/practicum/contacts/presentation/main/ContactAdapter.java new file mode 100644 index 000000000..9c2317248 --- /dev/null +++ b/app/src/main/java/ru/yandex/practicum/contacts/presentation/main/ContactAdapter.java @@ -0,0 +1,114 @@ +package ru.yandex.practicum.contacts.presentation.main; + +import android.content.Context; +import android.graphics.drawable.Drawable; +import android.text.TextUtils; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.content.ContextCompat; +import androidx.recyclerview.widget.AdapterListUpdateCallback; +import androidx.recyclerview.widget.AsyncDifferConfig; +import androidx.recyclerview.widget.AsyncListDiffer; +import androidx.recyclerview.widget.DiffUtil; +import androidx.recyclerview.widget.RecyclerView; + +import com.bumptech.glide.Glide; + +import java.util.List; +import java.util.Objects; + +import ru.yandex.practicum.contacts.R; +import ru.yandex.practicum.contacts.databinding.ItemContactBinding; + +public class ContactAdapter extends RecyclerView.Adapter { + + private final AsyncListDiffer differ = new AsyncListDiffer<>( + new AdapterListUpdateCallback(this), + new AsyncDifferConfig.Builder<>(new ListDiffCallback()).build() + ); + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + final LayoutInflater inflater = LayoutInflater.from(parent.getContext()); + final ItemContactBinding binding = ItemContactBinding.inflate(inflater, parent, false); + return new ViewHolder(binding); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + holder.bind(differ.getCurrentList().get(position)); + } + + @Override + public int getItemCount() { + return differ.getCurrentList().size(); + } + + public void setItems(List items) { + differ.submitList(items); + } + + public void setItems(List items, @NonNull Runnable callback) { + differ.submitList(items, callback); + } + + static class ViewHolder extends RecyclerView.ViewHolder { + + private final ItemContactBinding binding; + + public ViewHolder(@NonNull ItemContactBinding binding) { + super(binding.getRoot()); + this.binding = binding; + binding.getRoot().setOnClickListener(view -> { + }); + } + + public void bind(ContactUi contact) { + binding.name.setText(contact.getName()); + loadAvatar(contact); + + final int phoneVisibility = TextUtils.isEmpty(contact.getPhone()) ? View.GONE : View.VISIBLE; + binding.phone.setText(contact.getPhone()); + binding.phone.setVisibility(phoneVisibility); + + binding.contactType.setData(contact.getTypes()); + } + + private void loadAvatar(ContactUi contact) { + final Context context = binding.contactPhoto.getContext(); + final Drawable drawable = Objects.requireNonNull(ContextCompat.getDrawable(context, R.drawable.ic_avatar)); + drawable.setTint(ContextCompat.getColor(context, R.color.color_light_grey)); + Glide.with(binding.contactPhoto) + .load(contact.getPhoto()) + .circleCrop() + .placeholder(drawable) + .fallback(drawable) + .error(drawable) + .into(binding.contactPhoto); + } + } + + static class ListDiffCallback extends DiffUtil.ItemCallback { + + @Override + public boolean areItemsTheSame(@NonNull ContactUi oldItem, @NonNull ContactUi newItem) { + return oldItem.hashCode() == newItem.hashCode(); + } + + @Override + public boolean areContentsTheSame(@NonNull ContactUi oldItem, @NonNull ContactUi newItem) { + return oldItem.equals(newItem); + } + + @Nullable + @Override + public Object getChangePayload(@NonNull ContactUi oldItem, @NonNull ContactUi newItem) { + return newItem; + } + } +} diff --git a/app/src/main/java/ru/yandex/practicum/contacts/presentation/main/ContactUi.java b/app/src/main/java/ru/yandex/practicum/contacts/presentation/main/ContactUi.java new file mode 100644 index 000000000..4b2f216e8 --- /dev/null +++ b/app/src/main/java/ru/yandex/practicum/contacts/presentation/main/ContactUi.java @@ -0,0 +1,65 @@ +package ru.yandex.practicum.contacts.presentation.main; + +import androidx.annotation.NonNull; + +import java.util.List; + +import ru.yandex.practicum.contacts.model.ContactType; + +public class ContactUi { + + private final String name; + private final String phone; + private final String photo; + private final List types; + + public ContactUi( + @NonNull String name, + @NonNull String phone, + @NonNull String photo, + @NonNull List types + ) { + this.name = name; + this.phone = phone; + this.photo = photo; + this.types = types; + } + + public String getName() { + return name; + } + + public String getPhone() { + return phone; + } + + public String getPhoto() { + return photo; + } + + public List getTypes() { + return types; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + ContactUi contact = (ContactUi) o; + + if (!name.equals(contact.name)) return false; + if (!phone.equals(contact.phone)) return false; + if (!photo.equals(contact.photo)) return false; + return types.equals(contact.types); + } + + @Override + public int hashCode() { + int result = name.hashCode(); + result = 31 * result + phone.hashCode(); + result = 31 * result + photo.hashCode(); + result = 31 * result + types.hashCode(); + return result; + } +} diff --git a/app/src/main/java/ru/yandex/practicum/contacts/presentation/main/MainActivity.java b/app/src/main/java/ru/yandex/practicum/contacts/presentation/main/MainActivity.java new file mode 100644 index 000000000..104653a53 --- /dev/null +++ b/app/src/main/java/ru/yandex/practicum/contacts/presentation/main/MainActivity.java @@ -0,0 +1,197 @@ +package ru.yandex.practicum.contacts.presentation.main; + +import android.annotation.SuppressLint; +import android.os.Bundle; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.Toast; + +import androidx.annotation.IdRes; +import androidx.annotation.StringRes; +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.content.ContextCompat; +import androidx.lifecycle.ViewModelProvider; + +import com.google.android.material.badge.BadgeDrawable; +import com.google.android.material.badge.BadgeUtils; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +import ru.yandex.practicum.contacts.R; +import ru.yandex.practicum.contacts.databinding.ActivityMainBinding; +import ru.yandex.practicum.contacts.model.ContactType; +import ru.yandex.practicum.contacts.presentation.filter.FilterContactTypeDialogFragment; +import ru.yandex.practicum.contacts.presentation.sort.SortDialogFragment; +import ru.yandex.practicum.contacts.presentation.main.model.MenuClick; +import ru.yandex.practicum.contacts.presentation.sort.model.SortType; +import ru.yandex.practicum.contacts.ui.widget.DividerItemDecoration; +import ru.yandex.practicum.contacts.utils.widget.EditTextUtils; + +@SuppressLint("UnsafeExperimentalUsageError") +public class MainActivity extends AppCompatActivity { + + public static final String SORT_TAG = "SORT_TAG"; + public static final String FILTER_TAG = "FILTER_TAG"; + + private ActivityMainBinding binding; + private MainViewModel viewModel; + private ContactAdapter adapter; + + private final Map badges = new HashMap<>(); + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + binding = ActivityMainBinding.inflate(getLayoutInflater()); + setContentView(binding.getRoot()); + setSupportActionBar(binding.toolbar); + binding.toolbar.setTitleTextAppearance(this, R.style.Toolbar_Title); + + adapter = new ContactAdapter(); + binding.recycler.setAdapter(adapter); + + final DividerItemDecoration decoration = new DividerItemDecoration(this, R.drawable.item_decoration_72dp, DividerItemDecoration.VERTICAL); + binding.recycler.addItemDecoration(decoration); + + viewModel = new ViewModelProvider(this).get(MainViewModel.class); + viewModel.getContactsLiveDate().observe(this, this::updateContacts); + viewModel.getUiStateLiveDate().observe(this, this::updateUiState); + + createBadges(); + EditTextUtils.addTextListener(binding.searchLayout.searchText, query -> viewModel.updateSearchText(query.toString())); + EditTextUtils.debounce(binding.searchLayout.searchText, query -> viewModel.search()); + binding.searchLayout.resetButton.setOnClickListener(view -> clearSearch()); + + getSupportFragmentManager().setFragmentResultListener(SortDialogFragment.REQUEST_KEY, this, (requestKey, result) -> { + final SortType newSortType = SortDialogFragment.from(result); + viewModel.updateSortType(newSortType); + }); + + getSupportFragmentManager().setFragmentResultListener(FilterContactTypeDialogFragment.REQUEST_KEY, this, (requestKey, result) -> { + final Set newFilterContactTypes = FilterContactTypeDialogFragment.from(result); + viewModel.updateFilterContactTypes(newFilterContactTypes); + }); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.menu_main, menu); + attachBadges(); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + int id = item.getItemId(); + if (id == R.id.menu_sort) { + viewModel.onMenuClick(MenuClick.SORT); + return true; + } + if (id == R.id.menu_filter) { + viewModel.onMenuClick(MenuClick.FILTER); + return true; + } + if (id == R.id.menu_search) { + viewModel.onMenuClick(MenuClick.SEARCH); + return true; + } + + return super.onOptionsItemSelected(item); + } + + private void showSortDialog(SortType sortType) { + SortDialogFragment.newInstance(sortType).show(getSupportFragmentManager(), SORT_TAG); + } + + private void showFilterContactTypeDialog(Set contactTypes) { + FilterContactTypeDialogFragment.newInstance(contactTypes).show(getSupportFragmentManager(), FILTER_TAG); + } + + @Override + public void onBackPressed() { + viewModel.onBackPressed(); + } + + private void updateContacts(List contacts) { + adapter.setItems(contacts, () -> binding.recycler.scrollToPosition(0)); + if (contacts.size() > 0) { + binding.recycler.setVisibility(View.VISIBLE); + binding.nothingFound.setVisibility(View.GONE); + } else { + binding.recycler.setVisibility(View.GONE); + binding.nothingFound.setVisibility(View.VISIBLE); + } + } + + private void updateUiState(MainViewModel.UiState uiState) { + final Boolean finishActivity = uiState.actions.finishActivity.data; + if (finishActivity != null && finishActivity) { + finish(); + return; + } + binding.searchLayout.getRoot().setVisibility(uiState.searchVisibility ? View.VISIBLE : View.GONE); + binding.searchLayout.resetButton.setVisibility(uiState.resetSearchButtonVisibility ? View.VISIBLE : View.GONE); + if (uiState.actions.showSortTypeDialog.data != null) { + showSortDialog(uiState.actions.showSortTypeDialog.data); + } + final Set filterContactTypes = uiState.actions.showFilterContactTypeDialog.data; + if (filterContactTypes != null && filterContactTypes.size() > 0) { + showFilterContactTypeDialog(filterContactTypes); + } + updateBadges(uiState); + } + + private void updateBadges(MainViewModel.UiState uiState) { + updateBadge(uiState.menuBadges.sort, R.id.menu_sort); + updateBadge(uiState.menuBadges.filter, R.id.menu_filter); + updateBadge(uiState.menuBadges.search, R.id.menu_search); + } + + private void updateBadge(MainViewModel.UiState.MenuBadge badge, @IdRes int menuItemId) { + final BadgeDrawable drawable = Objects.requireNonNull(badges.get(menuItemId)); + if (badge != null) { + drawable.setVisible(true); + if (badge.value > 0) { + drawable.setNumber(badge.value); + } else { + drawable.clearNumber(); + } + } else { + drawable.setVisible(false); + } + } + + private void createBadges() { + badges.put(R.id.menu_sort, createBadge()); + badges.put(R.id.menu_filter, createBadge()); + badges.put(R.id.menu_search, createBadge()); + } + + private void attachBadges(){ + for (Map.Entry entry : badges.entrySet()) { + BadgeUtils.attachBadgeDrawable(entry.getValue(), binding.toolbar, entry.getKey()); + } + } + + private BadgeDrawable createBadge() { + final BadgeDrawable drawable = BadgeDrawable.create(this); + drawable.setBackgroundColor(ContextCompat.getColor(this, R.color.color_red)); + drawable.setVisible(false); + return drawable; + } + + private void clearSearch() { + binding.searchLayout.searchText.setText(""); + viewModel.search(); + } + + private void toast(@StringRes int res) { + Toast.makeText(this, res, Toast.LENGTH_SHORT).show(); + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/yandex/practicum/contacts/presentation/main/MainState.java b/app/src/main/java/ru/yandex/practicum/contacts/presentation/main/MainState.java new file mode 100644 index 000000000..dd1708e8a --- /dev/null +++ b/app/src/main/java/ru/yandex/practicum/contacts/presentation/main/MainState.java @@ -0,0 +1,70 @@ +package ru.yandex.practicum.contacts.presentation.main; + +import androidx.annotation.NonNull; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import ru.yandex.practicum.contacts.model.ContactType; +import ru.yandex.practicum.contacts.model.MergedContact; +import ru.yandex.practicum.contacts.presentation.sort.model.SortType; + +public class MainState { + + private final SortType defaultSortType = SortType.BY_NAME; + private final Set defaultContactTypes = Collections.unmodifiableSet(new HashSet<>(Arrays.asList(ContactType.values()))); + + private List allContacts = Collections.emptyList(); + private SortType sortType = defaultSortType; + private Set contactTypes = new HashSet<>(defaultContactTypes); + private String query = ""; + + @NonNull + public List getAllContacts() { + return allContacts; + } + + public void setAllContacts(@NonNull List allContacts) { + this.allContacts = allContacts; + } + + @NonNull + public SortType getDefaultSortType() { + return defaultSortType; + } + + @NonNull + public SortType getSortType() { + return sortType; + } + + public void setSortType(@NonNull SortType sortType) { + this.sortType = sortType; + } + + @NonNull + public Set getDefaultContactTypes() { + return defaultContactTypes; + } + + @NonNull + public Set getContactTypes() { + return contactTypes; + } + + public void setContactTypes(@NonNull Set contactTypes) { + this.contactTypes = contactTypes; + } + + @NonNull + public String getQuery() { + return query; + } + + public void setQuery(@NonNull String query) { + this.query = query; + } +} diff --git a/app/src/main/java/ru/yandex/practicum/contacts/presentation/main/MainViewModel.java b/app/src/main/java/ru/yandex/practicum/contacts/presentation/main/MainViewModel.java new file mode 100644 index 000000000..29cfee42b --- /dev/null +++ b/app/src/main/java/ru/yandex/practicum/contacts/presentation/main/MainViewModel.java @@ -0,0 +1,290 @@ +package ru.yandex.practicum.contacts.presentation.main; + +import android.app.Application; +import android.text.TextUtils; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.lifecycle.AndroidViewModel; +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; + +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; + +import ru.yandex.practicum.contacts.model.Contact; +import ru.yandex.practicum.contacts.model.ContactSource; +import ru.yandex.practicum.contacts.model.ContactType; +import ru.yandex.practicum.contacts.model.MergedContact; +import ru.yandex.practicum.contacts.interactor.ContactMerger; +import ru.yandex.practicum.contacts.repository.ContactRepository; +import ru.yandex.practicum.contacts.repository.ContactSourceRepository; +import ru.yandex.practicum.contacts.mapper.ContactUiMapper; +import ru.yandex.practicum.contacts.presentation.main.model.MenuClick; +import ru.yandex.practicum.contacts.presentation.sort.model.SortType; +import ru.yandex.practicum.contacts.utils.java.ThreadUtils; +import ru.yandex.practicum.contacts.utils.model.MergedContactUtils; + +public class MainViewModel extends AndroidViewModel { + + private final ContactSourceRepository contactSourceRepository; + private final ContactRepository contactRepository; + private final ContactMerger contactMerger; + private final ContactUiMapper uiMapper; + + private final MutableLiveData> contactsLiveDate = new MutableLiveData<>(); + private final MutableLiveData uiStateLiveDate = new MutableLiveData<>(); + + private final MainState state = new MainState(); + private final UiState uiState = new UiState(); + + public MainViewModel(@NonNull Application application) { + super(application); + contactSourceRepository = new ContactSourceRepository(application); + contactRepository = new ContactRepository(application); + contactMerger = new ContactMerger(); + uiMapper = new ContactUiMapper(); + ThreadUtils.runAsync(this::initLoading); + } + + public LiveData> getContactsLiveDate() { + return contactsLiveDate; + } + + public MutableLiveData getUiStateLiveDate() { + return uiStateLiveDate; + } + + public void initLoading() { + final Set sources = contactSourceRepository.getAllContactSources(); + final List sourceNames = sources.stream() + .map(ContactSource::getName) + .collect(Collectors.toList()); + final List contacts = contactRepository.getContacts(sourceNames); + + final List allContacts = contactMerger.getMergedContacts(contacts, sources); + state.setAllContacts(allContacts); + mapContactsAndUpdate(); + } + + public void search() { + mapContactsAndUpdate(); + } + + public void onMenuClick(MenuClick click) { + switch (click) { + case SORT: + uiState.actions.showSortTypeDialog.data = state.getSortType(); + break; + case FILTER: + uiState.actions.showFilterContactTypeDialog.data = new HashSet<>(state.getContactTypes()); + break; + case SEARCH: + uiState.searchVisibility = !uiState.searchVisibility; + break; + } + updateUiState(); + } + + public void updateSortType(SortType sortType) { + state.setSortType(sortType); + updateBadges(); + mapContactsAndUpdate(); + } + + public void updateFilterContactTypes(Set filterContactTypes) { + state.setContactTypes(filterContactTypes); + updateBadges(); + mapContactsAndUpdate(); + } + + public void onBackPressed() { + if (uiState.searchVisibility) { + uiState.searchVisibility = false; + } else { + uiState.actions.finishActivity.data = true; + } + updateUiState(); + } + + public void updateSearchText(String query) { + state.setQuery(query); + uiState.resetSearchButtonVisibility = state.getQuery().length() != 0; + updateUiState(); + } + + private void updateBadges() { + if (state.getSortType() != state.getDefaultSortType()) { + uiState.menuBadges.sort = new UiState.MenuBadge(0); + } else { + uiState.menuBadges.sort = null; + } + + if (!state.getContactTypes().equals(state.getDefaultContactTypes())) { + uiState.menuBadges.filter = new UiState.MenuBadge(state.getContactTypes().size()); + } else { + uiState.menuBadges.filter = null; + } + + updateUiState(); + } + + private void mapContactsAndUpdate() { + final List uiContacts = state.getAllContacts().stream() + .filter(contact -> MergedContactUtils.contains(contact, state.getQuery())) + .filter(contact -> MergedContactUtils.contains(contact, state.getContactTypes())) + .sorted(createComparator(state.getSortType())) + .map(uiMapper::map) + .collect(Collectors.toList()); + contactsLiveDate.postValue(uiContacts); + } + + private Comparator createComparator(SortType type) { + switch (type) { + case BY_NAME: + return createComparator(MergedContact::getFirstName) + .thenComparing(createComparator(MergedContact::getSurname)) + .thenComparing(createComparator(MergedContact::getNormalizedNumber)) + .thenComparing(createComparator(MergedContact::getEmail)); + case BY_NAME_REVERSED: + return createReversedComparator(MergedContact::getFirstName) + .thenComparing(createReversedComparator(MergedContact::getSurname)) + .thenComparing(createReversedComparator(MergedContact::getNormalizedNumber)) + .thenComparing(createReversedComparator(MergedContact::getEmail)); + case BY_SURNAME: + return createComparator(MergedContact::getSurname) + .thenComparing(createComparator(MergedContact::getFirstName)) + .thenComparing(createComparator(MergedContact::getNormalizedNumber)) + .thenComparing(createComparator(MergedContact::getEmail)); + case BY_SURNAME_REVERSED: + return createReversedComparator(MergedContact::getSurname) + .thenComparing(createReversedComparator(MergedContact::getFirstName)) + .thenComparing(createReversedComparator(MergedContact::getNormalizedNumber)) + .thenComparing(createReversedComparator(MergedContact::getEmail)); + default: + throw new IllegalArgumentException("Not supported SortType"); + } + } + + private Comparator createComparator(Function keyExtractor) { + return (left, right) -> { + final String leftField = keyExtractor.apply(left); + final String rightField = keyExtractor.apply(right); + if (!TextUtils.isEmpty(leftField) && !TextUtils.isEmpty(rightField)) { + return leftField.compareTo(rightField); + } + // Empty lines should be after + if (TextUtils.isEmpty(leftField) && !TextUtils.isEmpty(rightField)) { + return 1; + } + if (!TextUtils.isEmpty(leftField) && TextUtils.isEmpty(rightField)) { + return -1; + } + return 0; + }; + } + + private Comparator createReversedComparator(Function keyExtractor) { + return (left, right) -> { + final String leftField = keyExtractor.apply(left); + final String rightField = keyExtractor.apply(right); + if (!TextUtils.isEmpty(leftField) && !TextUtils.isEmpty(rightField)) { + return rightField.compareTo(leftField); + } + // Empty lines should be after + if (TextUtils.isEmpty(leftField) && !TextUtils.isEmpty(rightField)) { + return 1; + } + if (!TextUtils.isEmpty(leftField) && TextUtils.isEmpty(rightField)) { + return -1; + } + return 0; + }; + } + + private void updateUiState() { + uiStateLiveDate.setValue(uiState.copy()); + uiState.actions.clear(); + } + + public static class UiState { + + public boolean searchVisibility = false; + public boolean resetSearchButtonVisibility = false; + + public Actions actions = new Actions(); + public MenuBadges menuBadges = new MenuBadges(); + + @NonNull + public UiState copy() { + final UiState copy = new UiState(); + copy.searchVisibility = searchVisibility; + copy.resetSearchButtonVisibility = resetSearchButtonVisibility; + copy.actions = actions.copy(); + copy.menuBadges = menuBadges.copy(); + return copy; + } + + public static class Actions { + public Action finishActivity = new Action<>(false); + public Action showSortTypeDialog = new Action<>(null); + public Action> showFilterContactTypeDialog = new Action<>(Collections.emptySet()); + + @NonNull + public Actions copy() { + final Actions copy = new Actions(); + copy.finishActivity = new Action<>(finishActivity.data); + copy.showSortTypeDialog = new Action<>(showSortTypeDialog.data); + copy.showFilterContactTypeDialog = new Action<>(showFilterContactTypeDialog.data); + return copy; + } + + public void clear() { + finishActivity.data = false; + showSortTypeDialog.data = null; + showFilterContactTypeDialog.data = Collections.emptySet(); + } + } + + public static class Action { + @Nullable + public T data; + + public Action(@Nullable T value) { + this.data = value; + } + } + + public static class MenuBadges { + + @Nullable + public MenuBadge sort = null; + @Nullable + public MenuBadge filter = null; + @Nullable + public MenuBadge search = null; + + public MenuBadges copy() { + final MenuBadges copy = new MenuBadges(); + copy.sort = sort == null ? null : new MenuBadge(sort.value); + copy.filter = filter == null ? null : new MenuBadge(filter.value); + copy.search = search == null ? null : new MenuBadge(search.value); + return copy; + } + } + + public static class MenuBadge { + + public int value; + + public MenuBadge(int value) { + this.value = value; + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/yandex/practicum/contacts/presentation/main/model/MenuClick.java b/app/src/main/java/ru/yandex/practicum/contacts/presentation/main/model/MenuClick.java new file mode 100644 index 000000000..a85b8448a --- /dev/null +++ b/app/src/main/java/ru/yandex/practicum/contacts/presentation/main/model/MenuClick.java @@ -0,0 +1,5 @@ +package ru.yandex.practicum.contacts.presentation.main.model; + +public enum MenuClick { + SORT, FILTER, SEARCH +} diff --git a/app/src/main/java/ru/yandex/practicum/contacts/presentation/sort/SortDialogFragment.java b/app/src/main/java/ru/yandex/practicum/contacts/presentation/sort/SortDialogFragment.java new file mode 100644 index 000000000..06ac17cb7 --- /dev/null +++ b/app/src/main/java/ru/yandex/practicum/contacts/presentation/sort/SortDialogFragment.java @@ -0,0 +1,80 @@ +package ru.yandex.practicum.contacts.presentation.sort; + +import android.os.Bundle; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.content.ContextCompat; +import androidx.recyclerview.widget.DividerItemDecoration; + +import java.util.List; +import java.util.Objects; + +import ru.yandex.practicum.contacts.R; +import ru.yandex.practicum.contacts.presentation.base.BaseBottomSheetDialogFragment; +import ru.yandex.practicum.contacts.presentation.sort.model.SortType; + +public class SortDialogFragment extends BaseBottomSheetDialogFragment { + + public static final String REQUEST_KEY = "REQUEST_KEY_SORT"; + public static final String ARG_SELECTED_SORT_TYPE = "ARG_SELECTED_SORT_TYPE"; + + private SortTypeAdapter adapter; + + private SortDialogFragment() { + super(SortViewModel.class); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + iniViewModel(); + adapter = new SortTypeAdapter(viewModel::onSortTypeItemClick); + binding.recycler.setAdapter(adapter); + + final DividerItemDecoration decoration = new DividerItemDecoration(requireActivity(), DividerItemDecoration.VERTICAL); + decoration.setDrawable(Objects.requireNonNull(ContextCompat.getDrawable(requireActivity(), R.drawable.item_decoration_16dp))); + binding.recycler.addItemDecoration(decoration); + + viewModel.getSortTypesLiveDate().observe(this, this::updateSortTypes); + viewModel.getUiStateLiveDate().observe(this, this::updateState); + } + + private void iniViewModel() { + final SortType defaultSortType = from(getArguments()); + viewModel.init(defaultSortType); + } + + private void updateSortTypes(List sortTypes) { + adapter.setItems(sortTypes); + } + + private void updateState(SortViewModel.UiState state) { + binding.applyButton.setEnabled(state.isApplyEnable); + + if (state.newSelectedSortType != null) { + getParentFragmentManager().setFragmentResult(REQUEST_KEY, createBundle(state.newSelectedSortType)); + dismiss(); + } + } + + public static SortDialogFragment newInstance(SortType selectedSortType) { + final SortDialogFragment fragment = new SortDialogFragment(); + fragment.setArguments(createBundle(selectedSortType)); + return fragment; + } + + public static SortType from(@Nullable Bundle bundle) { + if (bundle == null) { + return SortType.BY_NAME; + } + return (SortType) bundle.getSerializable(ARG_SELECTED_SORT_TYPE); + } + + private static Bundle createBundle(SortType sortType) { + final Bundle bundle = new Bundle(); + bundle.putSerializable(ARG_SELECTED_SORT_TYPE, sortType); + return bundle; + } +} diff --git a/app/src/main/java/ru/yandex/practicum/contacts/presentation/sort/SortTypeAdapter.java b/app/src/main/java/ru/yandex/practicum/contacts/presentation/sort/SortTypeAdapter.java new file mode 100644 index 000000000..fde5f59c8 --- /dev/null +++ b/app/src/main/java/ru/yandex/practicum/contacts/presentation/sort/SortTypeAdapter.java @@ -0,0 +1,110 @@ +package ru.yandex.practicum.contacts.presentation.sort; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.recyclerview.widget.AdapterListUpdateCallback; +import androidx.recyclerview.widget.AsyncDifferConfig; +import androidx.recyclerview.widget.AsyncListDiffer; +import androidx.recyclerview.widget.DiffUtil; +import androidx.recyclerview.widget.RecyclerView; + +import java.util.List; +import java.util.function.Consumer; + +import ru.yandex.practicum.contacts.R; +import ru.yandex.practicum.contacts.databinding.ItemSortBinding; +import ru.yandex.practicum.contacts.presentation.sort.model.SortType; + +public class SortTypeAdapter extends RecyclerView.Adapter { + + private final AsyncListDiffer differ = new AsyncListDiffer<>( + new AdapterListUpdateCallback(this), + new AsyncDifferConfig.Builder<>(new ListDiffCallback()).build() + ); + + private final Consumer clickListener; + + public SortTypeAdapter(Consumer clickListener) { + this.clickListener = clickListener; + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + final LayoutInflater inflater = LayoutInflater.from(parent.getContext()); + final ItemSortBinding binding = ItemSortBinding.inflate(inflater, parent, false); + return new ViewHolder(binding, clickListener); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + holder.bind(differ.getCurrentList().get(position)); + } + + @Override + public int getItemCount() { + return differ.getCurrentList().size(); + } + + public void setItems(List items) { + differ.submitList(items); + } + + static class ViewHolder extends RecyclerView.ViewHolder { + + private final ItemSortBinding binding; + + private SortTypeUI data; + + public ViewHolder(@NonNull ItemSortBinding binding, Consumer clickListener) { + super(binding.getRoot()); + this.binding = binding; + this.binding.getRoot().setOnClickListener(v -> clickListener.accept(data)); + } + + public void bind(SortTypeUI data) { + this.data = data; + final int sortResId = resource(data.getSortType()); + binding.text.setText(sortResId); + binding.selected.setVisibility(data.isSelected() ? View.VISIBLE : View.GONE); + } + + private int resource(SortType sortType) { + switch (sortType) { + case BY_NAME: + return R.string.sort_by_name; + case BY_NAME_REVERSED: + return R.string.sort_by_name_reversed; + case BY_SURNAME: + return R.string.sort_by_surname; + case BY_SURNAME_REVERSED: + return R.string.sort_by_surname_reversed; + default: + throw new IllegalArgumentException("Not supported SortType"); + } + } + } + + static class ListDiffCallback extends DiffUtil.ItemCallback { + + @Override + public boolean areItemsTheSame(@NonNull SortTypeUI oldItem, @NonNull SortTypeUI newItem) { + return oldItem.getSortType() == newItem.getSortType(); + } + + @Override + public boolean areContentsTheSame(@NonNull SortTypeUI oldItem, @NonNull SortTypeUI newItem) { + return oldItem.equals(newItem); + } + + @Nullable + @Override + public Object getChangePayload(@NonNull SortTypeUI oldItem, @NonNull SortTypeUI newItem) { + return newItem; + } + } +} diff --git a/app/src/main/java/ru/yandex/practicum/contacts/presentation/sort/SortTypeUI.java b/app/src/main/java/ru/yandex/practicum/contacts/presentation/sort/SortTypeUI.java new file mode 100644 index 000000000..eb71f27cd --- /dev/null +++ b/app/src/main/java/ru/yandex/practicum/contacts/presentation/sort/SortTypeUI.java @@ -0,0 +1,42 @@ +package ru.yandex.practicum.contacts.presentation.sort; + +import androidx.annotation.NonNull; + +import ru.yandex.practicum.contacts.presentation.sort.model.SortType; + +public class SortTypeUI { + + private final SortType sortType; + private final boolean selected; + + public SortTypeUI(@NonNull SortType sortType, boolean selected) { + this.sortType = sortType; + this.selected = selected; + } + + public SortType getSortType() { + return sortType; + } + + public boolean isSelected() { + return selected; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + SortTypeUI that = (SortTypeUI) o; + + if (selected != that.selected) return false; + return sortType == that.sortType; + } + + @Override + public int hashCode() { + int result = sortType.hashCode(); + result = 31 * result + (selected ? 1 : 0); + return result; + } +} diff --git a/app/src/main/java/ru/yandex/practicum/contacts/presentation/sort/SortViewModel.java b/app/src/main/java/ru/yandex/practicum/contacts/presentation/sort/SortViewModel.java new file mode 100644 index 000000000..1ce55fd6f --- /dev/null +++ b/app/src/main/java/ru/yandex/practicum/contacts/presentation/sort/SortViewModel.java @@ -0,0 +1,74 @@ +package ru.yandex.practicum.contacts.presentation.sort; + +import androidx.annotation.Nullable; +import androidx.lifecycle.MutableLiveData; + +import java.util.Arrays; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import ru.yandex.practicum.contacts.presentation.base.BaseBottomSheetViewModel; +import ru.yandex.practicum.contacts.presentation.sort.model.SortType; + +public class SortViewModel extends BaseBottomSheetViewModel { + + private final UiState uiState = new UiState(); + private final MutableLiveData> sortTypesLiveDate = new MutableLiveData<>(); + private final MutableLiveData uiStateLiveDate = new MutableLiveData<>(); + + private SortType defaultSortType; + private SortType selectedSortType; + + public void init(SortType defaultSortType) { + this.defaultSortType = defaultSortType; + this.selectedSortType = defaultSortType; + updateSortTypes(); + updateUiState(); + } + + public void onSortTypeItemClick(SortTypeUI sortType) { + selectedSortType = sortType.getSortType(); + updateSortTypes(); + updateUiState(); + } + + @Override + public void onApplyClick() { + uiState.newSelectedSortType = selectedSortType; + updateUiState(); + } + + @Override + public void onResetClick() { + selectedSortType = defaultSortType; + updateSortTypes(); + updateUiState(); + } + + public MutableLiveData> getSortTypesLiveDate() { + return sortTypesLiveDate; + } + + public MutableLiveData getUiStateLiveDate() { + return uiStateLiveDate; + } + + private void updateSortTypes() { + final SortType[] sortTypes = SortType.values(); + final List sortTypesUi = Arrays.stream(sortTypes) + .map(sortType -> new SortTypeUI(sortType, Objects.equals(sortType, selectedSortType))) + .collect(Collectors.toList()); + sortTypesLiveDate.setValue(sortTypesUi); + } + + private void updateUiState() { + uiState.isApplyEnable = defaultSortType != selectedSortType; + uiStateLiveDate.setValue(uiState); + } + + static class UiState { + public boolean isApplyEnable = false; + @Nullable public SortType newSelectedSortType = null; + } +} diff --git a/app/src/main/java/ru/yandex/practicum/contacts/presentation/sort/model/SortType.java b/app/src/main/java/ru/yandex/practicum/contacts/presentation/sort/model/SortType.java new file mode 100644 index 000000000..31844dcb6 --- /dev/null +++ b/app/src/main/java/ru/yandex/practicum/contacts/presentation/sort/model/SortType.java @@ -0,0 +1,8 @@ +package ru.yandex.practicum.contacts.presentation.sort.model; + +public enum SortType { + BY_NAME, + BY_NAME_REVERSED, + BY_SURNAME, + BY_SURNAME_REVERSED +} diff --git a/app/src/main/java/ru/yandex/practicum/contacts/repository/ContactRepository.java b/app/src/main/java/ru/yandex/practicum/contacts/repository/ContactRepository.java new file mode 100644 index 000000000..6010726ce --- /dev/null +++ b/app/src/main/java/ru/yandex/practicum/contacts/repository/ContactRepository.java @@ -0,0 +1,233 @@ +package ru.yandex.practicum.contacts.repository; + +import android.content.Context; +import android.net.Uri; +import android.provider.ContactsContract.CommonDataKinds; +import android.provider.ContactsContract.CommonDataKinds.Organization; +import android.provider.ContactsContract.CommonDataKinds.Phone; +import android.provider.ContactsContract.CommonDataKinds.StructuredName; +import android.provider.ContactsContract.CommonDataKinds.StructuredPostal; +import android.provider.ContactsContract.Data; +import android.provider.ContactsContract.RawContacts; +import android.telephony.PhoneNumberUtils; +import android.text.TextUtils; +import android.util.SparseArray; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +import ru.yandex.practicum.contacts.model.Address; +import ru.yandex.practicum.contacts.model.Contact; +import ru.yandex.practicum.contacts.model.Email; +import ru.yandex.practicum.contacts.model.PhoneNumber; +import ru.yandex.practicum.contacts.utils.android.ContextUtils; +import ru.yandex.practicum.contacts.utils.android.CursorUtils; + +public class ContactRepository { + + private final Context context; + + public ContactRepository(Context context) { + this.context = context; + } + + public List getContacts(Collection sources) { + if (!ContextUtils.hasContactPermissions(context)) { + return Collections.emptyList(); + } + + List contacts = new ArrayList<>(); + + final Uri uri = Data.CONTENT_URI; + final String[] projection = getContactProjection(); + SparseArray> emails = getEmails(sources); + SparseArray> phoneNumbers = getPhoneNumbers(sources); + SparseArray> addresses = getAddresses(sources); + + String[] mimeTypes = new String[]{Organization.CONTENT_ITEM_TYPE, StructuredName.CONTENT_ITEM_TYPE}; + for (String mimeType : mimeTypes) { + String selection = Data.MIMETYPE + " = ?"; + String[] selectionArgs = new String[]{mimeType}; + String sortOrder = Data.RAW_CONTACT_ID; + + ContextUtils.query(context, uri, projection, selection, selectionArgs, cursor -> { + String accountName = CursorUtils.getString(cursor, RawContacts.ACCOUNT_NAME); + + int id = CursorUtils.getInteger(cursor, Data.RAW_CONTACT_ID); + String prefix = ""; + String firstName = ""; + String middleName = ""; + String surname = ""; + String suffix = ""; + + // ignore names at Organization type contacts + if (Objects.equals(mimeType, StructuredName.CONTENT_ITEM_TYPE)) { + prefix = CursorUtils.getString(cursor, StructuredName.PREFIX); + firstName = CursorUtils.getString(cursor, StructuredName.GIVEN_NAME); + middleName = CursorUtils.getString(cursor, StructuredName.MIDDLE_NAME); + surname = CursorUtils.getString(cursor, StructuredName.FAMILY_NAME); + suffix = CursorUtils.getString(cursor, StructuredName.SUFFIX); + } + + String photoUri = CursorUtils.getString(cursor, StructuredName.PHOTO_URI); + int starred = CursorUtils.getInteger(cursor, StructuredName.STARRED); + int contactId = CursorUtils.getInteger(cursor, Data.CONTACT_ID); + String thumbnailUri = CursorUtils.getString(cursor, StructuredName.PHOTO_THUMBNAIL_URI); + + Contact contact = new Contact( + id, + prefix, + firstName, + middleName, + surname, + suffix, + photoUri, + phoneNumbers.get(id, new ArrayList<>()), + emails.get(id, new ArrayList<>()), + addresses.get(id, new ArrayList<>()), + accountName, + starred, + contactId, + thumbnailUri + ); + + contacts.add(contact); + }); + } + + return Collections.unmodifiableList(contacts); + } + + private String[] getContactProjection() { + return new String[]{ + Data.MIMETYPE, + Data.CONTACT_ID, + Data.RAW_CONTACT_ID, + StructuredName.PREFIX, + StructuredName.GIVEN_NAME, + StructuredName.MIDDLE_NAME, + StructuredName.FAMILY_NAME, + StructuredName.SUFFIX, + StructuredName.PHOTO_URI, + StructuredName.PHOTO_THUMBNAIL_URI, + StructuredName.STARRED, + StructuredName.CUSTOM_RINGTONE, + RawContacts.ACCOUNT_NAME, + RawContacts.ACCOUNT_TYPE + }; + } + + private SparseArray> getEmails(Collection sources) { + SparseArray> emails = new SparseArray<>(); + Uri uri = CommonDataKinds.Email.CONTENT_URI; + String[] projection = new String[]{ + Data.RAW_CONTACT_ID, + CommonDataKinds.Email.DATA, + CommonDataKinds.Email.TYPE, + CommonDataKinds.Email.LABEL + }; + + String selection = getSourcesSelection(sources.size()); + String[] selectionArgs = getSourcesSelectionArgs(sources); + + ContextUtils.query(context, uri, projection, selection, selectionArgs, cursor -> { + int id = CursorUtils.getInteger(cursor, Data.RAW_CONTACT_ID); + String email = CursorUtils.getString(cursor, CommonDataKinds.Email.DATA); + int type = CursorUtils.getInteger(cursor, CommonDataKinds.Email.TYPE); + String label = CursorUtils.getString(cursor, CommonDataKinds.Email.LABEL); + + if (!TextUtils.isEmpty(email)) { + if (emails.get(id) == null) { + emails.put(id, new ArrayList<>()); + } + emails.get(id).add(new Email(email, type, label)); + } + }); + return emails; + } + + private SparseArray> getPhoneNumbers(Collection sources) { + SparseArray> phoneNumbers = new SparseArray<>(); + Uri uri = Phone.CONTENT_URI; + String[] projection = new String[]{ + Data.RAW_CONTACT_ID, + Phone.NUMBER, + Phone.NORMALIZED_NUMBER, + Phone.TYPE, + Phone.LABEL + }; + + String selection = getSourcesSelection(sources.size()); + String[] selectionArgs = getSourcesSelectionArgs(sources); + + ContextUtils.query(context, uri, projection, selection, selectionArgs, cursor -> { + int id = CursorUtils.getInteger(cursor, Data.RAW_CONTACT_ID); + String number = CursorUtils.getString(cursor, Phone.NUMBER); + String normalizedNumber = CursorUtils.getString(cursor, Phone.NORMALIZED_NUMBER); + int type = CursorUtils.getInteger(cursor, Phone.TYPE); + String label = CursorUtils.getString(cursor, Phone.LABEL); + + if (!TextUtils.isEmpty(number)) { + if (TextUtils.isEmpty(normalizedNumber)) { + normalizedNumber = PhoneNumberUtils.normalizeNumber(number); + } + if (phoneNumbers.get(id) == null) { + phoneNumbers.put(id, new ArrayList<>()); + } + PhoneNumber phoneNumber = new PhoneNumber(number, type, label, normalizedNumber); + phoneNumbers.get(id).add(phoneNumber); + } + }); + + return phoneNumbers; + } + + private SparseArray> getAddresses(Collection sources) { + SparseArray> addresses = new SparseArray<>(); + Uri uri = StructuredPostal.CONTENT_URI; + String[] projection = new String[]{ + Data.RAW_CONTACT_ID, + StructuredPostal.FORMATTED_ADDRESS, + StructuredPostal.TYPE, + StructuredPostal.LABEL + }; + + String selection = getSourcesSelection(sources.size()); + String[] selectionArgs = getSourcesSelectionArgs(sources); + + ContextUtils.query(context, uri, projection, selection, selectionArgs, cursor -> { + int id = CursorUtils.getInteger(cursor, Data.RAW_CONTACT_ID); + String address = CursorUtils.getString(cursor, StructuredPostal.FORMATTED_ADDRESS); + int type = CursorUtils.getInteger(cursor, StructuredPostal.TYPE); + String label = CursorUtils.getString(cursor, StructuredPostal.LABEL); + + if (!TextUtils.isEmpty(address)) { + if (addresses.get(id) == null) { + addresses.put(id, new ArrayList<>()); + } + + addresses.get(id).add(new Address(address, type, label)); + } + }); + + return addresses; + } + + private String getSourcesSelection(int count) { + return RawContacts.ACCOUNT_NAME + " IN (" + getQuestionMarks(count) + ")"; + } + + private String getQuestionMarks(int count) { + final String[] symbols = new String[count]; + Arrays.fill(symbols, "?"); + return TextUtils.join(",", symbols); + } + + private String[] getSourcesSelectionArgs(Collection sources) { + return sources.toArray(new String[0]); + } +} diff --git a/app/src/main/java/ru/yandex/practicum/contacts/repository/ContactSourceRepository.java b/app/src/main/java/ru/yandex/practicum/contacts/repository/ContactSourceRepository.java new file mode 100644 index 000000000..b091e13b4 --- /dev/null +++ b/app/src/main/java/ru/yandex/practicum/contacts/repository/ContactSourceRepository.java @@ -0,0 +1,154 @@ +package ru.yandex.practicum.contacts.repository; + +import android.accounts.Account; +import android.accounts.AccountManager; +import android.content.ContentProviderOperation; +import android.content.ContentProviderResult; +import android.content.ContentResolver; +import android.content.Context; +import android.net.Uri; +import android.provider.ContactsContract; +import android.provider.ContactsContract.Groups; +import android.provider.ContactsContract.RawContacts; +import android.provider.ContactsContract.Settings; +import android.text.TextUtils; + +import androidx.annotation.Nullable; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.stream.Collectors; + +import ru.yandex.practicum.contacts.model.ContactSource; +import ru.yandex.practicum.contacts.utils.Constants; +import ru.yandex.practicum.contacts.utils.android.ContextUtils; +import ru.yandex.practicum.contacts.utils.android.CursorUtils; + +public class ContactSourceRepository { + + private final Context context; + + private boolean wasLocalAccountInitialized = false; + + public ContactSourceRepository(Context context) { + this.context = context; + } + + public Set getAllContactSources() { + if (!ContextUtils.hasContactPermissions(context)) { + return Collections.emptySet(); + } + if (!wasLocalAccountInitialized) { + initializeLocalPhoneAccount(); + wasLocalAccountInitialized = true; + } + + final Set contactSources = new HashSet<>(); + final Account[] accounts = AccountManager.get(context).getAccounts(); + final Set accountSources = Arrays.stream(accounts) + .map(this::getContactSource) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + + final Set contentResolverSources = getContentResolverAccounts().stream() + .filter(this::isNotEmpty) + .collect(Collectors.toSet()); + + contactSources.addAll(accountSources); + contactSources.addAll(contentResolverSources); + + if (!containsRegularPhoneSource(contactSources)) { + contactSources.add(new ContactSource(Constants.StorageType.PHONE_STORAGE, Constants.StorageType.PHONE_STORAGE, Constants.StorageType.PHONE_STORAGE)); + } + + contactSources.add(new ContactSource(Constants.StorageType.SMT_PRIVATE, Constants.StorageType.SMT_PRIVATE, Constants.StorageType.PHONE_STORAGE_PRIVATE)); + + return Collections.unmodifiableSet(contactSources); + } + + @Nullable + private ContactSource getContactSource(Account account) { + if (isSyncable(account)) { + return new ContactSource(account.name, account.type, getAccountPublicName(account.type, account.name)); + } else { + return null; + } + } + + private Set getContentResolverAccounts() { + final Uri[] uris = {Groups.CONTENT_URI, Settings.CONTENT_URI, RawContacts.CONTENT_URI}; + final Set sources = new HashSet<>(); + for (Uri uri : uris) { + sources.addAll(fillSourcesFromUri(uri)); + } + return Collections.unmodifiableSet(sources); + } + + private Set fillSourcesFromUri(Uri uri) { + final Set sources = new HashSet<>(); + final String[] projection = {RawContacts.ACCOUNT_NAME, RawContacts.ACCOUNT_TYPE}; + ContextUtils.query(context, uri, projection, cursor -> { + final String name = CursorUtils.getString(cursor, RawContacts.ACCOUNT_NAME); + final String type = CursorUtils.getString(cursor, RawContacts.ACCOUNT_TYPE); + final ContactSource source = new ContactSource(name, type, getAccountPublicName(type, name)); + sources.add(source); + }); + return Collections.unmodifiableSet(sources); + } + + private boolean isSyncable(Account account) { + return ContentResolver.getIsSyncable(account, ContactsContract.AUTHORITY) == 1; + } + + private String getAccountPublicName(String type, String name) { + switch (type) { + case (Constants.Packages.GOOGLE): + return Constants.StorageType.GOOGLE; + case (Constants.Packages.TELEGRAM): + return Constants.StorageType.TELEGRAM; + case (Constants.Packages.SIGNAL): + return Constants.StorageType.SIGNAL; + case (Constants.Packages.WHATSAPP): + return Constants.StorageType.WHATSAPP; + case (Constants.Packages.VIBER): + return Constants.StorageType.VIBER; + case (Constants.Packages.THREEMA): + return Constants.StorageType.THREEMA; + default: + return name; + } + } + + private void initializeLocalPhoneAccount() { + try { + final ContentProviderOperation.Builder builder = ContentProviderOperation.newInsert(RawContacts.CONTENT_URI); + builder.withValue(RawContacts.ACCOUNT_NAME, null); + builder.withValue(RawContacts.ACCOUNT_TYPE, null); + + final ArrayList operations = new ArrayList<>(); + operations.add(builder.build()); + + final ContentProviderResult[] results = context.getContentResolver().applyBatch(ContactsContract.AUTHORITY, operations); + final Optional rawContactUri = Arrays.stream(results).findFirst(); + rawContactUri.ifPresent(value -> context.getContentResolver().delete(value.uri, null, null)); + } catch (Exception ignored) { + + } + } + + private boolean isNotEmpty(ContactSource source) { + return !TextUtils.isEmpty(source.getName()) && !TextUtils.isEmpty(source.getType()); + } + + private boolean containsRegularPhoneSource(Set contactSources) { + return contactSources.stream().anyMatch(account -> { + final String accountType = account.getType(); + return accountType.startsWith("com.google") || accountType.startsWith("com.android") || accountType.startsWith("com.qualcomm"); + }); + } +} diff --git a/app/src/main/java/ru/yandex/practicum/contacts/ui/widget/ContactTypeImageView.java b/app/src/main/java/ru/yandex/practicum/contacts/ui/widget/ContactTypeImageView.java new file mode 100644 index 000000000..060025f1e --- /dev/null +++ b/app/src/main/java/ru/yandex/practicum/contacts/ui/widget/ContactTypeImageView.java @@ -0,0 +1,35 @@ +package ru.yandex.practicum.contacts.ui.widget; + +import android.content.Context; +import android.util.AttributeSet; +import android.widget.ImageView; + +import androidx.annotation.NonNull; + +import ru.yandex.practicum.contacts.model.ContactType; +import ru.yandex.practicum.contacts.utils.model.ContactTypeUtils; + +public class ContactTypeImageView extends StackImageView { + + public ContactTypeImageView(Context context) { + super(context); + } + + public ContactTypeImageView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public ContactTypeImageView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + } + + public ContactTypeImageView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + } + + @Override + public void loadItem(ContactType item, @NonNull ImageView icon) { + int iconRes = ContactTypeUtils.getIconRes(item); + icon.setImageResource(iconRes); + } +} diff --git a/app/src/main/java/ru/yandex/practicum/contacts/ui/widget/DividerItemDecoration.java b/app/src/main/java/ru/yandex/practicum/contacts/ui/widget/DividerItemDecoration.java new file mode 100644 index 000000000..47d43e311 --- /dev/null +++ b/app/src/main/java/ru/yandex/practicum/contacts/ui/widget/DividerItemDecoration.java @@ -0,0 +1,133 @@ +package ru.yandex.practicum.contacts.ui.widget; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Rect; +import android.graphics.drawable.Drawable; +import android.view.View; +import android.widget.LinearLayout; + +import androidx.annotation.DrawableRes; +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import ru.yandex.practicum.contacts.utils.android.ContextUtils; + + +public class DividerItemDecoration extends RecyclerView.ItemDecoration { + + public static final int HORIZONTAL = LinearLayout.HORIZONTAL; + public static final int VERTICAL = LinearLayout.VERTICAL; + + @NonNull + private final Drawable dividerFirst; + @NonNull + private final Drawable dividerOther; + + private int mOrientation; + + private final Rect mBounds = new Rect(); + + public DividerItemDecoration(@NonNull Context context, @DrawableRes int dividerFirst, @DrawableRes int dividerOther, int orientation) { + this.dividerFirst = ContextUtils.requireDrawable(context, dividerFirst); + this.dividerOther = ContextUtils.requireDrawable(context, dividerOther); + setOrientation(orientation); + } + + public DividerItemDecoration(@NonNull Context context, @DrawableRes int divider, int orientation) { + this(context, divider, divider, orientation); + } + + public void setOrientation(int orientation) { + if (orientation != HORIZONTAL && orientation != VERTICAL) { + throw new IllegalArgumentException("Invalid orientation. It should be either HORIZONTAL or VERTICAL"); + } + mOrientation = orientation; + } + + @Override + public void onDraw(@NonNull Canvas c, RecyclerView parent, @NonNull RecyclerView.State state) { + if (parent.getLayoutManager() == null) { + return; + } + if (mOrientation == VERTICAL) { + drawVertical(c, parent); + } else { + drawHorizontal(c, parent); + } + } + + private void drawVertical(Canvas canvas, RecyclerView parent) { + canvas.save(); + final int left; + final int right; + //noinspection AndroidLintNewApi - NewApi lint fails to handle overrides. + if (parent.getClipToPadding()) { + left = parent.getPaddingLeft(); + right = parent.getWidth() - parent.getPaddingRight(); + canvas.clipRect(left, parent.getPaddingTop(), right, + parent.getHeight() - parent.getPaddingBottom()); + } else { + left = 0; + right = parent.getWidth(); + } + + final int childCount = parent.getChildCount(); + for (int i = 0; i < childCount; i++) { + final View child = parent.getChildAt(i); + parent.getDecoratedBoundsWithMargins(child, mBounds); + final int bottom = mBounds.bottom + Math.round(child.getTranslationY()); + final int top = bottom - getDivider(i).getIntrinsicHeight(); + getDivider(i).setBounds(left, top, right, bottom); + getDivider(i).draw(canvas); + } + canvas.restore(); + } + + private void drawHorizontal(Canvas canvas, RecyclerView parent) { + canvas.save(); + final int top; + final int bottom; + //noinspection AndroidLintNewApi - NewApi lint fails to handle overrides. + if (parent.getClipToPadding()) { + top = parent.getPaddingTop(); + bottom = parent.getHeight() - parent.getPaddingBottom(); + canvas.clipRect(parent.getPaddingLeft(), top, + parent.getWidth() - parent.getPaddingRight(), bottom); + } else { + top = 0; + bottom = parent.getHeight(); + } + + final int childCount = parent.getChildCount(); + for (int i = 0; i < childCount; i++) { + final View child = parent.getChildAt(i); + parent.getDecoratedBoundsWithMargins(child, mBounds); + final int right = mBounds.right + Math.round(child.getTranslationX()); + final int left = right - getDivider(i).getIntrinsicWidth(); + getDivider(i).setBounds(left, top, right, bottom); + getDivider(i).draw(canvas); + } + canvas.restore(); + } + + @Override + public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, + @NonNull RecyclerView.State state) { + final int position = ((RecyclerView.LayoutParams) view.getLayoutParams()).getViewAdapterPosition(); + if (mOrientation == VERTICAL) { + outRect.set(0, 0, 0, getDivider(position).getIntrinsicHeight()); + } else { + outRect.set(0, 0, getDivider(position).getIntrinsicWidth(), 0); + } + } + + @NonNull + private Drawable getDivider(int index) { + if (index == 0) { + return dividerFirst; + } else { + return dividerOther; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ru/yandex/practicum/contacts/ui/widget/StackImageView.java b/app/src/main/java/ru/yandex/practicum/contacts/ui/widget/StackImageView.java new file mode 100644 index 000000000..ac76a81bb --- /dev/null +++ b/app/src/main/java/ru/yandex/practicum/contacts/ui/widget/StackImageView.java @@ -0,0 +1,113 @@ +package ru.yandex.practicum.contacts.ui.widget; + +import android.content.Context; +import android.content.res.TypedArray; +import android.util.AttributeSet; +import android.widget.FrameLayout; +import android.widget.ImageView; + +import androidx.annotation.NonNull; + +import java.util.List; + +import ru.yandex.practicum.contacts.R; + +public abstract class StackImageView extends FrameLayout { + + protected static final int DEFAULT_COUNT = 3; + protected static final int DEFAULT_ICON_SIZE_RES = R.dimen.stack_image_view_icon_size; + protected static final int DEFAULT_BORDER_SIZE_RES = R.dimen.stack_image_view_icon_border; + protected static final int DEFAULT_ICON_OFFSET_RES = R.dimen.stack_image_view_icon_offset; + + protected int itemsCount; + protected int maxCount; + protected int iconSize; + protected int borderSize; + protected int iconOffset; + + private ImageView[] icons; + + public StackImageView(Context context) { + this(context, null, 0); + } + + public StackImageView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public StackImageView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + applyAttrs(attrs); + init(); + } + + public StackImageView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + applyAttrs(attrs); + init(); + } + + private void applyAttrs(AttributeSet attrs) { + TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.StackImageView); + + maxCount = a.getInteger(R.styleable.StackImageView_iconsCount, DEFAULT_COUNT); + iconSize = a.getDimensionPixelSize(R.styleable.StackImageView_iconItemSize, + getResources().getDimensionPixelSize(DEFAULT_ICON_SIZE_RES)); + borderSize = a.getDimensionPixelSize(R.styleable.StackImageView_borderSize, + getResources().getDimensionPixelSize(DEFAULT_BORDER_SIZE_RES)); + iconOffset = a.getDimensionPixelSize(R.styleable.StackImageView_iconOffset, + getResources().getDimensionPixelSize(DEFAULT_ICON_OFFSET_RES)); + + a.recycle(); + + if (maxCount <= 0) { + throw new IllegalStateException("icons_count must be greater than zero"); + } + } + + private void init() { + icons = new ImageView[maxCount]; + for (int i = maxCount - 1; i >= 0; i--) { + ImageView view = new ImageView(getContext()); + + FrameLayout.LayoutParams layoutParams = new LayoutParams(iconSize, iconSize); + layoutParams.leftMargin = iconOffset * i; + view.setLayoutParams(layoutParams); + + view.setVisibility(GONE); + view.setBackgroundResource(R.drawable.bg_image_view_borger); + + addView(view); + icons[i] = view; + } + } + + public void setData(List items) { + itemsCount = items.size(); + int size = Math.min(maxCount, itemsCount); + + for (int i = 0; i < size; i++) { + ImageView icon = icons[i]; + icon.setVisibility(VISIBLE); + + T item = items.get(i); + if (i < size - 1 || size == itemsCount) { + loadItem(item, icon); + } else { + loadLastItem(item, icon); + } + } + + if (size < maxCount) { + for (int i = size; i < maxCount; i++) { + icons[i].setVisibility(GONE); + } + } + } + + public abstract void loadItem(T item, @NonNull ImageView icon); + + public void loadLastItem(T item, @NonNull ImageView icon) { + loadItem(item, icon); + } +} diff --git a/app/src/main/java/ru/yandex/practicum/contacts/utils/Constants.java b/app/src/main/java/ru/yandex/practicum/contacts/utils/Constants.java new file mode 100644 index 000000000..dce4e20f5 --- /dev/null +++ b/app/src/main/java/ru/yandex/practicum/contacts/utils/Constants.java @@ -0,0 +1,28 @@ +package ru.yandex.practicum.contacts.utils; + +public class Constants { + + public static class Packages { + public static final String GOOGLE = "com.google"; + public static final String TELEGRAM = "org.telegram.messenger"; + public static final String SIGNAL = "org.thoughtcrime.securesms"; + public static final String WHATSAPP = "com.whatsapp"; + public static final String VIBER = "com.viber.voip"; + public static final String THREEMA = "ch.threema.app"; + } + + public static class StorageType{ + public static final String SMT_PRIVATE = "smt_private" ; + public static final String GOOGLE = "Google"; + public static final String TELEGRAM = "Telegram"; + public static final String SIGNAL = "Signal"; + public static final String WHATSAPP = "WhatsApp"; + public static final String VIBER = "Viber"; + public static final String THREEMA = "Threema"; + public static final String PHONE_STORAGE = "Phone storage"; + public static final String PHONE_STORAGE_PRIVATE = "Phone storage (hidden)"; + } + + + +} diff --git a/app/src/main/java/ru/yandex/practicum/contacts/utils/android/ContextUtils.java b/app/src/main/java/ru/yandex/practicum/contacts/utils/android/ContextUtils.java new file mode 100644 index 000000000..1e06c990b --- /dev/null +++ b/app/src/main/java/ru/yandex/practicum/contacts/utils/android/ContextUtils.java @@ -0,0 +1,55 @@ +package ru.yandex.practicum.contacts.utils.android; + +import android.Manifest; +import android.content.Context; +import android.content.pm.PackageManager; +import android.database.Cursor; +import android.graphics.drawable.Drawable; +import android.net.Uri; + +import androidx.annotation.DrawableRes; +import androidx.annotation.NonNull; +import androidx.core.content.ContextCompat; + +import java.util.Objects; +import java.util.function.Consumer; + +public class ContextUtils { + + public static boolean hasContactPermissions(Context context) { + return hasPermission(context, Manifest.permission.READ_CONTACTS); + } + + private static boolean hasPermission(Context context, String permission) { + return ContextCompat.checkSelfPermission(context, permission) == PackageManager.PERMISSION_GRANTED; + } + + public static void query(Context context, Uri uri, String[] projection, Consumer callback) { + try (Cursor cursor = context.getContentResolver().query(uri, projection, null, null, null)) { + if (cursor.moveToFirst()) { + do { + callback.accept(cursor); + } while (cursor.moveToNext()); + } + } catch (Exception ignored) { + + } + } + + public static void query(Context context, Uri uri, String[] projection, String selection, String[] selectionArgs, Consumer callback) { + try (Cursor cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs, null)) { + if (cursor.moveToFirst()) { + do { + callback.accept(cursor); + } while (cursor.moveToNext()); + } + } catch (Exception ignored) { + + } + } + + @NonNull + public static Drawable requireDrawable(Context context, @DrawableRes int id) { + return Objects.requireNonNull(ContextCompat.getDrawable(context, id)); + } +} diff --git a/app/src/main/java/ru/yandex/practicum/contacts/utils/android/CursorUtils.java b/app/src/main/java/ru/yandex/practicum/contacts/utils/android/CursorUtils.java new file mode 100644 index 000000000..5130e21f6 --- /dev/null +++ b/app/src/main/java/ru/yandex/practicum/contacts/utils/android/CursorUtils.java @@ -0,0 +1,25 @@ +package ru.yandex.practicum.contacts.utils.android; + +import android.database.Cursor; + +public class CursorUtils { + + public static String getString(Cursor cursor, String key) { + final int index = cursor.getColumnIndex(key); + if (index >= 0) { + final String string = cursor.getString(index); + return string != null ? string : ""; + } else { + return ""; + } + } + + public static int getInteger(Cursor cursor, String key) { + final int index = cursor.getColumnIndex(key); + if (index >= 0) { + return cursor.getInt(index); + } else { + return -1; + } + } +} diff --git a/app/src/main/java/ru/yandex/practicum/contacts/utils/android/Debouncer.java b/app/src/main/java/ru/yandex/practicum/contacts/utils/android/Debouncer.java new file mode 100644 index 000000000..58e096571 --- /dev/null +++ b/app/src/main/java/ru/yandex/practicum/contacts/utils/android/Debouncer.java @@ -0,0 +1,42 @@ +package ru.yandex.practicum.contacts.utils.android; + +import android.os.Handler; +import android.os.Looper; +import android.os.Message; + +import androidx.annotation.NonNull; + +public class Debouncer { + + private static final int MESSAGE_ID = 1; + private static final int DELAY = 500; + + private final OnValueUpdateListener listener; + + public Debouncer(OnValueUpdateListener listener) { + this.listener = listener; + } + + @SuppressWarnings("unchecked") + private final Handler handler = new Handler(Looper.getMainLooper()) { + @Override + public void handleMessage(@NonNull Message message) { + if (message.what == MESSAGE_ID) { + listener.onValueUpdate((T) message.obj); + return; + } + super.handleMessage(message); + } + }; + + public void updateValue(T value) { + final Message message = Message.obtain(handler, MESSAGE_ID, value); + handler.removeMessages(MESSAGE_ID); + handler.sendMessageDelayed(message, DELAY); + } + + @FunctionalInterface + public interface OnValueUpdateListener { + void onValueUpdate(T value); + } +} diff --git a/app/src/main/java/ru/yandex/practicum/contacts/utils/java/ThreadUtils.java b/app/src/main/java/ru/yandex/practicum/contacts/utils/java/ThreadUtils.java new file mode 100644 index 000000000..e508ac48d --- /dev/null +++ b/app/src/main/java/ru/yandex/practicum/contacts/utils/java/ThreadUtils.java @@ -0,0 +1,25 @@ +package ru.yandex.practicum.contacts.utils.java; + +import android.os.Handler; +import android.os.Looper; + +import java.util.function.Consumer; +import java.util.function.Supplier; + +public class ThreadUtils { + + public static void runAsync(Supplier supplier, Consumer consumer) { + final Handler handler = new Handler(Looper.getMainLooper()); + new Thread() { + @Override + public void run() { + final T value = supplier.get(); + handler.post(() -> consumer.accept(value)); + } + }.start(); + } + + public static void runAsync(Runnable runnable) { + new Thread(runnable).start(); + } +} diff --git a/app/src/main/java/ru/yandex/practicum/contacts/utils/model/ContactTypeUtils.java b/app/src/main/java/ru/yandex/practicum/contacts/utils/model/ContactTypeUtils.java new file mode 100644 index 000000000..d9dc22743 --- /dev/null +++ b/app/src/main/java/ru/yandex/practicum/contacts/utils/model/ContactTypeUtils.java @@ -0,0 +1,74 @@ +package ru.yandex.practicum.contacts.utils.model; + +import androidx.annotation.DrawableRes; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import ru.yandex.practicum.contacts.R; +import ru.yandex.practicum.contacts.model.ContactType; +import ru.yandex.practicum.contacts.presentation.filter.model.FilterContactType; +import ru.yandex.practicum.contacts.utils.Constants; + +public class ContactTypeUtils { + + @DrawableRes + public static int getIconRes(@NonNull ContactType type) { + switch (type) { + case TELEGRAM: + return R.drawable.ic_type_telegram; + case WHATS_APP: + return R.drawable.ic_type_whatsapp; + case VIBER: + return R.drawable.ic_type_viber; + case SIGNAL: + return R.drawable.ic_type_signal; + case THREEMA: + return R.drawable.ic_type_threema; + case PHONE: + return R.drawable.ic_type_phone; + case EMAIL: + return R.drawable.ic_type_email; + default: + throw new IllegalArgumentException("Not supported type of ContactType"); + } + } + + public static FilterContactType toFilterContactType(ContactType type) { + switch (type) { + case TELEGRAM: + return FilterContactType.TELEGRAM; + case WHATS_APP: + return FilterContactType.WHATS_APP; + case VIBER: + return FilterContactType.VIBER; + case SIGNAL: + return FilterContactType.SIGNAL; + case THREEMA: + return FilterContactType.THREEMA; + case PHONE: + return FilterContactType.PHONE; + case EMAIL: + return FilterContactType.EMAIL; + default: + throw new IllegalArgumentException("Not supported ContactType"); + } + } + + @Nullable + public static ContactType parse(String value) { + switch (value) { + case Constants.StorageType.TELEGRAM: + return ContactType.TELEGRAM; + case Constants.StorageType.WHATSAPP: + return ContactType.WHATS_APP; + case Constants.StorageType.VIBER: + return ContactType.VIBER; + case Constants.StorageType.SIGNAL: + return ContactType.SIGNAL; + case Constants.StorageType.THREEMA: + return ContactType.THREEMA; + default: + return null; + } + } +} diff --git a/app/src/main/java/ru/yandex/practicum/contacts/utils/model/ContactUtils.java b/app/src/main/java/ru/yandex/practicum/contacts/utils/model/ContactUtils.java new file mode 100644 index 000000000..5f6e3f28f --- /dev/null +++ b/app/src/main/java/ru/yandex/practicum/contacts/utils/model/ContactUtils.java @@ -0,0 +1,42 @@ +package ru.yandex.practicum.contacts.utils.model; + +import android.text.TextUtils; + +import ru.yandex.practicum.contacts.model.Contact; + +public class ContactUtils { + + public static String getDisplayName(Contact contact) { + final String fullName = getFullName(contact); + if (!TextUtils.isEmpty(fullName)) { + return fullName; + } + final String phone = PhoneUtils.format(getFirstPhone(contact)); + if (!TextUtils.isEmpty(phone)) { + return phone; + } + final String email = getFirstEmail(contact); + if (!TextUtils.isEmpty(email)) { + return email; + } + return ""; + } + + public static String getFullName(Contact contact) { + final String firstMiddle = (contact.getFirstName() + " " + contact.getMiddleName()).trim(); + final String suffixComma = TextUtils.isEmpty(contact.getSuffix()) ? "" : ", " + contact.getSuffix(); + return (contact.getPrefix() + " " + contact.getSurname() + " " + firstMiddle + suffixComma).trim(); + } + + public static String getFirstPhone(Contact contact) { + return !contact.getPhoneNumbers().isEmpty() ? contact.getPhoneNumbers().get(0).getValue().trim() : ""; + } + + public static String getFirstNormalizedPhone(Contact contact) { + return !contact.getPhoneNumbers().isEmpty() ? contact.getPhoneNumbers().get(0).getNormalizedNumber().trim() : ""; + } + + public static String getFirstEmail(Contact contact) { + return !contact.getEmails().isEmpty() ? contact.getEmails().get(0).getValue().trim() : ""; + } +} diff --git a/app/src/main/java/ru/yandex/practicum/contacts/utils/model/FilterContactTypeUtils.java b/app/src/main/java/ru/yandex/practicum/contacts/utils/model/FilterContactTypeUtils.java new file mode 100644 index 000000000..030af2824 --- /dev/null +++ b/app/src/main/java/ru/yandex/practicum/contacts/utils/model/FilterContactTypeUtils.java @@ -0,0 +1,57 @@ +package ru.yandex.practicum.contacts.utils.model; + +import androidx.annotation.NonNull; +import androidx.annotation.StringRes; + +import ru.yandex.practicum.contacts.R; +import ru.yandex.practicum.contacts.model.ContactType; +import ru.yandex.practicum.contacts.presentation.filter.model.FilterContactType; + +public class FilterContactTypeUtils { + + @StringRes + public static int getStringRes(FilterContactType contactType) { + switch (contactType) { + case ALL: + return R.string.filter_contact_type_all; + case TELEGRAM: + return R.string.filter_contact_type_telegram; + case WHATS_APP: + return R.string.filter_contact_type_whatsapp; + case VIBER: + return R.string.filter_contact_type_viber; + case SIGNAL: + return R.string.filter_contact_type_signal; + case THREEMA: + return R.string.filter_contact_type_threema; + case PHONE: + return R.string.filter_contact_type_phone; + case EMAIL: + return R.string.filter_contact_type_email; + default: + throw new IllegalArgumentException("Not supported SortType"); + } + } + + @NonNull + public static ContactType toContactType(FilterContactType type) { + switch (type) { + case TELEGRAM: + return ContactType.TELEGRAM; + case WHATS_APP: + return ContactType.WHATS_APP; + case VIBER: + return ContactType.VIBER; + case SIGNAL: + return ContactType.SIGNAL; + case THREEMA: + return ContactType.THREEMA; + case PHONE: + return ContactType.PHONE; + case EMAIL: + return ContactType.EMAIL; + default: + throw new IllegalArgumentException("Not supported FilterContactType"); + } + } +} diff --git a/app/src/main/java/ru/yandex/practicum/contacts/utils/model/MergedContactUtils.java b/app/src/main/java/ru/yandex/practicum/contacts/utils/model/MergedContactUtils.java new file mode 100644 index 000000000..d1178df4b --- /dev/null +++ b/app/src/main/java/ru/yandex/practicum/contacts/utils/model/MergedContactUtils.java @@ -0,0 +1,52 @@ +package ru.yandex.practicum.contacts.utils.model; + +import android.text.TextUtils; + +import java.util.Collections; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +import ru.yandex.practicum.contacts.model.ContactType; +import ru.yandex.practicum.contacts.model.MergedContact; + +public class MergedContactUtils { + + public static boolean contains(MergedContact contact, String query) { + final String lowerCaseQuery = query.toLowerCase(); + if (TextUtils.isEmpty(lowerCaseQuery)) { + return true; + } + return contact.getFirstName().toLowerCase().contains(lowerCaseQuery) || + contact.getMiddleName().toLowerCase().contains(lowerCaseQuery) || + contact.getSurname().toLowerCase().contains(lowerCaseQuery) || + contact.getNormalizedNumber().toLowerCase().contains(lowerCaseQuery) || + contact.getPhone().toLowerCase().contains(lowerCaseQuery) || + contact.getEmail().toLowerCase().contains(lowerCaseQuery); + } + + public static boolean contains(MergedContact contact, Set types) { + if (types.isEmpty()) { + return true; + } + final List contactTypes = getContactTypes(contact); + return !Collections.disjoint(contactTypes, types); + } + + public static List getContactTypes(MergedContact contact) { + final List allTypes = contact.getContactTypes().stream() + .map(ContactTypeUtils::parse) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + if (!TextUtils.isEmpty(contact.getPhone())) { + allTypes.add(ContactType.PHONE); + } + if (!TextUtils.isEmpty(contact.getEmail())) { + allTypes.add(ContactType.EMAIL); + } + Collections.sort(allTypes); + + return Collections.unmodifiableList(allTypes); + } +} diff --git a/app/src/main/java/ru/yandex/practicum/contacts/utils/model/PhoneUtils.java b/app/src/main/java/ru/yandex/practicum/contacts/utils/model/PhoneUtils.java new file mode 100644 index 000000000..508677b09 --- /dev/null +++ b/app/src/main/java/ru/yandex/practicum/contacts/utils/model/PhoneUtils.java @@ -0,0 +1,14 @@ +package ru.yandex.practicum.contacts.utils.model; + +import android.telephony.PhoneNumberUtils; +import android.text.TextUtils; + +import java.util.Locale; + +public class PhoneUtils { + + public static String format(String phone) { + final String formattedPhone = PhoneNumberUtils.formatNumber(phone, Locale.getDefault().getISO3Country()); + return TextUtils.isEmpty(formattedPhone) ? phone : formattedPhone; + } +} diff --git a/app/src/main/java/ru/yandex/practicum/contacts/utils/widget/EditTextUtils.java b/app/src/main/java/ru/yandex/practicum/contacts/utils/widget/EditTextUtils.java new file mode 100644 index 000000000..5a644e7fa --- /dev/null +++ b/app/src/main/java/ru/yandex/practicum/contacts/utils/widget/EditTextUtils.java @@ -0,0 +1,49 @@ +package ru.yandex.practicum.contacts.utils.widget; + +import android.text.Editable; +import android.text.TextWatcher; +import android.widget.EditText; + +import java.util.function.Consumer; + +import ru.yandex.practicum.contacts.utils.android.Debouncer; + +public class EditTextUtils { + + public static void debounce(EditText editText, Debouncer.OnValueUpdateListener listener) { + final Debouncer debouncer = new Debouncer<>(listener); + editText.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + } + + @Override + public void afterTextChanged(Editable s) { + debouncer.updateValue(s); + } + }); + } + + public static void addTextListener(EditText editText, Consumer consumer) { + editText.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + + } + + @Override + public void afterTextChanged(Editable s) { + consumer.accept(s); + } + }); + } +} diff --git a/app/src/main/res/color/text_button_blue.xml b/app/src/main/res/color/text_button_blue.xml new file mode 100644 index 000000000..53b750062 --- /dev/null +++ b/app/src/main/res/color/text_button_blue.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/color/text_button_white.xml b/app/src/main/res/color/text_button_white.xml new file mode 100644 index 000000000..427ba803d --- /dev/null +++ b/app/src/main/res/color/text_button_white.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/bg_button_blue.xml b/app/src/main/res/drawable/bg_button_blue.xml new file mode 100644 index 000000000..7655fae8f --- /dev/null +++ b/app/src/main/res/drawable/bg_button_blue.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_button_white.xml b/app/src/main/res/drawable/bg_button_white.xml new file mode 100644 index 000000000..145cd99c4 --- /dev/null +++ b/app/src/main/res/drawable/bg_button_white.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_image_view_borger.xml b/app/src/main/res/drawable/bg_image_view_borger.xml new file mode 100644 index 000000000..8487a64d4 --- /dev/null +++ b/app/src/main/res/drawable/bg_image_view_borger.xml @@ -0,0 +1,13 @@ + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_search_box.xml b/app/src/main/res/drawable/bg_search_box.xml new file mode 100644 index 000000000..5f66560ff --- /dev/null +++ b/app/src/main/res/drawable/bg_search_box.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/bg_splash.xml b/app/src/main/res/drawable/bg_splash.xml new file mode 100644 index 000000000..e565e5259 --- /dev/null +++ b/app/src/main/res/drawable/bg_splash.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_avatar.xml b/app/src/main/res/drawable/ic_avatar.xml new file mode 100644 index 000000000..92961b4fa --- /dev/null +++ b/app/src/main/res/drawable/ic_avatar.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_close.xml b/app/src/main/res/drawable/ic_close.xml new file mode 100644 index 000000000..c231419c4 --- /dev/null +++ b/app/src/main/res/drawable/ic_close.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_done.xml b/app/src/main/res/drawable/ic_done.xml new file mode 100644 index 000000000..a7db8de6a --- /dev/null +++ b/app/src/main/res/drawable/ic_done.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 000000000..14482c7ab --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,12 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 000000000..a99df9103 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_menu_filter.xml b/app/src/main/res/drawable/ic_menu_filter.xml new file mode 100644 index 000000000..235aef9df --- /dev/null +++ b/app/src/main/res/drawable/ic_menu_filter.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_menu_search.xml b/app/src/main/res/drawable/ic_menu_search.xml new file mode 100644 index 000000000..c75a5e7a3 --- /dev/null +++ b/app/src/main/res/drawable/ic_menu_search.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_menu_sort.xml b/app/src/main/res/drawable/ic_menu_sort.xml new file mode 100644 index 000000000..bf0699c88 --- /dev/null +++ b/app/src/main/res/drawable/ic_menu_sort.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_type_email.xml b/app/src/main/res/drawable/ic_type_email.xml new file mode 100644 index 000000000..f8dd390ba --- /dev/null +++ b/app/src/main/res/drawable/ic_type_email.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_type_phone.xml b/app/src/main/res/drawable/ic_type_phone.xml new file mode 100644 index 000000000..434b0dbe8 --- /dev/null +++ b/app/src/main/res/drawable/ic_type_phone.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_type_signal.xml b/app/src/main/res/drawable/ic_type_signal.xml new file mode 100644 index 000000000..4cd687267 --- /dev/null +++ b/app/src/main/res/drawable/ic_type_signal.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/ic_type_telegram.xml b/app/src/main/res/drawable/ic_type_telegram.xml new file mode 100644 index 000000000..0b977f18a --- /dev/null +++ b/app/src/main/res/drawable/ic_type_telegram.xml @@ -0,0 +1,18 @@ + + + + + + diff --git a/app/src/main/res/drawable/ic_type_threema.xml b/app/src/main/res/drawable/ic_type_threema.xml new file mode 100644 index 000000000..6ade9526e --- /dev/null +++ b/app/src/main/res/drawable/ic_type_threema.xml @@ -0,0 +1,21 @@ + + + + + + + diff --git a/app/src/main/res/drawable/ic_type_viber.xml b/app/src/main/res/drawable/ic_type_viber.xml new file mode 100644 index 000000000..27383dd8a --- /dev/null +++ b/app/src/main/res/drawable/ic_type_viber.xml @@ -0,0 +1,30 @@ + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_type_whatsapp.xml b/app/src/main/res/drawable/ic_type_whatsapp.xml new file mode 100644 index 000000000..bc2463481 --- /dev/null +++ b/app/src/main/res/drawable/ic_type_whatsapp.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable/item_decoration_16dp.xml b/app/src/main/res/drawable/item_decoration_16dp.xml new file mode 100644 index 000000000..ccb568058 --- /dev/null +++ b/app/src/main/res/drawable/item_decoration_16dp.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/item_decoration_72dp.xml b/app/src/main/res/drawable/item_decoration_72dp.xml new file mode 100644 index 000000000..c3aed1096 --- /dev/null +++ b/app/src/main/res/drawable/item_decoration_72dp.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/font/ys_display_medium.ttf b/app/src/main/res/font/ys_display_medium.ttf new file mode 100644 index 0000000000000000000000000000000000000000..860c389dff9407172b3432fbb34dd973ed28886f GIT binary patch literal 131604 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/font/ys_display_regular.ttf b/app/src/main/res/font/ys_display_regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..4ac0cc71e520c71eaebb919f70332bc99ec34a76 GIT binary patch literal 129484 zcmcG%2Vh*q)h|ACcUP;r(n{K*wnZyxS1YYn(n?xs)w`@>6hN$ zPj4`m`f2|>Z~3zMi!W!4UytjTt~htu=G`Z*?q)10mNCt1D=yq><5%hm7&E?%-vg^R zuQ@m7yEpl9@0+-1%bI0dHZv{$<4bWpOI)+zqScAX-^^qz`v~K%m)EXZwo-TFC%?tD z+4#L`EgG~3V_v}de4OX4J$LK2{R!Z(oW5`!;XdvQ_h{<_pH>96=wqZa#n2=6_A@`h>Cc0mh{5 zj9|lA%QGMEOj?p$_YW4QdJiq^_0B&fQ~k@n7yb~ub=slnR^5!#I3`gm{^35=ZPSV9 z!*cxoElsz0lKh+aD?Lvw_`4FLKZgOLqB$OI^j;OtRZouqu*lX z?8~S}vD=vBMRym5+FJYj`dAaQ1*fGCr(;;T>NfVNjj`|0@BE+A6R2z~k-dTXS(Lx9 zI^N03rJXEC%0>MSi<5rEa@a9e&Kp=WN;it;SZ?_nM6J=VcTm`)l;n~`;>Dp22#@(zBlQ|jfYU3ga+OP8*|yDwlG z)uk+6nm7F~X%3!W#ccc=Y?%6tI-TFn()jA>5BP4ji~kkRsRj&QMPI6!g@1)IjPaJ> zJcHG-mstsWZTboRD;)of)v4N9os@~<5ZBjfdJ>{7T&a9veXIk?L7~81^sYIX1v=m|3N4yn8p`GRkuJJuI6)gWmvmalVID^6%ii z1i#&E1ICrZp2R!%ur2)Oco(h(Ol<<51k=-s>Ta|Vj0wI`g}pcZ9>Ey(A5q>7!etMnI*5CKU%IFUZDfIh0-~dG`0-Y4&-hWKL%)i9_gx_T>lP_mkJewI+ z-(_yq&qVP`4}m7MxQFPm4)ktfW*Q68o@AQ-jQwu<3!-77BSEjA8N$7E|MZmfAg(V$ z0ewm-fcbqGQ#5`FuhAtmkD~A_a87e7y8LkZ6M2qJ{}1|J5Bz_EIsx^`=s8F8P81Vr zntP!VO&>G|LxtvIbfI|}U7~3y>X_zibP=#p=C6QBG_4VBMV0W}jiNQ8IWdoEE{96S z^xx!pJ^gzzw_j#4pu;JYpRu1r<~hxEltR8%neQ~`L-SstQ%>_rTJgYO(<_W$eG zEdURs@_+jN(Dl37e$3^YiMI(^)ymwM&u+|TcX*Bx@1xSn+E9#w*At%y&n14#2f_DI zXnv{=DhDY3Nz1ec(xc;Ph2}{DYLc%%20Wj_d?23pAdVp$B_G;@&^bN;KiA;8Lfm@*ZLgxd zj_1CP`uCtun#UQ_AJV(Qr!6$c4cm~bO0_Y?Kz-2M| zS3LbbY7gp9sDEhhp@OzCM!Hfl8^w8{(60(uC5|gej)R}j{7j*1QM!regpN@cqEtxz z;Pvtx9#-D*IbdX@XG0q`E7!TiJNlus6=OU!*BvOWpkK&5yo;$0qFt%E7c%tsD9{N( z8;t!5C8R%g;rK=I3+m-Vm>SGw9N-5d0Hm;5r zdN_;`rUu7l>}s|PvO>ppvhTC|*<+r7X!W9hZJ0y`@sC zVpOrJM0KLNUp=8-qP|Xjqxw$u!|JEhzgEAmxklG$*=PB?E!LJ`OSfg(EVf)*fvwc$ zvjuEzwk5XBwu^07+U~PGX#0U}()P67XwS9Jw=d33&F#uvn!D-`34gHuOA1a;PorNp zm;nEaJ;)ws&#;s19rWjKY>L8O)Z-;djzgfB9YfL0pFt`p54M9ffLs1HXlTAAAP?_`Ur8P`fz)zBpUTma*k*1$&LHWUsUJ zYy&%&ZDha1{M*Dfv-8;bu*$Zut?bv(M1RZPWc%4S+2!mCc7R>U-oi+(0u5fn4uR_4 zVZUSV0_T6@CG2DFVV_`5|DBhzfADhl8TYYIc^UgBuYkn-ANDU^%|3@L`jY!WsWt2i zUd4jE7BmvzjMuYiUdJWg$T@G|THeMryoIZH6OZAoT+JS0AFz*jEN|y=yo1N{PM*l; z@C49%67L2V`2*MS9-hMccrx#0e`O!?G(N!fd@fJr{ebzyJe?16BcI0&e2AO)e4fEa zxEb>9kL*u8n=jy5e4JbO7|-L&xScQNRzATUd?~l_MLd@;;RSpJFXXG)BfN;O<;DCQ z_Gk7U&*Y;#hcA?#m9CP0D4md=;V!<4=kw*<$yahWU&CLL_DcJt{p=^~CFw@#Ch2DB z7U@>0N4ibwm2Q{rknWWFr0?-_`DVU>Z{(Z!dC-S`!CqxC9Q+Tn>J<2Z1RkJ<6&}O1 zESANwcxcCoED2m78T>@gQdt^HX9i|u(C1kO%Vb&L7dfzbt$2bR^4-DmVC@w!C+z1! z=4M5(g*~t-y{wd#!It;23RcOgn4eX%8WsS@u7f7tz#5@-G_w{4orXd7vrg8<=0HaD zuwK^3=CXb^zy{e6o5$v}VK%}>A#29j0`Tz(wumieOE8A*>|%BayA*VEC8*{)b}c)C zx$rIU{qM3H*$wO_b~C$`-2(n`J7&mT>`wMQaFXw{``EqUa1XHOK%YdftJr^mJ`aOp z*Fc_ZVyoFJY=@Xx8^Hrlf|{fMtpyD;c0Id?T>yGthvWZ-WMlAiY-K-YOWB3&B2Y9a zgFVHLvlHw`?1$i`G@l-1k3jak#9m-O6_|J)bK)6T%0FS-z_?l%v%-g~#>WGAj+&l>TX6Y-aXew2@&weP$vcIMG{rr(qpE^| zvB9yUyV|mjHnojs+3mLWqbCN&j-F`CvX77Bs+e$3(c??bF)Mx6qR%mI{2nVmW!~7) zrmUlE{J;Tv=Dab7{phX(2eJ-e9Lo7IcH*=K&Q5P=QW^jWJOhwuKgM?r;9;m7_AF{} z*d2ECbG!}jjw|k&H`b1x+Q&TrJ1Be-Ge^UuB0hPD%$H1()wl-8=OF4%Opbg^F!?qKL_f1dg=tiX$!5m1l&?{P@cJ={ ziqIyvXfQ>h??NAGHfxK}VHu*6=wJf8l1jZ*sV|tsRM(4kuTu9b^--#E-ASeXSgDCI zaNKuDsgEl4Nu~Z+sh^=5@B2up#e48P@gCI4U@r8#SE(D7dXrM0RO%F}MSta5{EqX7 zly-3)&c$`;*8sJn-mKK(I-Gx`oF@a;^xj6L-lWu`9nTT%;`z7^bwB-%`yW#3kCeI} z43VC5jZ*(ysW(wA?w9M2lyfVXr+mF!Pe-5QdU1~W8fq8slWWm_0T2z}2)N~DP*D@m zJptauSp0N5i<`cT#ZF_cNdaiG(B=3)p(#EAtLk-FFh2k#hW>ScKfT9yN~J2Ds$8{B zwM+GinyCZoA@z3kZR(&Vp!s%8Zp_x0kF`6s55{)Hz7~5b_JcTMTvOcYxIe{zm{6DS zU}8+-P~shlA0#bKI-c~d&ZgU}yCFF?N{4B&pqNub#yq^<)!8gFJ+4-XLm(F*cA3DEu z#kf*kM_tEVFSuTFohsZ=xUFz+;i1Bt3hyp_%)Q@zt@{@Dz3#`|&$?f6|Hl16QE$;$ z(aNIDMLUWvFFI0mS8;K1b#YtqK=DNJ+TyD{%=5D6jgrig-jYKlr@STJt);AVe(A>2 z%S%s`epyypcBt&*@`3V0bk{spVzOg z-&((`{$Ty}^>@`Dtv_D>Lj7y?r|Lhb|GYunkkXLd;A*I7Xli(@;Y7pB4R193q2c3( zuNq?;(;ID##f{aCZH)ts6OC&dw>Iu-JlJ@B<6VtM8;>`>(D+*8sm2c)KW|bu^)`() zt!&!dw4>?rrXx+aHQnEItm*ltlTB|nz1Q?MT3%~8)$&2h=dJ42l-BH4S8GLU zQ)_SQSnJBx&8<6HFK-RD#kU#Ta@$JU0&N{_Lv4%O*0o*Ow!7`>wj0{M-}Xq`(`_%d zZ*IS({b>8M?XR`}sr~bg_>Pi}K*!Y`FLoAmKGLP`+BqkFPGHW-?gibOyZ3fq-+h1g zv)#Y!{-B5TWcHNy9PIgVuch~n-p~6s^nExtd+y`?)%{KVUHt?7WBp6}*Y+cuMGTh;O&7w z4SYE8*xM3$w{+f-d8g(Z=XcG&dj9jn=Hc$)yGP1Lc8xqe z^7LrT=;G0JqZf|u9=&?>hS497emQ)?G8M|`lD&wl1tM;$XUVZiI$u;IRwlzoA zX0Khn_T6(z&e?X(tLtp*TxP<=D_Bb&3A0R_dLsaN6wEwKl}W<&i`;r-j=Z~w`_TLYtz=dFG#=O z{0knt;L{7+EaF+G~$r`@waY*Dbj2z;%yb_ri4_ zUia0Jf+N*OI*zOcE0$Q@^q1^GaA*~TVE-fqCz)QKS_7Rb1)ADPQZw4epr3fNp2Rh# zhH<4qO$}-m*Dy{x2y}OG6Zg`MPeR_SJ}oUvQ#n*=xdoLKRlahgAwA~yAH<|5rv&qh2VVCodxB-2EHv*D_?0{>~M{}qToLoDFOZ0o<#A|;G|EXsNyUEBZ?Jg z33#XuEjnD6h~pGGMxBZ}6>YINw&2)+qQ$WlwH56~)JD`CZ5cRLq0Yh(@*P@--%;rk z#i#Z8Q49`I9BF=smanZd&E=utqYe0m%IhpG~a+%=Ps_AqrYun+|E2nu;oP>7`lm|KKa4f;j>m&bpM;@3&oVnxh{dok8@ouoB5DoePt z(xUQ}S5;P&NX`-!$irXhGg!FEP{JL#F@|)DWU{FEKdcoUu7=o6coEY7Udg!ruDHs9c~ zgl}9RWyN>sjg3_nSC(EIpHbWe+R;E;`Am9UIsxi6K&h>S6?hlT0GNVeurvb*{ZwZu zb&*n6hbc=>Ak;^})JY9a8K%*5M*vxmXYrGy66={$yd@5#mci5@H9df9#nB{`_0X?n z__a#>>cp>gI4UhOaUV|uFda0t8YEx2Mbf4l1wab`KQ4YMkwU*Y38c!;JNeD+!R6b` zg??MhsHw2pZu1wK|Lo3p6csu0-O_F}+3bFo$>j3eZPkUQ>+{_1yu9M#cn1u&oE#3zL8@ zOldLl7Qlig1J!fG@H7+n%u(=U)PSIWnDxX^LyXLNR5&o>X_GMJ%`8S7^@opA(AqEB za>GaY;X7R6qheS+G6P_MvR6!XGmcutQ4Ege2%_&*t^+aW7C3dFRvFCx5*~uKfv6lh zWdrw{bX;rUz>B}iZ-~+AI8Uec$_m1gN}$AF;DuL5+UxFfy3c88SXgf{Vshl=XIJ)? zmb4ZdEaeUMOR}@<1+ks6CGMPRTcRO0J~j@N9dwrD)bzQ_Dx2pF4wm#<3tan3tK%$L zjzUXSmpi}4V^4JVxg8DOtc;?7EiX`#r@PdWQF4ygShcOfm?7a`qgo}YC0$`-PG(1W zL3vH^;<~P!47>aC^m@RXcnSRb`-N;Mo`k4?dj_x!@e&0skO=e}vOMq`B!(Ak-=}qy z&rhtAo_IliPV@9d+%3HYqX(=(M{b;`=fo2><5@-+y=l~t#*!MwLF&XF8YI6@rveR| z{^)6R>h;b>PrI`wKXuOBZx*fGcws~R#hX_;3s!92Uccki7|D_J>5JHu^cJ!$;&=mN zCwLPQO+x*SybltDBj&e@FUJ^lY$*vcIGdvSrm#mbMp(BjS&WfJLBffNGh*|^B^YW6 zGh2<{c=(ri{6hRA3Cuptsdf6DelRVq-=sB7H#XIc?lfMgo1?qHv~z^-+PJZJNoGgo zWtUZUWWuS$!8^zKS5hU6<=RO=67Dd$Z!usBDy9L;V0b*q5(%{g`4WTU5dWs*IJ}mF z=R(AQ#rh09F!1cN1J82jE3Y)+f4svzeJA`<>jCi6N&K0K8x5fXd{W>BuV8qE2D2eS zh<8(iN)Wy*-9h&5xRj(=Db19VkztM3r^hdf%g8jxsdLgyHhWH*tH2sB(}M?|Pb2Ta z7)&&-2wh=ZNXsG30aDQO{`(!5%xe#fz%FDl*+L>!)_;a zBgSqb#RGZ<c2AYFB%O#p3I5x;ju-w8wiE>>ppSZ@jp8Y~T3! zHy4yNTiZ7c4sL3uTb6V<6u+1dF-3SgzxSh08s$)?T zr;7*f6P0T*ZtymHCHLul2B%)dpBWB5%*T5cKl;V@zW2Q*{%Wwit&PtMJ^gSllB9yfg@pAvxV_SDcoq?~;H~^e(uSTUJWj=O z0ecN@?YI}QY1AV=<_8Pa@?5?r5kB>j??XJkmDxc$RUzD!zZzyWX$5|V&F4t%Z0_tOTD1UP#t5xC!nEqZc#^_UvJjLkXMs|B znE28OO39dqRupNG%vKWxq=>C10uLm8aD%`IQK(F->0w%xO(|ll5FAiLBN{)+5vL-K z=_i0qGCI08_0Fcz5Cu=qpjCcoHj!4z^n2a|M68AN5n_c-D|8_Kj^xCU;L`^OM2&XR z-)`ma0L@8)gymm}wpZ|DQW3865Z#4oTLpGRS|h1)DhyQia*){*Bgn(UEAtIWoF5Br z=7)lh@VP6FjUDU0;ReEwi@n6B_;a9aEj^Vq;y3|2HQ|PXZ^Np_oFFd;@of!Spec|( zhv}g~aq-uKMf`QXrwMM`CV38q`dLVlBiv7QxS!lwX-9k$Gm_eM zmJ(~eD?X>P$X=0ZNJ%lJ`wRR7KJ(v6OGS~ijw(-nYHE%p*O;icdfhHlc73tIWlvAk z#>RKW$7&OEe63SI3%k$c`O~QS8OGnkmT(Tz&wb0c@SjxARO*N zh}{FHphr9;{ycOB9jy=@?E${!E?LnwD27ppqlGvsEz{^!A!9wH0+PNIJ)w;zc;8|y z@Tbhww#k6!(7L1Br8jh*KwI9*2QTZMv*+=1C+^(dX$g$gG`A;a%`dk#lv-?oey?wi zzQA9sR;4;DwraOIu;$>2|uR>^gfjR$^d2#Np3&$TuxCv(@Btf}>O5A`bKru}u`g7MTwc%?I2b zo*>a47qa2V&q6j08Hc16;6+05hOy~Ob2#l<2l7@p-}S|^?%;pV8Q>3Ix>I^$YR)UK zAYEle5KA~z2oW=>3Vlm~x=_H*qZq8LlM(S9V`-oksx4uvoz+W)jD_|{k#1S#Ohu`1JN}pVq{?omo$VPSy)9p%^>t;%wH9b1VeXpesKP_p2oo;XUEd& zzf4@wGA}K!GWXEc`E|wS`3t2d`X;*jbn!hsRehyKY0C!ChtU{TXN>HwDN#Y$-yKQUo{As{ziY zi~4phDD#Z&9O~R;8Q*%@H!j#yv&x&ZKE7;X@BI1u7MA6hd#bNHaNxQeM~@knAP^2? zThI&nc45n=P?C)RUiA2?wsD_t;U#lU zP3)~2YjQa1hs&iWY`r@sCazrMm9|X%!qK&P?%Y+iW&n8C!Dc!+PnLo*{K?}FIw5r&|B+hW!5<1f zz-_@(d@R^2J<;}FTX3pP>EB1_UmROY{X^;!UKFw|X3=f5&z-Ov=^vTjkaON<75JT+ z@kKcBh7PU~2O%*X?v|s2-xGY4X9fQ_fgVk@1z*8H725j_Xs?*9B6f{LsHi@jO#%p* z(Ht=gn7J`2Wo_RLo^L>_EE!3egaHKVBw6SZa5~-V9MbE_UqNWEhmf=zr0?W)ZJaZA z$C$Td!TvE%XO&CmT|K95d6U&rH(p!OQ))bg+&F#xn%*Mc;=Kcddlvf=QjG~c+4-)X zbDEpi^}21E(kURMaZxPs40Y+!=m-zN@qduY579+-ZtSXijt$QI501Gy26i;>h+_ zE7UK0JtHz^R{U@lHX=h*&=avhiI-!g9(tHTp*b@iPVQ8|A&ooYxbKdMU-0Vlg8$7+ z7sy}<-WR-u4`X$Z%sW0s|1_+U<|fh$qhO;@K!kThY&=;rCjSv(lYbfoD*=q{_M7Y0X=@HkPC6zLWb!EtUs0Mz&Y&?;iX9(<*)o~OJ^x%^yD%kgA z3y^Y0nj=MEi5+W7R3gMIhe<_n4uR(U(^P!vuDO@287c7gRa?u)FY4^rzMvwfWnFLM zmGgu5o`$A9DS3I0b2~@(Pk4c>(U$WUH0bmhv1_gE8~gh=l9OGoL--;_odiU! zquGMgWibRYlFetw*9g>X&16i8+K%eG6bNl&rA;>RPs1) zZ;dNK=dJ51=$SCO8(f8h&8|y#_f8lKmsBn4%kl)Qh4b5sinsqYrFX-_=&0-CsCoc}-wNS0NM;|HOPlQA-K`DGvtf z7dP2$4HHVt7Y=S|GsYdmy5(3VDW#!4&61b101+VqS>+}birBHrmxa*TrI&xQ(`3?b!5KB8VGaJi%C ztMul@x81t)h9&zSp7+4M0jY56SLpQ(((0+ZqzT||`d@(482X*SE+XmiZ*)&Bp-LO2 zghdmKV1Cg$0MQ18(`lxJj!1Q(co(sJ`3SV7a-<2ss?r=h_oaz{{bs{M4^N!>bR#bg zzJ`?+bAq4q*x(O>*y90rF{ugtjb$VBSfpe}!FiUh{5AcJbV?34tBCI?gf3%w{-O`h zdFtt5UJ`s23rP+JpXM!q5c*(4AB^Zj99fsA+ex!<9l>NM`--C6GiMi~d&2e3?UFGV z&#wwz!v6}#<>4mD*VZ)kGrV&cxd$r{>(!E!SDiMGl~+V!y^3y(Sg*`XCB`w2DFFoL z7)>y{0qT*#KFoE;j|V@I-m05Qry$sAF=Zf4Xegn1GwVIdL=rJ&m5)2(+YnTi-aTmvSb!tbTB)AMo*#dG#?4gn4yH91zO5p!*EC ztG#A))#)?6yL@$>6u9~&v(ge(8hOkR(mVC9- z6?WpLq#$J~S%gAC@}i+pC`i%TlJG_7l*G79kVu*kZbsB{$d;1jas>hDaO4sMWJmTJ zxgsekvQ|k2lH;>R19-QILj5q6MP$LqH)&#cY^JL!uYGjqfr_dY?`V#BarKfFYSmwI zEA!Hdt2&EQOOrk36nj=`XV=`p(kh?T+?;9a9}50W?@OvM?($ zWa6hI8hsfV^jIz>D8c|(X~M8lEXD||6eb&)CdyouT`Z)W#1Zh~WZ=>2E%HuLOjkaV zXRSeiA_btpVgk%1$_FG<6CqEZUU8Ny@h3UWLCVZ;81&SQWyE(3XACzK57g&<*g9`s zs}gAXs%1s9t*&CV^m_2Q(i&USir_z%tXsRxRMuNr)mw^%5)@D3kAXI_Wd9qMi$(D~ zg#aR>jPM)EgQRF^NEJ2+#3rJJ5|<_+sNtl>QON&B2{2*pgbZilf6J;})Z(~eTgluE zYj5G$ynzu8co0*Pra%M!5P$(YA?>EhF_6#-T z=Qj+M)XdKq?zqPeAGg_EZEqbOUR7RiYhKaPvZBdWTEx9mMZS!(UZ1b0EMwJ*RV%?3 z(M>)Ab2gTvETR&5_@=uKm8;G(J2yG`}A-i2P*UgLT(g>>I=nXVRcD zmQ41pIjaD3FSx93eJ5(N93ToL#qHGDuF(y9=<$sARY-A=H3Y;b2FflpY zXLxA1zJ9@AYGz!#HYQe^o>*0OEq^lD$)9W}8egH2)T%m3l2pxsBBap~&RY5Bz*z~R zKiAS~DJ-27VD#cu_Avh_A{s4cKy<} zl4e6jN=k;IrDRdJslUzxc9T~>z z#Q4~xo;eeqDl3%ehDG((DsMuZ(U`7H@}>UATDhgvr}c;%zU%x;l;o0LI@H^BjIz zEs#Yn{g9q8izg|1LInNMOiGBhlsKdj{Lbc5)kVD`gO4qC|yimHcWs@?TY{ z_ifCySTZxL*3=YxWnOxDsxIG@XN~Ig)V}QU0)%Rt6Oye)i4}Vd?d$B4D{J_~qx* z=$cH2?6Bw?w87JJd%vawGCw)2JTd$%6|8Zn6W=0x3QMq~Xj(_u(M4fr)^HUfkI>O9 zB9BR=?N#8(NuH%s zyd~kZd96&+$OsiXBhsK)U=fWO>{|lHX8Kx-}^oz9gN_Fe4iniV-buu5`{cVzG{kZ zyg5I=dE8gApe3IYiE;y^D+F?(%9%{=ns`U+vc|?`Ee=Nu)vbqxW~NY-C9 z&|L)vWZhLx)Rpt6lr-I%n&Hghp4g0$M$%oO%m$*=*Z8W^;QP`eE6Qu3xZ2$P@hNUB@rQK83U6Fk zU>+n*vAm~T))ZxVSqT^1yAsQp6h9e9U~qmHtDm?% zJ8kWi;xCOLC7KAyUn*xUq@kAcn$P4fEdlM3=YA535cb>=oB-+W&WOqrSyV+vNkrRI zR#g#|zFE;>%uF_9n_E3qP3ex33|m9F^_(Szfpk-f-jt3dzV)dVr@`J{o#zP5iBC(^ zr8eu664LVA=4^eOMw@D@aLwsTKps+aoHi!G=EBor)!G!Bzoan!_<6x%OInvEHtWjY;gN;cX4;)-?owf&~g%is!>G ziyXCoV#b4)OGvgLoio&T#Wfb``z&)gy6-|-N5mJAMXKV4p^j7hRk60vUDC zdiUHve&cuh`9Fi@T*)g)10#M3?@MH7_t!?=H%r#f;?0o`D6A<0J%WA6OZ)Hrwc{6^ zxA~$iW6O3d+P91E1^f7;M33-m^Pi*NU+1rl^m`VPXK7&n-d`J%A`ySB zU+WWcI9Q;C{6FxxftcJdt*k*#k%(#F; zOp(4SNyCwbXzNOkfm1dBX}q$}jVvsAaX|ty1;|rH`S4)<%FH1%nD~h>p!~9Xnfp?m z-hfk|85^&S%T0BZX8mH~j=TDf$CUQ^vNUS7rdb=CS0d#HyZPfmVgMB8V*DzM;p=?8 zkuFE5fPB3))g!)MI-^KR#McWy??Ijx{0Cnj#3o37zCYj(ZpS<8rnm6L(z`HAv983! zu&qzjytR_bXpLHDro0nASn`Hg6|S3tfST<2p)^7p`a*_oN@(d9+8WRXX&57?usdlH zD=jgplFwmY%hF$tHDcMCdDF4x`y+NmDhe(kxCh&hw0pKz?FkAU4M2Fc=*6Ttj+MVQxZ# z-Bpxs$i=B4&lnRMkZEaP`XIYLwASf#f9%=xeBqZSzrpog*6SB6Z`e+C^sun^o%h z*jitblWor`F1OZ|;vl=YTpmx|bQ{*bGY)*Szwm#ZAkA{7LI?^K>&(fQxDCF<3t@s@ zOiUb`PDRnyEP1Awq_R4X&QowcMyY8fODu*+VolER%f^wM4ry#27BxOa8bzXq5jIb8 zHf&Oai^;nUjTj@)$TW*h#6hzNOknsS-GrXam86Q$CIN(W+8}^571|(x=sO)XIp&N2 z1T9`7#~g~xW-)SFqd78sy%Ds_p$9fxwmvJNh&7Mt$>J&8~`UXJ5UuwWYlL z$DKVl++gtr;!Bzf(_Cq73C7ff3SUO9IYk?5F7P|+y5rNj%DczRx;A5)uPDP|O4ex2 z&bp$i-ei6I#g%0%mRVh9+GE}>HS=ZCL!bi_Lxy3jB8g>Emi=Hv5nduR3SNB_I(Uc< z&O^&JbZ|ZnL<=4KJoHm?=s>j4?V^Pa2E<*m%MAPoFeRmz*7K%-^_LsFAME=K(t9OD;1tK;9DrKGqRvC zsnbPfy`1<&dlk6QBCnuv!IM-90wEn7PXQ}H5;W$B3wPBDL0W;9TKqZ*@~9PSGeRMh zNPbQv&Y%cRk>VT)u^7@r0hasbU%S4(e*Lxchvj;>`{J>(vayT1ySG!leMw2L-?CwO zL5)J-a3W|2?z(R!r`PEWG~cp$8Q&%S69GLh!%kay7-^XpXC5GCB8ZtVd<`h2R5WXF zMsy?3o&=mB=Sn#v=XHu$w2+3H>^Ye^gmw#|ndlH;CU1jVEMVYgi5lk6rUDL3Can@V z6ef6?*6b*?*3MbAxWB~jnUiT4te!h6N$qOQu3WDrsjH>l|a-XpJB`U<;FYM59H6wcKo zSds=$UKt7+%e;zKz>EAES^@giKyU{?C-~)$o6w7#=?INig4Avy;D=%rAu2CK2Z`Dn z{zGaH(Kk_hMVR(0aE1+{S%v7LAGGfi2=wAD6?jV->N0e^^kfE24WzKrte~Ae{{0NV zUMWH#0)7sILs_U$;Nbs`{#&o=8!XCSGCbVY+uK-B>@N8C#4qL5HkJi+w_kfLUvRa( z(23|3AYUmhPAcl4*)KVHc5H0l6W^oPq zH^n+JlI(JRumYG1r=6nCDoPIg5u(W z0=JvrPA6zI806YmS>|??mATwy^n6+aIwkg4NM>VXZH3|-w01dB&_l@{WV-Z%{sI3T14> zDB0t(xQWDi6iXqZDMjlMl$|Z_1*8>oQ&!_i%}FMCMVSt;B}iH4@L1%Tg}pU5(>ooXjK3LKJtEP-Kv)%<|P5}04* z9}ZXo^F~Jp2S-Qei9T6o^8C>13b0?GRjlc!y&g!Tka<3>CnXDquui*<^}H2QH@4eK5RA0ACXBi%azr*5uB9Bd?W$GSVLTUaj^WQB zlY4f%mOqPKb!WH7@aHa$zJ6W!IWO#pZeJU2KXFNP`_19@pHMsShW@CwVD2QteF7bv z0xGiZLoiAS^A5#J9ho~3Z-+v=(O&9Q%t3iC9a_pJCz4VE8ZAd6Q%g?%BeFos!wEE; zM4{Ae@+Fc;3%Pgn%B8~M!aidhyp{gb+w=7*)s|rI+k5Bp3;9QpB~4HM;~!W<^nP0q zu9eZ?J@Q;c%mr)MF!nr*S&JQiuM_PZU{+rWT)aB=ty?ZCzI=<5mQ z@^enC6z%v{4r6zS=aUkm<{)ru(OKxUsT@P*8wIlo*8gXkIRy|%Bd2l;G<2#vC!{YC@U}&l zR$9^{k5O8!BKq`V3|jsIyG@Lp))O8VV}#XCFyDp>*UxMxnE&>S?ayJa>B#l;e0mN& z|ADA>!iC&^0{cYM40lW)$6os%Bj!+onD(VK?XhvT;83|IWiXV5sr_qw2GHAZx!~A< z9BGK;lnm{SN5^Kg5e3Vu%gBdNCaBtt_FViP0B6zUg7G3@k<@Y zR&P2v!XIf^6G>(SuHXfHQ%8M#d_`YZa*{`0iBTUH>*;IO>pYPK8Mfdb6B3KADH6*v zQuP*|6(4U>YZhFRs4J1zXP8v#xtkJ`i)XCT$PJoe)X9=BSuED5uFU1{X#`9~j<;$x zU|I#R0cWG|*#l=Y+X-i2AZKWQ4m*lR+Rss*qva>C7dKKRMNXeZqW#r&kRKP@ngcH( zFIFLO(88#+V?}Dqeksyx6#sx|AhRP8x(nr^7Yb8l5_ypF(J@AAscpiFD}&&aS9ez_ zzk=O|&3wwcK@ds})S!qpsUUw!K9YmOFzVHk#p-LxbvLIPn;aDtwzBFnTe>CJl3rWd zTswTu+LEr)jG~GndugE5exRr(SEo;@OH3_tVzoC-n%8d3+OQPGK3t#`J!q4RV(#ON?PtIt6 zDBQl9rfT^4UxnMBpms5bK2uTb+6y{pW$%(t6x+>%3?w7Ck2yP^tKp66s`X-;Gh!lL8dd+zi^QzL~gZIXKKhU3bqNXpFt76)M^9!mRDMRx* zi#)OEDGBXOX(feOeO+^VN_|x#FSgJV%YgU8K;y-`g=i3kZwdv`ZKq0x-`0-Xi2{Q(Yev>L^E>x>VDCV@3b-G z;t;Jqgd;6$o?b6#i9CIO4!6g!O~00}2X|+mOZ0qP&tur5cav{{6nt=3sHY_N-o`bb zDZZ;LTKylZ=*%6_;SNWxyE;vVqmM#{I*O#f_}BbNXWw+7cqj6hlrft zq$(7J=k}~{ceF-GhNIY}1-FpAq@XvskyC(OIwn^#$;cux@hCDtlaE+4oC>mhgmetc zMq!nZT*Qu(CRrY8;CxphYS19@6py>GwaQyX$vLIUVatM_n@#sxY?PeCYch=YW?Ly= zCtf$S(YC3N(sYvNZM1E~QLBT;RP|=?u>>=+HY$5Ff_Kvq!Ar!@1)fF#AK)ojhU4k& zn-m>eZYMm+*FUP@TW%+O$nC59ha>uZWM>Ls5V+9_wiySe zD(_JdC#&vpP~G8XhUzY_;G)SjF_Pro^GwRP7?Ag$1 z7j*j>Y#0uDE`g-{Z_1*;hfu^QO?Z0|dO@`>Jla`&;%xEfh?1d%SFDQS87Ffu8HOq;?E_Ayl zN)XM+1eSBQw00%Rt>k%QMnvzks9G-az6UpC=d5W9rLeWF&B$gPt9#U%F^%y>xi=iYsRHp_zO!|6w20 z%A4t(c!NV^M0||L4-M8!fh$Hw#W({}8Sj!Fg*$2vbsL}mLAOJCt->zFIOVQ3!&d~D zSW&zvv>cdtB<37wf+8o0^e)UBr%y&Xc9i{5Zn<60o23GaEf0|%y}ev2ONW-_u-P10 z-aM=T#aItW&Fl*yN3q@~dY#|@^T-eNH?nv7kA&E?S|dUey#W>%N)_BCeI@MMe(GUp zJ$S@-c?M|3&45-TAf^@&1DxR26p<$A5z+6D-Wu*l#eqO^acym}DJREd%*l}|sy)Si zf3c_9W6hv&QwDssfZa;yQ=}>xSv%!`MPjcZpPiB(1jf*ufa`!V6dOt@N)rk>+z5{; znA^(6uA$w4l_=1!Y63}y%%rYFQ(9t~-k^=ieyYrwZZqp+V)Vw$l)@OvuZlxo@})Mu zM0y{K~SHvmEt@w%gQKwfPx9gw4%oIK)SbLgi-p_WoQpy1xX zjGwSQm2B@K*$^A4fH-N5iUzz*1EofTJzfyfphtg%1CEiVVx;kcVkuM{N^~O$0?#Dq zI2En(4qU)ZI29r)6T;+D#TfIljCpW6=Bnz%iOC^Oq&9=onwA99SJWvisbwn&$GN>52N`Q(B^{I$V&{tSD3tK2aePs4ioYOsPG!{ z1ZaudKv$tH@Jm`pM;Ek>j?)@HTIENy67co~Zxrx!umO4-KGG&eK_nj8-RpG7%3@uHtn5^#PbyGm1u48VdeV#d z=@8eR-oR^S;^4NKr>CQYKopIkS^a0>{zp89r(;R(djv}|LnYT1#e*o9Db#;jWikVv zrqdfvL+5Q@kKLRzz<>HEZxpz8upyEUA^1o7e|C&VdTs{Ig?gW*&>vx77DfDrBRGv> zP9Ff)&rLtVAA$~^z~;@AzvR6-+cNcJ(llw)OF1Y<`%udJr4!M`0RhBy5{Q5(E*07v zT{m$1?E|;-JA=vmZ%wbg)+FXi=>2Jwe@@%TNAF!9p<3`b>X4#Ix%p*f`30q=PnYH4Z$Tbh2+%>X=csfZ_Fad~i!TK;Ve^u; zEA}1*bn*QRbpw2lq3>tR!S@lDv9~5+ge_+)#4NAFFgS)owOXlZ2GBX#7F72t^=Mey z%<>^S$&nfTO^sS=sHJG10(nJ1iH86DBOAw9J33JYN!u;L_Vlrh0ddrg4vyfr0n`(~ z$A9?o4LSWOwA-;o@<)A)!ya|~^&jR?{Z;-jMixVFc3?IN;O%?_zd4FO> z$cW5S+L0NHlHqO5KrIK&iCpQUL6jirL`~{ap*hhvEoCP`p*FI4B?o^;NaQK0W-@UYNU(>cG@rP)Ya(kW>dtk@2 zE~3@Y{H2Oz+HRN^<#yy2S2k=F9G-z2jUlMNrII6vm+QC;}MlSKI;Dt`c{A9v} z7JsB+R~WI4wOB|-n+E8lWR3(P)k*s;hP=dBXL?zAV)dwQzD~ci2fGL)XyXd>nfV#| znB*&Um)E5HSFEO4rETow{mmn57twwK%_?1o<}&M++FWt_+vhH~C9Q4HlDlYgAP#g>UW6E(GEFIV)Mn(Zq@01?^iPYjw=%tv4^jj8oU1}GcycDLYD+U>ijw_>cZo+5EWs)aavo ztH$NskFR-!U&gCKJ0SDsrlzUT7RaA8(XfcuuEQ4q^1(`wsTaz~0$#|}l<<1`0+c*6 z=x0e;#jGX9B?K<^uu|3@l8O;ZUc|SVg_s48RX$KfU#H>^J#cU7z4-ru(gz+`_aF}L zDZK~B4+5XEZ<_M30_YdzE?kdV5_@`E0ec;k8+fN_7bSWBXo8DA410xMBbu6m3=tm) zZD&fdmeys#GlsrX^otTJ2?68}llPYPi_N%1b}ZHcsg&)d3mkV&OzhdZPi#=BY)^R= zw#d4A!AfMhWn%B<4^r}~U9I(^&!h*c(dT^dpCcqALi~qhL?mLT*!Ypik+Q-kGKfet zp7JN;P|qZMe%VUU$BO6KP7-}U3q+y#4z-cyN4^0Q3JndR4h)iFL6w3v&D>boh!sGD zCGcn;vR=TPRGRSK^zyzgk~+V2qDq7-8t0W1cA52M);m*ome;N=D%_xNw;(+>xE_n{ z?INg`+39N?Ojv9#HqX)9>WsqniT8CqRdZnZJHV{l@$d`_v!>>q0?FD%Ge z0veFxZxn}J05~Exy#W4);B zrKA_iM`vG>uWTnJX9LY%l0Td=R97<8aHbXcpQ}~`-}h96m*lTnxnflYmZSTyA|0>A zK5zI#`w1tpbBLXuaXos3C?auPN)iT~1T9mNu)O4rG#{F|q->D(LvvDtLh_DO4taer zR!reLT38kAH)suFFXDlLi8tQhL;vIH^6=fkN49rue+zy40&n;q?CFs}dD>C@K}d;N zTggP6??m=UFBE+v4VT_dni}yEB1uwwNNJ&Le%WuLQhEacqSf{d4-F0X@$6Q9Veqxi z_RioLR$D#kSJP2az7-gNURc19n8=ie{;7vw(mSK#WMo5zLdj zP#!~h9_2L@4Frt*WmWn>A{mDC)v)j_H{#?zl&4T$LisHUZFHQ5ud89%xV)2^ShI`W z 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 000000000..0a363b828 --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_bottom_sheet.xml b/app/src/main/res/layout/fragment_bottom_sheet.xml new file mode 100644 index 000000000..1237c2b63 --- /dev/null +++ b/app/src/main/res/layout/fragment_bottom_sheet.xml @@ -0,0 +1,51 @@ + + + + + +