From 896ceb57e2c5668ec5959842a48bc63c218960fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hor=C3=A1cio=20Com=C3=A9?= Date: Mon, 19 Aug 2019 09:46:46 +0200 Subject: [PATCH 01/10] added markwon dependency MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Horácio Comé --- .idea/misc.xml | 2 +- app/build.gradle | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.idea/misc.xml b/.idea/misc.xml index 7bfef59..37a7509 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,6 +1,6 @@ - + diff --git a/app/build.gradle b/app/build.gradle index 0f590b9..610313d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -73,6 +73,7 @@ dependencies { implementation 'com.github.horaciocome1:simple-recyclerview-touch-listener:0.2.2' implementation 'com.github.bumptech.glide:glide:4.9.0' implementation 'jp.wasabeef:glide-transformations:4.0.1' + implementation 'io.noties.markwon:core:4.1.0' // navigation implementation "android.arch.navigation:navigation-fragment-ktx:$nav_version" From a326df2ef410f5feb3de4d2c247665eb849403bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hor=C3=A1cio=20Com=C3=A9?= Date: Mon, 19 Aug 2019 17:20:50 +0200 Subject: [PATCH 02/10] - created preview fragment where one can preview changes made to the message MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Horácio Comé --- .idea/misc.xml | 2 +- .idea/navEditor.xml | 12 +++++++ .../ui/posts/create/CreatePostFragment.kt | 19 +++++++++-- .../ui/posts/create/PreviewMessageFragment.kt | 33 +++++++++++++++++++ .../drawable/outline_remove_red_eye_18.xml | 10 ++++++ .../layout-w600dp/fragment_create_post.xml | 17 ++++++++++ .../layout-w900dp/fragment_create_post.xml | 17 ++++++++++ .../main/res/layout/fragment_create_post.xml | 17 ++++++++++ .../res/layout/fragment_preview_message.xml | 8 +++++ app/src/main/res/navigation/navigation.xml | 19 ++++++++++- app/src/main/res/values/strings.xml | 1 + 11 files changed, 150 insertions(+), 5 deletions(-) create mode 100644 app/src/main/java/io/github/horaciocome1/reaque/ui/posts/create/PreviewMessageFragment.kt create mode 100755 app/src/main/res/drawable/outline_remove_red_eye_18.xml create mode 100644 app/src/main/res/layout/fragment_preview_message.xml diff --git a/.idea/misc.xml b/.idea/misc.xml index 37a7509..7bfef59 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,6 +1,6 @@ - + diff --git a/.idea/navEditor.xml b/.idea/navEditor.xml index a293e4a..2b48139 100644 --- a/.idea/navEditor.xml +++ b/.idea/navEditor.xml @@ -355,6 +355,18 @@ + + + + + + + diff --git a/app/src/main/java/io/github/horaciocome1/reaque/ui/posts/create/CreatePostFragment.kt b/app/src/main/java/io/github/horaciocome1/reaque/ui/posts/create/CreatePostFragment.kt index f595b8b..2db9915 100644 --- a/app/src/main/java/io/github/horaciocome1/reaque/ui/posts/create/CreatePostFragment.kt +++ b/app/src/main/java/io/github/horaciocome1/reaque/ui/posts/create/CreatePostFragment.kt @@ -18,6 +18,7 @@ import androidx.core.content.ContextCompat import androidx.fragment.app.Fragment import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProviders +import androidx.navigation.findNavController import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.card.MaterialCardView import io.github.horaciocome1.reaque.R @@ -73,7 +74,7 @@ class CreatePostFragment : Fragment() { state = BottomSheetBehavior.STATE_HIDDEN skipCollapsed = true } - topics_recyclerview.addOnItemClickListener { _, position -> + topics_recyclerview?.addOnItemClickListener { _, position -> binding.topics?.let { if (it.isNotEmpty()) { selectTopicBehavior.state = BottomSheetBehavior.STATE_HIDDEN @@ -87,14 +88,22 @@ class CreatePostFragment : Fragment() { select_topic_button?.setOnClickListener { selectTopicBehavior.state = BottomSheetBehavior.STATE_HALF_EXPANDED } - select_pic_button.setOnClickListener { + select_pic_button?.setOnClickListener { selectPicBehavior.state = BottomSheetBehavior.STATE_HALF_EXPANDED } toolbar?.setNavigationOnClickListener { viewModel.navigateUp(it) saveDraft() } - create_button?.setOnClickListener { binding.viewmodel = viewModel.create(it) } + create_button?.setOnClickListener { + binding.viewmodel = viewModel.create(it) + } + preview_button?.setOnClickListener { + val directions = CreatePostFragmentDirections.actionOpenPreviewMessage( + viewModel.post.message + ) + view.findNavController().navigate(directions) + } } override fun onStart() { @@ -108,6 +117,10 @@ class CreatePostFragment : Fragment() { saveDraft() viewModel.post.message = it create_button.isEnabled = viewModel.isPostReady + preview_button.visibility = if (it.isBlank()) + View.GONE + else + View.VISIBLE }) viewModel.topics.observe(this, Observer { binding.topics = it diff --git a/app/src/main/java/io/github/horaciocome1/reaque/ui/posts/create/PreviewMessageFragment.kt b/app/src/main/java/io/github/horaciocome1/reaque/ui/posts/create/PreviewMessageFragment.kt new file mode 100644 index 0000000..2031580 --- /dev/null +++ b/app/src/main/java/io/github/horaciocome1/reaque/ui/posts/create/PreviewMessageFragment.kt @@ -0,0 +1,33 @@ +package io.github.horaciocome1.reaque.ui.posts.create + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import io.github.horaciocome1.reaque.R +import io.noties.markwon.Markwon +import kotlinx.android.synthetic.main.fragment_preview_message.* + +class PreviewMessageFragment : Fragment() { + + private val markwon: Markwon? by lazy { + if (context != null) + Markwon.create(context!!) + else + null + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + return inflater.inflate(R.layout.fragment_preview_message, container, false) + } + + override fun onStart() { + super.onStart() + arguments?.let { + val message = PreviewMessageFragmentArgs.fromBundle(it).message + markwon?.setMarkdown(message_textview, message) + } + } + +} \ No newline at end of file diff --git a/app/src/main/res/drawable/outline_remove_red_eye_18.xml b/app/src/main/res/drawable/outline_remove_red_eye_18.xml new file mode 100755 index 0000000..cdf91f5 --- /dev/null +++ b/app/src/main/res/drawable/outline_remove_red_eye_18.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/layout-w600dp/fragment_create_post.xml b/app/src/main/res/layout-w600dp/fragment_create_post.xml index 2684b19..c298956 100644 --- a/app/src/main/res/layout-w600dp/fragment_create_post.xml +++ b/app/src/main/res/layout-w600dp/fragment_create_post.xml @@ -160,6 +160,23 @@ app:layout_constraintStart_toEndOf="@+id/select_topic_button" app:layout_constraintTop_toTopOf="@+id/select_topic_button" /> + + diff --git a/app/src/main/res/layout-w900dp/fragment_create_post.xml b/app/src/main/res/layout-w900dp/fragment_create_post.xml index f7f5161..abf521b 100644 --- a/app/src/main/res/layout-w900dp/fragment_create_post.xml +++ b/app/src/main/res/layout-w900dp/fragment_create_post.xml @@ -160,6 +160,23 @@ app:layout_constraintStart_toEndOf="@+id/select_topic_button" app:layout_constraintTop_toTopOf="@+id/select_topic_button" /> + + diff --git a/app/src/main/res/layout/fragment_create_post.xml b/app/src/main/res/layout/fragment_create_post.xml index 0c49714..3767369 100644 --- a/app/src/main/res/layout/fragment_create_post.xml +++ b/app/src/main/res/layout/fragment_create_post.xml @@ -160,6 +160,23 @@ app:layout_constraintStart_toEndOf="@+id/select_topic_button" app:layout_constraintTop_toTopOf="@+id/select_topic_button" /> + + diff --git a/app/src/main/res/layout/fragment_preview_message.xml b/app/src/main/res/layout/fragment_preview_message.xml new file mode 100644 index 0000000..5870e54 --- /dev/null +++ b/app/src/main/res/layout/fragment_preview_message.xml @@ -0,0 +1,8 @@ + + \ No newline at end of file diff --git a/app/src/main/res/navigation/navigation.xml b/app/src/main/res/navigation/navigation.xml index 3ed3140..3b5b729 100644 --- a/app/src/main/res/navigation/navigation.xml +++ b/app/src/main/res/navigation/navigation.xml @@ -195,5 +195,22 @@ + android:label="@string/create_post"> + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index dc44217..c2399ee 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -86,5 +86,6 @@ Please wait while updates are uploaded . . . Please wait while your post is created . . . Update available + Preview From 9686ddccf2f413cace887e411fcc625081ba38a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hor=C3=A1cio=20Com=C3=A9?= Date: Thu, 29 Aug 2019 13:41:14 +0200 Subject: [PATCH 03/10] updated gradle files - gradle plugin - google play services plugin - firebase plugins to firebase-perf - gradle.properties android studio 3.5 new properties to boost performance - updated firebase dependencies implementations - updated kotlin plugin version MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit other fixes including - FCM custom service superclass has changed Signed-off-by: Horácio Comé --- .idea/codeStyles/Project.xml | 9 ------- .idea/misc.xml | 2 +- .idea/navEditor.xml | 9 +++++++ app/build.gradle | 14 +++++------ .../services/MyFirebaseMessagingService.kt | 25 +++++++++---------- build.gradle | 8 +++--- gradle.properties | 6 +++-- gradle/wrapper/gradle-wrapper.properties | 4 +-- 8 files changed, 39 insertions(+), 38 deletions(-) diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index cb22ebb..c33b7e4 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -1,18 +1,9 @@ - - - - - diff --git a/app/build.gradle b/app/build.gradle index 610313d..bd3271c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -80,14 +80,14 @@ dependencies { implementation "android.arch.navigation:navigation-ui-ktx:$nav_version" // firebase - implementation 'com.google.firebase:firebase-core:17.0.1' - implementation 'com.google.firebase:firebase-firestore:20.2.0' - implementation 'com.google.firebase:firebase-storage:18.1.1' - implementation 'com.google.firebase:firebase-auth:18.1.0' - implementation 'com.google.firebase:firebase-perf:18.0.1' + implementation 'com.google.firebase:firebase-core:17.2.0' + implementation 'com.google.firebase:firebase-firestore:21.0.0' + implementation 'com.google.firebase:firebase-storage:19.0.0' + implementation 'com.google.firebase:firebase-auth:19.0.0' + implementation 'com.google.firebase:firebase-perf:19.0.0' implementation 'com.crashlytics.sdk.android:crashlytics:2.10.1' - implementation 'com.google.firebase:firebase-dynamic-links:18.0.0' - implementation 'com.google.firebase:firebase-messaging:19.0.1' + implementation 'com.google.firebase:firebase-dynamic-links:19.0.0' + implementation 'com.google.firebase:firebase-messaging:20.0.0' // play services implementation 'com.google.android.gms:play-services-auth:17.0.0' diff --git a/app/src/main/java/io/github/horaciocome1/reaque/services/MyFirebaseMessagingService.kt b/app/src/main/java/io/github/horaciocome1/reaque/services/MyFirebaseMessagingService.kt index 29bd832..7aad843 100644 --- a/app/src/main/java/io/github/horaciocome1/reaque/services/MyFirebaseMessagingService.kt +++ b/app/src/main/java/io/github/horaciocome1/reaque/services/MyFirebaseMessagingService.kt @@ -16,22 +16,21 @@ import io.github.horaciocome1.reaque.util.Constants.USER_ID class MyFirebaseMessagingService : FirebaseMessagingService() { - override fun onNewToken(token: String?) { + + override fun onNewToken(token: String) { val service = UsersService() - token?.let { service.updateRegistrationToken(it) {} } + service.updateRegistrationToken(token) {} } - override fun onMessageReceived(remoteMessage: RemoteMessage?) { - remoteMessage?.let { - if (it.data.isNotEmpty() && it.notification != null) { - val userId = it.data[USER_ID] - val title = it.notification!!.title - val body = it.notification!!.body - val clickAction = it.notification!!.clickAction - if (userId != null && title != null && body != null && clickAction != null) - if (userId.isNotBlank() && title.isNotBlank() && body.isNotBlank() && clickAction.isNotBlank()) - sendNotification(title, body, clickAction, userId) - } + override fun onMessageReceived(remoteMessage: RemoteMessage) { + if (remoteMessage.data.isNotEmpty() && remoteMessage.notification != null) { + val userId = remoteMessage.data[USER_ID] + val title = remoteMessage.notification!!.title + val body = remoteMessage.notification!!.body + val clickAction = remoteMessage.notification!!.clickAction + if (userId != null && title != null && body != null && clickAction != null) + if (userId.isNotBlank() && title.isNotBlank() && body.isNotBlank() && clickAction.isNotBlank()) + sendNotification(title, body, clickAction, userId) } } diff --git a/build.gradle b/build.gradle index 04907d5..fe849f2 100644 --- a/build.gradle +++ b/build.gradle @@ -16,7 +16,7 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { - ext.kotlin_version = '1.3.41' + ext.kotlin_version = '1.3.50' ext.nav_version = '1.0.0' repositories { google() @@ -26,11 +26,11 @@ buildscript { } } dependencies { - classpath 'com.android.tools.build:gradle:3.4.2' + classpath 'com.android.tools.build:gradle:3.5.0' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - classpath 'com.google.gms:google-services:4.3.0' + classpath 'com.google.gms:google-services:4.3.1' classpath "android.arch.navigation:navigation-safe-args-gradle-plugin:$nav_version" - classpath 'com.google.firebase:firebase-plugins:1.2.0' + classpath 'com.google.firebase:perf-plugin:1.3.1' classpath 'io.fabric.tools:gradle:1.28.0' // NOTE: Do not place your application dependencies here; they belong diff --git a/gradle.properties b/gradle.properties index 089b893..7f0e8c1 100644 --- a/gradle.properties +++ b/gradle.properties @@ -12,6 +12,7 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and limitations under the License. # + #org.gradle.jvmargs=-Xmx1536m # SPEED UP GRADLE org.gradle.jvmargs=-Xmx3072m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 @@ -32,9 +33,10 @@ android.enableJetifier=true android.enableR8=true # databinding -android.databinding.enableV2=true +android.databinding.incremental=true + # Enable the incremental annotation processing experimental flag SPEED UP GRADLE kapt.incremental.apt=true # SPEED UP GRADLE org.gradle.configureondemand=true - +-Pandroid.debug.obsoleteApi=true \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 8c2521a..c39e415 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ -#Thu Apr 18 13:09:36 CAT 2019 +#Tue Aug 27 15:11:09 CAT 2019 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.1.1-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip From 3224842c18edf055259488bcad5ab03edd059c60 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hor=C3=A1cio=20Com=C3=A9?= Date: Wed, 4 Sep 2019 10:46:57 +0200 Subject: [PATCH 04/10] updated gradle files - gradle plugin - google play services plugin - firebase plugins to firebase-perf - gradle.properties android studio 3.5 new properties to boost performance - updated firebase dependencies implementations - updated kotlin plugin version - removed markwon dependency - updated version name to 1.5.0-rc-10, version code 17 - updated build tools version to 29.0.2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit other fixes including - FCM custom service superclass has changed and improvements including, - tweaks on reading layout - tweaks on creating post layout and logic - tweaks on sign in (now user needs to manifest he wants to sign in) - added beautiful illustrations - tweaked item post, it has more color now - tweaked item topic, it looks beauty now - enlarged item suggestion - querying posts ordering them by timestamp only, because of the poor score algorithm Signed-off-by: Horácio Comé --- .idea/assetWizardSettings.xml | 20 +- .idea/navEditor.xml | 30 +- app/build.gradle | 7 +- .../reaque/data/bookmarks/BookmarksService.kt | 38 ++- .../configurations/ConfigurationsService.kt | 15 +- .../reaque/data/feed/FeedService.kt | 15 +- .../reaque/data/posts/PostsService.kt | 52 +++- .../reaque/data/ratings/RatingsService.kt | 37 ++- .../reaque/data/readings/ReadingsService.kt | 10 +- .../reaque/data/shares/SharesService.kt | 5 +- .../reaque/data/storage/StorageService.kt | 9 +- .../subscriptions/SubscriptionsService.kt | 55 +++- .../reaque/data/users/UsersService.kt | 39 ++- .../services/MyFirebaseMessagingService.kt | 24 +- .../horaciocome1/reaque/ui/MainActivity.kt | 93 ++++-- .../reaque/ui/explore/ExploreFragment.kt | 16 +- .../reaque/ui/explore/ExploreViewModel.kt | 20 +- .../reaque/ui/explore/TopicsAdapter.kt | 6 +- .../reaque/ui/feed/FeedFragment.kt | 13 +- .../reaque/ui/feed/FeedViewModel.kt | 12 +- .../reaque/ui/feed/FeedViewModelFactory.kt | 3 +- .../reaque/ui/more/MoreFragment.kt | 35 +-- .../reaque/ui/more/MoreViewModel.kt | 48 ++- .../reaque/ui/posts/PostsAdapter.kt | 9 +- .../reaque/ui/posts/PostsFragment.kt | 12 +- .../reaque/ui/posts/PostsViewModel.kt | 73 +---- .../reaque/ui/posts/PostsViewModelFactory.kt | 11 +- .../reaque/ui/posts/ReadPostFragment.kt | 68 ----- .../ui/posts/create/CreatePostFragment.kt | 152 ++++------ .../ui/posts/create/CreatePostViewModel.kt | 47 ++- .../ui/posts/create/PreviewMessageFragment.kt | 33 -- .../reaque/ui/posts/read/ReadPostFragment.kt | 77 +++++ .../reaque/ui/posts/read/ReadPostViewModel.kt | 95 ++++++ .../ui/posts/read/ReadPostViewModelFactory.kt | 27 ++ .../ui/posts/{ => read}/SetRatingFragment.kt | 28 +- .../reaque/ui/sign_in/SignInFragment.kt | 78 ++--- .../reaque/ui/sign_in/SignInViewModel.kt | 45 +++ .../ui/sign_in/SignInViewModelFactory.kt | 10 + .../reaque/ui/users/UserProfileFragment.kt | 70 ----- .../reaque/ui/users/UsersAdapter.kt | 3 +- .../reaque/ui/users/UsersFragment.kt | 12 +- .../reaque/ui/users/UsersViewModel.kt | 45 --- .../ui/users/profile/UserProfileFragment.kt | 69 +++++ .../ui/users/profile/UserProfileViewModel.kt | 61 ++++ .../profile/UserProfileViewModelFactory.kt | 16 + .../ui/users/update/UpdateUserFragment.kt | 41 +-- .../ui/users/update/UpdateUserViewModel.kt | 12 +- .../update/UpdateUserViewModelFactory.kt | 8 +- .../reaque/util/BindingAdapters.kt | 12 +- .../horaciocome1/reaque/util/Constants.kt | 2 - .../horaciocome1/reaque/util/DateUtils.kt | 4 +- .../reaque/util/DocumentSnapshotUtils.kt | 15 +- .../horaciocome1/reaque/util/DomainUtils.kt | 9 +- .../horaciocome1/reaque/util/InjectorUtils.kt | 34 ++- .../reaque/util/ObservableViewModel.kt | 3 +- ...ckground_all_corners_rounded_secondary.xml | 8 + .../main/res/drawable/background_splash.xml | 10 +- .../res/drawable/ic_undraw_camera_mg5h.xml | 146 +++++++++ .../res/drawable/ic_undraw_feedback_h2ft.xml | 175 +++++++++++ .../res/drawable/ic_undraw_ideas_s70l.xml | 130 ++++++++ .../res/drawable/ic_undraw_reading_0re1.xml | 146 +++++++++ .../drawable/ic_undraw_reading_book_4wjf.xml | 132 ++++++++ .../res/drawable/outline_arrow_forward_18.xml | 10 + .../drawable/outline_remove_red_eye_18.xml | 10 - app/src/main/res/drawable/splash_screen.png | Bin 57036 -> 0 bytes .../main/res/layout-land/activity_main.xml | 92 +++--- .../res/layout-v21/fragment_read_post.xml | 283 +++++++++++------- app/src/main/res/layout-v21/item_topic.xml | 72 +++++ .../layout-w600dp/fragment_create_post.xml | 128 +++----- .../layout-w900dp/fragment_create_post.xml | 119 +++----- app/src/main/res/layout/activity_main.xml | 122 ++++---- .../main/res/layout/fragment_create_post.xml | 128 +++----- .../res/layout/fragment_preview_message.xml | 8 - .../main/res/layout/fragment_read_post.xml | 269 ++++++++++------- .../main/res/layout/fragment_set_rating.xml | 84 +++--- app/src/main/res/layout/fragment_sign_in.xml | 62 ++-- .../main/res/layout/fragment_update_user.xml | 28 +- .../main/res/layout/fragment_user_profile.xml | 2 +- app/src/main/res/layout/item_post.xml | 57 ++-- app/src/main/res/layout/item_suggestion.xml | 4 +- app/src/main/res/layout/layout_header.xml | 18 ++ app/src/main/res/navigation/navigation.xml | 33 +- app/src/main/res/values-pt/strings.xml | 2 + app/src/main/res/values/strings.xml | 5 +- app/src/main/res/values/styles.xml | 3 +- 85 files changed, 2680 insertions(+), 1389 deletions(-) delete mode 100644 app/src/main/java/io/github/horaciocome1/reaque/ui/posts/ReadPostFragment.kt delete mode 100644 app/src/main/java/io/github/horaciocome1/reaque/ui/posts/create/PreviewMessageFragment.kt create mode 100644 app/src/main/java/io/github/horaciocome1/reaque/ui/posts/read/ReadPostFragment.kt create mode 100644 app/src/main/java/io/github/horaciocome1/reaque/ui/posts/read/ReadPostViewModel.kt create mode 100644 app/src/main/java/io/github/horaciocome1/reaque/ui/posts/read/ReadPostViewModelFactory.kt rename app/src/main/java/io/github/horaciocome1/reaque/ui/posts/{ => read}/SetRatingFragment.kt (65%) create mode 100644 app/src/main/java/io/github/horaciocome1/reaque/ui/sign_in/SignInViewModel.kt create mode 100644 app/src/main/java/io/github/horaciocome1/reaque/ui/sign_in/SignInViewModelFactory.kt delete mode 100644 app/src/main/java/io/github/horaciocome1/reaque/ui/users/UserProfileFragment.kt create mode 100644 app/src/main/java/io/github/horaciocome1/reaque/ui/users/profile/UserProfileFragment.kt create mode 100644 app/src/main/java/io/github/horaciocome1/reaque/ui/users/profile/UserProfileViewModel.kt create mode 100644 app/src/main/java/io/github/horaciocome1/reaque/ui/users/profile/UserProfileViewModelFactory.kt create mode 100644 app/src/main/res/drawable/background_all_corners_rounded_secondary.xml create mode 100644 app/src/main/res/drawable/ic_undraw_camera_mg5h.xml create mode 100644 app/src/main/res/drawable/ic_undraw_feedback_h2ft.xml create mode 100644 app/src/main/res/drawable/ic_undraw_ideas_s70l.xml create mode 100644 app/src/main/res/drawable/ic_undraw_reading_0re1.xml create mode 100644 app/src/main/res/drawable/ic_undraw_reading_book_4wjf.xml create mode 100755 app/src/main/res/drawable/outline_arrow_forward_18.xml delete mode 100755 app/src/main/res/drawable/outline_remove_red_eye_18.xml delete mode 100644 app/src/main/res/drawable/splash_screen.png create mode 100644 app/src/main/res/layout-v21/item_topic.xml delete mode 100644 app/src/main/res/layout/fragment_preview_message.xml create mode 100644 app/src/main/res/layout/layout_header.xml diff --git a/.idea/assetWizardSettings.xml b/.idea/assetWizardSettings.xml index e74aec2..6815e57 100644 --- a/.idea/assetWizardSettings.xml +++ b/.idea/assetWizardSettings.xml @@ -186,7 +186,25 @@ - + + + diff --git a/.idea/navEditor.xml b/.idea/navEditor.xml index c4bf40a..92d655d 100644 --- a/.idea/navEditor.xml +++ b/.idea/navEditor.xml @@ -243,15 +243,6 @@ - @@ -302,6 +293,11 @@ + + + + + @@ -364,18 +360,6 @@ - - - - - - - @@ -419,8 +403,8 @@ diff --git a/app/build.gradle b/app/build.gradle index bd3271c..80c8ed9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -36,8 +36,8 @@ android { applicationId "io.github.horaciocome1.reaque" minSdkVersion 16 targetSdkVersion 29 - versionCode 16 - versionName "1.4.2-rc-35" + versionCode 17 + versionName "1.5.0-rc-10" vectorDrawables.useSupportLibrary = true multiDexEnabled true } @@ -51,7 +51,7 @@ android { dataBinding { enabled = true } - buildToolsVersion = '29.0.0' + buildToolsVersion = '29.0.2' } dependencies { @@ -73,7 +73,6 @@ dependencies { implementation 'com.github.horaciocome1:simple-recyclerview-touch-listener:0.2.2' implementation 'com.github.bumptech.glide:glide:4.9.0' implementation 'jp.wasabeef:glide-transformations:4.0.1' - implementation 'io.noties.markwon:core:4.1.0' // navigation implementation "android.arch.navigation:navigation-fragment-ktx:$nav_version" diff --git a/app/src/main/java/io/github/horaciocome1/reaque/data/bookmarks/BookmarksService.kt b/app/src/main/java/io/github/horaciocome1/reaque/data/bookmarks/BookmarksService.kt index 82f3d2d..f6acd2b 100644 --- a/app/src/main/java/io/github/horaciocome1/reaque/data/bookmarks/BookmarksService.kt +++ b/app/src/main/java/io/github/horaciocome1/reaque/data/bookmarks/BookmarksService.kt @@ -52,8 +52,14 @@ class BookmarksService : BookmarksInterface { } override fun bookmark(post: Post, onCompleteListener: (Task?) -> Unit) { - if (post.id.isNotBlank() && auth.currentUser != null) { - val bookmarkRef = db.document("users/${auth.currentUser!!.uid}/bookmarks/${post.id}") + if ( + post.id.isNotBlank() + && auth.currentUser != null + ) { + val bookmarkRef = db.document( + "users/${auth.currentUser!!.uid}" + + "/bookmarks/${post.id}" + ) val postRef = db.document("posts/${post.id}") val userRef = db.document("users/${auth.currentUser!!.uid}") db.runBatch { @@ -65,8 +71,14 @@ class BookmarksService : BookmarksInterface { } override fun unBookmark(post: Post, onCompleteListener: (Task?) -> Unit) { - if (post.id.isNotBlank() && auth.currentUser != null) { - val bookmarkRef = db.document("users/${auth.currentUser!!.uid}/bookmarks/${post.id}") + if ( + post.id.isNotBlank() + && auth.currentUser != null + ) { + val bookmarkRef = db.document( + "users/${auth.currentUser!!.uid}" + + "/bookmarks/${post.id}" + ) val postRef = db.document("posts/${post.id}") val userRef = db.document("users/${auth.currentUser!!.uid}") db.runBatch { @@ -79,9 +91,12 @@ class BookmarksService : BookmarksInterface { override fun get(): LiveData> { posts.value?.let { list -> - if (list.isEmpty() && auth.currentUser != null) + if ( + list.isEmpty() + && auth.currentUser != null + ) db.collection("users/${auth.currentUser!!.uid}/bookmarks") - .orderBy("score", Query.Direction.DESCENDING) + .orderBy("timestamp", Query.Direction.DESCENDING) .limit(100) .get() .addOnSuccessListener { @@ -97,7 +112,10 @@ class BookmarksService : BookmarksInterface { if (post.id.isNotBlank() && auth.currentUser != null) db.document("users/${auth.currentUser!!.uid}/bookmarks/${post.id}") .addSnapshotListener { snapshot, exception -> - isBookmarked.value = if (exception == null && snapshot != null) + isBookmarked.value = if ( + exception == null + && snapshot != null + ) if (snapshot.exists()) Constants.States.TRUE else @@ -113,7 +131,11 @@ class BookmarksService : BookmarksInterface { if (auth.currentUser != null) db.document("users/${auth.currentUser!!.uid}") .addSnapshotListener { snapshot, exception -> - if (exception == null && snapshot != null && snapshot.contains("bookmarks")) + if ( + exception == null + && snapshot != null + && snapshot.contains("bookmarks") + ) hasBookmarks.value = snapshot["bookmarks"].toString().toInt() > 0 } return hasBookmarks diff --git a/app/src/main/java/io/github/horaciocome1/reaque/data/configurations/ConfigurationsService.kt b/app/src/main/java/io/github/horaciocome1/reaque/data/configurations/ConfigurationsService.kt index 039a4be..dfa007b 100644 --- a/app/src/main/java/io/github/horaciocome1/reaque/data/configurations/ConfigurationsService.kt +++ b/app/src/main/java/io/github/horaciocome1/reaque/data/configurations/ConfigurationsService.kt @@ -38,8 +38,13 @@ class ConfigurationsService : ConfigurationsInterface { isUpdateAvailable.value = false db.document("configurations/default") .addSnapshotListener { snapshot, exception -> - if (exception == null && snapshot != null && snapshot.contains("version_code")) - isUpdateAvailable.value = versionCode < snapshot["version_code"].toString().toInt() + if ( + exception == null + && snapshot != null + && snapshot.contains("version_code") + ) + isUpdateAvailable.value = versionCode < snapshot["version_code"].toString() + .toInt() } return isUpdateAvailable } @@ -47,7 +52,11 @@ class ConfigurationsService : ConfigurationsInterface { override fun getLatestVersionName(): LiveData { db.document("configurations/default") .addSnapshotListener { snapshot, exception -> - if (exception == null && snapshot != null && snapshot.contains("version_name")) + if ( + exception == null + && snapshot != null + && snapshot.contains("version_name") + ) latestVersionName.value = snapshot["version_name"].toString() } return latestVersionName diff --git a/app/src/main/java/io/github/horaciocome1/reaque/data/feed/FeedService.kt b/app/src/main/java/io/github/horaciocome1/reaque/data/feed/FeedService.kt index f4c0743..76138ad 100644 --- a/app/src/main/java/io/github/horaciocome1/reaque/data/feed/FeedService.kt +++ b/app/src/main/java/io/github/horaciocome1/reaque/data/feed/FeedService.kt @@ -21,13 +21,19 @@ class FeedService : FeedInterface { private var _posts = mutableListOf() private val posts: MutableLiveData> by lazy { - MutableLiveData>().apply { value = mutableListOf() } + MutableLiveData>().apply { + value = mutableListOf() + } } override fun get(): LiveData> { - if (_posts.isEmpty() && auth.currentUser != null) - db.collection("users/${auth.currentUser!!.uid}/feed") - .orderBy("score", Query.Direction.DESCENDING) + auth.addAuthStateListener { + if ( + _posts.isEmpty() + && it.currentUser != null + ) + db.collection("users/${auth.currentUser!!.uid}/feed") + .orderBy("timestamp", Query.Direction.DESCENDING) .limit(100) .get() .addOnSuccessListener { @@ -36,6 +42,7 @@ class FeedService : FeedInterface { posts.value = _posts } } + } return posts } diff --git a/app/src/main/java/io/github/horaciocome1/reaque/data/posts/PostsService.kt b/app/src/main/java/io/github/horaciocome1/reaque/data/posts/PostsService.kt index a817d1c..ecc3962 100644 --- a/app/src/main/java/io/github/horaciocome1/reaque/data/posts/PostsService.kt +++ b/app/src/main/java/io/github/horaciocome1/reaque/data/posts/PostsService.kt @@ -32,15 +32,21 @@ class PostsService : PostsInterface { } private val topicPosts: MutableLiveData> by lazy { - MutableLiveData>().apply { value = mutableListOf() } + MutableLiveData>().apply { + value = mutableListOf() + } } private val userPosts: MutableLiveData> by lazy { - MutableLiveData>().apply { value = mutableListOf() } + MutableLiveData>().apply { + value = mutableListOf() + } } private val top10Posts: MutableLiveData> by lazy { - MutableLiveData>().apply { value = mutableListOf() } + MutableLiveData>().apply { + value = mutableListOf() + } } private var postId = "" @@ -54,12 +60,24 @@ class PostsService : PostsInterface { post.user = auth.currentUser!!.user val postRef = db.collection("posts").document() post.id = postRef.id - val postOnTopicRef = db.document("topics/${post.topic.id}/posts/${postRef.id}") - val postOnUserRef = db.document("users/${post.user.id}/posts/${postRef.id}") - val userOnTopicRef = db.document("topics/${post.topic.id}/users/${post.user.id}") + val postOnTopicRef = db.document( + "topics/${post.topic.id}" + + "/posts/${postRef.id}" + ) + val postOnUserRef = db.document( + "users/${post.user.id}" + + "/posts/${postRef.id}" + ) + val userOnTopicRef = db.document( + "topics/${post.topic.id}" + + "/users/${post.user.id}" + ) val topicRef = db.document("topics/${post.topic.id}") val userRef = db.document("users/${post.user.id}") - val myFeedRef = db.document("users/${post.user.id}/feed/${postRef.id}") + val myFeedRef = db.document( + "users/${post.user.id}" + + "/feed/${postRef.id}" + ) db.runBatch { it.set(postRef, post.map) it.set(postOnTopicRef, post.mapSimple) @@ -77,13 +95,15 @@ class PostsService : PostsInterface { } override fun get(post: Post): LiveData { - if (post.id != postId && post.id.isNotBlank()) { - this.post.value = Post("") - db.document("posts/${post.id}") - .addSnapshotListener { snapshot, exception -> - if (exception == null && snapshot != null) - this.post.value = snapshot.post - } + auth.addAuthStateListener { + if (post.id != postId && post.id.isNotBlank()) { + this.post.value = Post("") + db.document("posts/${post.id}") + .addSnapshotListener { snapshot, exception -> + if (exception == null && snapshot != null) + this.post.value = snapshot.post + } + } } return this.post } @@ -92,7 +112,7 @@ class PostsService : PostsInterface { if (user.id != userId && user.id.isNotBlank()) { userPosts.value = mutableListOf() db.collection("users/${user.id}/posts") - .orderBy("score", Query.Direction.DESCENDING) + .orderBy("timestamp", Query.Direction.DESCENDING) .limit(100) .get() .addOnSuccessListener { @@ -108,7 +128,7 @@ class PostsService : PostsInterface { if (topic.id != topicId && topic.id.isNotBlank()) { topicPosts.value = mutableListOf() db.collection("topics/${topic.id}/posts") - .orderBy("score", Query.Direction.DESCENDING) + .orderBy("timestamp", Query.Direction.DESCENDING) .limit(100) .get() .addOnSuccessListener { diff --git a/app/src/main/java/io/github/horaciocome1/reaque/data/ratings/RatingsService.kt b/app/src/main/java/io/github/horaciocome1/reaque/data/ratings/RatingsService.kt index db8e04a..60322a1 100644 --- a/app/src/main/java/io/github/horaciocome1/reaque/data/ratings/RatingsService.kt +++ b/app/src/main/java/io/github/horaciocome1/reaque/data/ratings/RatingsService.kt @@ -21,15 +21,29 @@ class RatingsService : RatingsInterface { } private val rating: MutableLiveData by lazy { - MutableLiveData().apply { value = 0 } + MutableLiveData().apply { + value = 0 + } } override fun set(post: Post, value: Int, onCompleteListener: (Task?) -> Unit) { - if (post.id.isNotBlank() && value >= 1 && value <= 5 && auth.currentUser != null) { + if ( + post.id.isNotBlank() + && value >= 1 + && value <= 5 + && auth.currentUser != null + ) { val data = auth.currentUser!!.user.map .plus("value" to value) - .plus("post" to mapOf("id" to post.id)) - db.document("posts/${post.id}/ratings/${auth.currentUser!!.uid}") + .plus( + "post" to mapOf( + "id" to post.id + ) + ) + db.document( + "posts/${post.id}" + + "/ratings/${auth.currentUser!!.uid}" + ) .set(data, SetOptions.merge()) .addOnCompleteListener(onCompleteListener) } @@ -38,10 +52,19 @@ class RatingsService : RatingsInterface { override fun get(post: Post): LiveData { rating.value = 0 if (post.id.isNotBlank() && auth.currentUser != null) - db.document("posts/${post.id}/ratings/${auth.currentUser!!.uid}") + db.document( + "posts/${post.id}" + + "/ratings/${auth.currentUser!!.uid}" + ) .addSnapshotListener { snapshot, exception -> - if (exception == null && snapshot != null && snapshot.contains("value")) - rating.value = snapshot["value"].toString().toInt() + if ( + exception == null + && snapshot != null + && snapshot.contains("value") + ) + rating.value = snapshot["value"] + .toString() + .toInt() } return rating } diff --git a/app/src/main/java/io/github/horaciocome1/reaque/data/readings/ReadingsService.kt b/app/src/main/java/io/github/horaciocome1/reaque/data/readings/ReadingsService.kt index 61763fb..b38fa99 100644 --- a/app/src/main/java/io/github/horaciocome1/reaque/data/readings/ReadingsService.kt +++ b/app/src/main/java/io/github/horaciocome1/reaque/data/readings/ReadingsService.kt @@ -24,8 +24,14 @@ class ReadingsService : ReadingsInterface { } override fun read(post: Post, onCompleteListener: (Task?) -> Unit) { - if (post.id.isNotBlank() && auth.currentUser != null) { - val readingRef = db.document("users/${auth.currentUser!!.uid}/readings/${post.id}") + if ( + post.id.isNotBlank() + && auth.currentUser != null + ) { + val readingRef = db.document( + "users/${auth.currentUser!!.uid}" + + "/readings/${post.id}" + ) val postRef = db.document("posts/${post.id}") db.runBatch { it.set(readingRef, post.mapSimple) diff --git a/app/src/main/java/io/github/horaciocome1/reaque/data/shares/SharesService.kt b/app/src/main/java/io/github/horaciocome1/reaque/data/shares/SharesService.kt index 314c7d7..85e9865 100644 --- a/app/src/main/java/io/github/horaciocome1/reaque/data/shares/SharesService.kt +++ b/app/src/main/java/io/github/horaciocome1/reaque/data/shares/SharesService.kt @@ -25,7 +25,10 @@ class SharesService : SharesInterface { override fun share(post: Post, onCompleteListener: (Task?) -> Unit) { if (post.id.isNotBlank() && auth.currentUser != null) { - val readingRef = db.document("users/${auth.currentUser!!.uid}/shares/${post.id}") + val readingRef = db.document( + "users/${auth.currentUser!!.uid}" + + "/shares/${post.id}" + ) val postRef = db.document("posts/${post.id}") db.runBatch { it.set(readingRef, post.mapSimple) diff --git a/app/src/main/java/io/github/horaciocome1/reaque/data/storage/StorageService.kt b/app/src/main/java/io/github/horaciocome1/reaque/data/storage/StorageService.kt index a8af729..36c6763 100644 --- a/app/src/main/java/io/github/horaciocome1/reaque/data/storage/StorageService.kt +++ b/app/src/main/java/io/github/horaciocome1/reaque/data/storage/StorageService.kt @@ -29,13 +29,18 @@ class StorageService { } fun upload(imageUri: Uri, topic: Topic, onSuccessListener: (Uri?) -> Unit) { - if (imageUri != Uri.EMPTY && topic.id.isNotBlank()) { + if ( + imageUri != Uri.EMPTY + && topic.id.isNotBlank() + ) { val path = "images/${topic.id}/${imageUri.lastPathSegment}" val ref = storage.reference.child(path) ref.putFile(imageUri) .continueWithTask(Continuation> { task -> if (!task.isSuccessful) - task.exception?.let { throw it } + task.exception?.let { + throw it + } return@Continuation ref.downloadUrl }) .addOnSuccessListener(onSuccessListener) diff --git a/app/src/main/java/io/github/horaciocome1/reaque/data/subscriptions/SubscriptionsService.kt b/app/src/main/java/io/github/horaciocome1/reaque/data/subscriptions/SubscriptionsService.kt index 64e3602..1708c12 100644 --- a/app/src/main/java/io/github/horaciocome1/reaque/data/subscriptions/SubscriptionsService.kt +++ b/app/src/main/java/io/github/horaciocome1/reaque/data/subscriptions/SubscriptionsService.kt @@ -67,9 +67,18 @@ class SubscriptionsService : SubscriptionsInterface { private var subscribersOf = "" override fun subscribe(user: User, onCompleteListener: (Task?) -> Unit) { - if (user.id.isNotBlank() && auth.currentUser != null) { - val subscriptionRef = db.document("users/${auth.currentUser!!.uid}/subscriptions/${user.id}") - val subscriberRef = db.document("users/${user.id}/subscribers/${auth.currentUser!!.uid}") + if ( + user.id.isNotBlank() + && auth.currentUser != null + ) { + val subscriptionRef = db.document( + "users/${auth.currentUser!!.uid}" + + "/subscriptions/${user.id}" + ) + val subscriberRef = db.document( + "users/${user.id}" + + "/subscribers/${auth.currentUser!!.uid}" + ) val myRef = db.document("users/${auth.currentUser!!.uid}") val hisRef = db.document("users/${user.id}") db.runBatch { @@ -82,9 +91,18 @@ class SubscriptionsService : SubscriptionsInterface { } override fun unSubscribe(user: User, onCompleteListener: (Task?) -> Unit) { - if (user.id.isNotBlank() && auth.currentUser != null) { - val subscriptionRef = db.document("users/${auth.currentUser!!.uid}/subscriptions/${user.id}") - val subscriberRef = db.document("users/${user.id}/subscribers/${auth.currentUser!!.uid}") + if ( + user.id.isNotBlank() + && auth.currentUser != null + ) { + val subscriptionRef = db.document( + "users/${auth.currentUser!!.uid}" + + "/subscriptions/${user.id}" + ) + val subscriberRef = db.document( + "users/${user.id}" + + "/subscribers/${auth.currentUser!!.uid}" + ) val myRef = db.document("users/${auth.currentUser!!.uid}") val hisRef = db.document("users/${user.id}") db.runBatch { @@ -97,7 +115,10 @@ class SubscriptionsService : SubscriptionsInterface { } override fun getSubscriptions(user: User): LiveData> { - if (user.id != subscriptionsOf && user.id.isNotBlank()) { + if ( + user.id != subscriptionsOf + && user.id.isNotBlank() + ) { db.collection("users/${user.id}/subscriptions") .orderBy("score", Query.Direction.DESCENDING) .limit(100) @@ -112,7 +133,10 @@ class SubscriptionsService : SubscriptionsInterface { } override fun getSubscribers(user: User): LiveData> { - if (user.id != subscribersOf && user.id.isNotBlank()) { + if ( + user.id != subscribersOf + && user.id.isNotBlank() + ) { db.collection("users/${user.id}/subscribers") .orderBy("score", Query.Direction.DESCENDING) .limit(100) @@ -128,10 +152,19 @@ class SubscriptionsService : SubscriptionsInterface { override fun amSubscribedTo(user: User): LiveData { amSubscribedTo.value = Constants.States.UNDEFINED - if (user.id.isNotBlank() && auth.currentUser != null) - db.document("users/${auth.currentUser!!.uid}/subscriptions/${user.id}") + if ( + user.id.isNotBlank() + && auth.currentUser != null + ) + db.document( + "users/${auth.currentUser!!.uid}" + + "/subscriptions/${user.id}" + ) .addSnapshotListener { snapshot, exception -> - amSubscribedTo.value = if (exception == null && snapshot != null) + amSubscribedTo.value = if ( + exception == null + && snapshot != null + ) if (snapshot.exists()) Constants.States.TRUE else diff --git a/app/src/main/java/io/github/horaciocome1/reaque/data/users/UsersService.kt b/app/src/main/java/io/github/horaciocome1/reaque/data/users/UsersService.kt index 3909a75..9f45907 100644 --- a/app/src/main/java/io/github/horaciocome1/reaque/data/users/UsersService.kt +++ b/app/src/main/java/io/github/horaciocome1/reaque/data/users/UsersService.kt @@ -23,11 +23,15 @@ class UsersService : UsersInterface { } private val user: MutableLiveData by lazy { - MutableLiveData().apply { value = User("") } + MutableLiveData().apply { + value = User("") + } } private val users: MutableLiveData> by lazy { - MutableLiveData>().apply { value = mutableListOf() } + MutableLiveData>().apply { + value = mutableListOf() + } } private var topicId = "" @@ -42,20 +46,28 @@ class UsersService : UsersInterface { } override fun get(user: User): LiveData { - if (user.id != userId && user.id.isNotBlank()) { - this.user.value = User("") - db.document("users/${user.id}") - .addSnapshotListener { snapshot, exception -> - if (exception == null && snapshot != null) - this.user.value = snapshot.user - } - userId = user.id + auth.addAuthStateListener { + if ( + user.id != userId + && user.id.isNotBlank() + ) { + this.user.value = User("") + db.document("users/${user.id}") + .addSnapshotListener { snapshot, exception -> + if (exception == null && snapshot != null) + this.user.value = snapshot.user + } + userId = user.id + } } return this.user } override fun get(topic: Topic): LiveData> { - if (topic.id != topicId && topic.id.isNotBlank()) + if ( + topic.id != topicId + && topic.id.isNotBlank() + ) db.collection("topics/${topic.id}/users") .orderBy("score", Query.Direction.DESCENDING) .limit(100) @@ -68,7 +80,10 @@ class UsersService : UsersInterface { } fun updateRegistrationToken(token: String, onCompleteListener: (Task?) -> Unit) { - if (token.isNotBlank() && auth.currentUser != null) { + if ( + token.isNotBlank() + && auth.currentUser != null + ) { val data = mapOf("registrationToken" to token) db.document("users/${auth.currentUser!!.uid}") .set(data, SetOptions.merge()) diff --git a/app/src/main/java/io/github/horaciocome1/reaque/services/MyFirebaseMessagingService.kt b/app/src/main/java/io/github/horaciocome1/reaque/services/MyFirebaseMessagingService.kt index 7aad843..70d4773 100644 --- a/app/src/main/java/io/github/horaciocome1/reaque/services/MyFirebaseMessagingService.kt +++ b/app/src/main/java/io/github/horaciocome1/reaque/services/MyFirebaseMessagingService.kt @@ -28,18 +28,32 @@ class MyFirebaseMessagingService : FirebaseMessagingService() { val title = remoteMessage.notification!!.title val body = remoteMessage.notification!!.body val clickAction = remoteMessage.notification!!.clickAction - if (userId != null && title != null && body != null && clickAction != null) - if (userId.isNotBlank() && title.isNotBlank() && body.isNotBlank() && clickAction.isNotBlank()) - sendNotification(title, body, clickAction, userId) + if ( + userId != null + && userId.isNotBlank() + && title != null + && title.isNotBlank() + && body != null + && body.isNotBlank() + && clickAction != null + && clickAction.isNotBlank() + ) + sendNotification(title, body, clickAction, userId) } } - private fun sendNotification(title: String, body: String, clickAction: String, userId: String) { + private fun sendNotification( + title: String, + body: String, + clickAction: String, + userId: String + ) { val intent = Intent(this, MainActivity::class.java) intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP) if (clickAction == MAIN_ACTIVITY) intent.putExtra(USER_ID, userId) - val pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_ONE_SHOT) + val pendingIntent = PendingIntent + .getActivity(this, 0, intent, PendingIntent.FLAG_ONE_SHOT) val sound = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION) val notification = NotificationCompat.Builder(this, resources.getString(R.string.default_notification_channel_id)) diff --git a/app/src/main/java/io/github/horaciocome1/reaque/ui/MainActivity.kt b/app/src/main/java/io/github/horaciocome1/reaque/ui/MainActivity.kt index 54dc8de..1b11961 100644 --- a/app/src/main/java/io/github/horaciocome1/reaque/ui/MainActivity.kt +++ b/app/src/main/java/io/github/horaciocome1/reaque/ui/MainActivity.kt @@ -24,7 +24,9 @@ import android.view.Menu import android.view.MenuItem import android.view.View import androidx.appcompat.app.AppCompatActivity +import androidx.databinding.DataBindingUtil import androidx.navigation.NavController +import androidx.navigation.NavDestination import androidx.navigation.Navigation import androidx.navigation.ui.NavigationUI import com.google.android.gms.common.ConnectionResult @@ -32,27 +34,37 @@ import com.google.android.gms.common.GoogleApiAvailability import com.google.firebase.auth.FirebaseAuth import io.github.horaciocome1.reaque.R import io.github.horaciocome1.reaque.data.users.User +import io.github.horaciocome1.reaque.databinding.ActivityMainBinding import io.github.horaciocome1.reaque.util.Constants.USER_ID import io.github.horaciocome1.reaque.util.handleDynamicLinks import kotlinx.android.synthetic.main.activity_main.* -class MainActivity : AppCompatActivity() { +class MainActivity : AppCompatActivity(), NavController.OnDestinationChangedListener { - private lateinit var navController: NavController - private lateinit var auth: FirebaseAuth + private val navController: NavController by lazy { + Navigation.findNavController(this, R.id.nav_host_fragment) + } + private val auth: FirebaseAuth by lazy { + FirebaseAuth.getInstance() + } private var passedThroughSignIn = false + private val isOrientationPortrait: Boolean + get() { + return resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT + } + + private lateinit var binding: ActivityMainBinding + override fun onCreate(savedInstanceState: Bundle?) { setTheme(R.style.AppTheme) super.onCreate(savedInstanceState) + binding = DataBindingUtil.setContentView(this, R.layout.activity_main) setContentView(R.layout.activity_main) checkGoogleApiAvailability() - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) - window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR + setLightStatusBar() setSupportActionBar(toolbar) supportActionBar?.setDisplayShowTitleEnabled(false) - navController = Navigation.findNavController(this, R.id.nav_host_fragment) - auth = FirebaseAuth.getInstance() setupNavigation() handleNotifications { val bundle = Bundle().apply { @@ -105,30 +117,19 @@ class MainActivity : AppCompatActivity() { override fun onSupportNavigateUp() = NavigationUI.navigateUp(navController, drawerlayout) - private fun setupNavigation() { - bottomnavigationview?.let { NavigationUI.setupWithNavController(it, navController) } - navigationview?.let { NavigationUI.setupWithNavController(it, navController) } - NavigationUI.setupActionBarWithNavController(this, navController, drawerlayout) - navController.addOnDestinationChangedListener(onDestinationChangedListener) - } - - private fun checkGoogleApiAvailability() { - val instance = GoogleApiAvailability.getInstance() - val code = instance.isGooglePlayServicesAvailable(this) - if (code != ConnectionResult.SUCCESS) - instance.makeGooglePlayServicesAvailable(this) - } - - private val isOrientationPortrait: Boolean - get() { - return resources.configuration.orientation == Configuration.ORIENTATION_PORTRAIT - } - - private val onDestinationChangedListener = NavController.OnDestinationChangedListener { _, destination, _ -> + override fun onDestinationChanged( + controller: NavController, + destination: NavDestination, + arguments: Bundle? + ) { title_textview.text = destination.label if (destination.id == R.id.destination_sign_in) passedThroughSignIn = true - if (destination.id != R.id.destination_sign_in && auth.currentUser == null && passedThroughSignIn) { + if ( + destination.id != R.id.destination_sign_in + && auth.currentUser == null + && passedThroughSignIn + ) { passedThroughSignIn = false finish() } @@ -141,11 +142,18 @@ class MainActivity : AppCompatActivity() { else -> show() } } - if (destination.id == R.id.destination_set_rating || destination.id == R.id.destination_create_post - || destination.id == R.id.destination_update_user || destination.id == R.id.destination_sign_in + if ( + destination.id == R.id.destination_set_rating + || destination.id == R.id.destination_create_post + || destination.id == R.id.destination_update_user + || destination.id == R.id.destination_sign_in ) supportActionBar?.hide() - if (destination.id != R.id.destination_feed && destination.id != R.id.destination_explore && destination.id != R.id.destination_more) { + if ( + destination.id != R.id.destination_feed + && destination.id != R.id.destination_explore + && destination.id != R.id.destination_more + ) { bottomnavigationview?.visibility = View.GONE divider6?.visibility = View.GONE } else { @@ -154,4 +162,27 @@ class MainActivity : AppCompatActivity() { } } + private fun setupNavigation() { + bottomnavigationview?.let { + NavigationUI.setupWithNavController(it, navController) + } + navigationview?.let { + NavigationUI.setupWithNavController(it, navController) + } + NavigationUI.setupActionBarWithNavController(this, navController, drawerlayout) + navController.addOnDestinationChangedListener(this) + } + + private fun checkGoogleApiAvailability() { + val instance = GoogleApiAvailability.getInstance() + val code = instance.isGooglePlayServicesAvailable(this) + if (code != ConnectionResult.SUCCESS) + instance.makeGooglePlayServicesAvailable(this) + } + + private fun setLightStatusBar() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) + window.decorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR + } + } diff --git a/app/src/main/java/io/github/horaciocome1/reaque/ui/explore/ExploreFragment.kt b/app/src/main/java/io/github/horaciocome1/reaque/ui/explore/ExploreFragment.kt index 8ad9cb9..3cd9351 100644 --- a/app/src/main/java/io/github/horaciocome1/reaque/ui/explore/ExploreFragment.kt +++ b/app/src/main/java/io/github/horaciocome1/reaque/ui/explore/ExploreFragment.kt @@ -22,7 +22,11 @@ class ExploreFragment : Fragment() { ViewModelProviders.of(this, factory)[ExploreViewModel::class.java] } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { binding = FragmentExploreBinding.inflate(inflater, container, false) return binding.root } @@ -36,8 +40,14 @@ class ExploreFragment : Fragment() { override fun onStart() { super.onStart() - viewModel.notEmptyTopics.observe(this, Observer { binding.viewmodel = viewModel.setTopics(it) }) - viewModel.top10.observe(this, Observer { binding.viewmodel = viewModel.setPosts(it) }) + viewModel.notEmptyTopics + .observe(this, Observer { + binding.viewmodel = viewModel.setTopics(it) + }) + viewModel.top10 + .observe(this, Observer { + binding.viewmodel = viewModel.setPosts(it) + }) } } \ No newline at end of file diff --git a/app/src/main/java/io/github/horaciocome1/reaque/ui/explore/ExploreViewModel.kt b/app/src/main/java/io/github/horaciocome1/reaque/ui/explore/ExploreViewModel.kt index 4800aa0..68b61d8 100644 --- a/app/src/main/java/io/github/horaciocome1/reaque/ui/explore/ExploreViewModel.kt +++ b/app/src/main/java/io/github/horaciocome1/reaque/ui/explore/ExploreViewModel.kt @@ -21,24 +21,28 @@ class ExploreViewModel(topicsRepository: TopicsRepository, postsRepository: Post val onItemPostClickListener: (View, Int) -> Unit = { view, position -> if (posts.isNotEmpty()) { - val directions = ExploreFragmentDirections.actionOpenReadPostFromExplore(posts[position].id) - view.findNavController().navigate(directions) + val directions = ExploreFragmentDirections + .actionOpenReadPostFromExplore(posts[position].id) + view.findNavController() + .navigate(directions) } } val onItemTopicClickListener: (View, Int) -> Unit = { view, position -> if (posts.isNotEmpty()) { - val directions = - ExploreFragmentDirections.actionOpenPostsFromExplore(topics[position].id, Constants.TOPIC_POSTS_REQUEST) - view.findNavController().navigate(directions) + val directions = ExploreFragmentDirections + .actionOpenPostsFromExplore(topics[position].id, Constants.TOPIC_POSTS_REQUEST) + view.findNavController() + .navigate(directions) } } val onItemTopicLongPressListener: (View, Int) -> Unit = { view, position -> if (posts.isNotEmpty()) { - val directions = - ExploreFragmentDirections.actionOpenUsersFromExplore(topics[position].id, Constants.TOPIC_USERS_REQUEST) - view.findNavController().navigate(directions) + val directions = ExploreFragmentDirections + .actionOpenUsersFromExplore(topics[position].id, Constants.TOPIC_USERS_REQUEST) + view.findNavController() + .navigate(directions) } } diff --git a/app/src/main/java/io/github/horaciocome1/reaque/ui/explore/TopicsAdapter.kt b/app/src/main/java/io/github/horaciocome1/reaque/ui/explore/TopicsAdapter.kt index a1757c2..3d2822e 100644 --- a/app/src/main/java/io/github/horaciocome1/reaque/ui/explore/TopicsAdapter.kt +++ b/app/src/main/java/io/github/horaciocome1/reaque/ui/explore/TopicsAdapter.kt @@ -22,12 +22,14 @@ import androidx.recyclerview.widget.RecyclerView import io.github.horaciocome1.reaque.data.topics.Topic import io.github.horaciocome1.reaque.databinding.ItemTopicBinding -class TopicsAdapter(private val list: List) : RecyclerView.Adapter() { +class TopicsAdapter(private val list: List) : + RecyclerView.Adapter() { private lateinit var binding: ItemTopicBinding override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { - binding = ItemTopicBinding.inflate(LayoutInflater.from(parent.context), parent, false) + val inflater = LayoutInflater.from(parent.context) + binding = ItemTopicBinding.inflate(inflater, parent, false) return ViewHolder(binding.root) } diff --git a/app/src/main/java/io/github/horaciocome1/reaque/ui/feed/FeedFragment.kt b/app/src/main/java/io/github/horaciocome1/reaque/ui/feed/FeedFragment.kt index c2c0dda..1059154 100644 --- a/app/src/main/java/io/github/horaciocome1/reaque/ui/feed/FeedFragment.kt +++ b/app/src/main/java/io/github/horaciocome1/reaque/ui/feed/FeedFragment.kt @@ -21,7 +21,11 @@ class FeedFragment : Fragment() { ViewModelProviders.of(this, factory)[FeedViewModel::class.java] } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { binding = FragmentFeedBinding.inflate(inflater, container, false) return binding.root } @@ -33,9 +37,10 @@ class FeedFragment : Fragment() { override fun onStart() { super.onStart() - viewModel.get().observe(this, Observer { - binding.viewmodel = viewModel.setPosts(it) - }) + viewModel.feed + .observe(this, Observer { + binding.viewmodel = viewModel.setPosts(it) + }) } } \ No newline at end of file diff --git a/app/src/main/java/io/github/horaciocome1/reaque/ui/feed/FeedViewModel.kt b/app/src/main/java/io/github/horaciocome1/reaque/ui/feed/FeedViewModel.kt index fff3999..cc77d0a 100644 --- a/app/src/main/java/io/github/horaciocome1/reaque/ui/feed/FeedViewModel.kt +++ b/app/src/main/java/io/github/horaciocome1/reaque/ui/feed/FeedViewModel.kt @@ -6,14 +6,18 @@ import androidx.navigation.findNavController import io.github.horaciocome1.reaque.data.feed.FeedRepository import io.github.horaciocome1.reaque.data.posts.Post -class FeedViewModel(private val repository: FeedRepository) : ViewModel() { +class FeedViewModel(repository: FeedRepository) : ViewModel() { var posts: List = mutableListOf() + val feed = repository.get() + val onItemClickListener: (View, Int) -> Unit = { view, position -> if (posts.isNotEmpty()) { - val directions = FeedFragmentDirections.actionOpenReadPostFromFeed(posts[position].id) - view.findNavController().navigate(directions) + val directions = FeedFragmentDirections + .actionOpenReadPostFromFeed(posts[position].id) + view.findNavController() + .navigate(directions) } } @@ -22,6 +26,4 @@ class FeedViewModel(private val repository: FeedRepository) : ViewModel() { return this } - fun get() = repository.get() - } \ No newline at end of file diff --git a/app/src/main/java/io/github/horaciocome1/reaque/ui/feed/FeedViewModelFactory.kt b/app/src/main/java/io/github/horaciocome1/reaque/ui/feed/FeedViewModelFactory.kt index 2383b83..42a5f4b 100644 --- a/app/src/main/java/io/github/horaciocome1/reaque/ui/feed/FeedViewModelFactory.kt +++ b/app/src/main/java/io/github/horaciocome1/reaque/ui/feed/FeedViewModelFactory.kt @@ -4,7 +4,8 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import io.github.horaciocome1.reaque.data.feed.FeedRepository -class FeedViewModelFactory(private val repository: FeedRepository) : ViewModelProvider.NewInstanceFactory() { +class FeedViewModelFactory(private val repository: FeedRepository) : + ViewModelProvider.NewInstanceFactory() { @Suppress("UNCHECKED_CAST") override fun create(modelClass: Class): T = FeedViewModel(repository) as T diff --git a/app/src/main/java/io/github/horaciocome1/reaque/ui/more/MoreFragment.kt b/app/src/main/java/io/github/horaciocome1/reaque/ui/more/MoreFragment.kt index 89e6d7b..28ebefa 100644 --- a/app/src/main/java/io/github/horaciocome1/reaque/ui/more/MoreFragment.kt +++ b/app/src/main/java/io/github/horaciocome1/reaque/ui/more/MoreFragment.kt @@ -15,8 +15,6 @@ package io.github.horaciocome1.reaque.ui.more -import android.content.Intent -import android.net.Uri import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -40,7 +38,11 @@ class MoreFragment : Fragment() { ViewModelProviders.of(this, factory)[MoreViewModel::class.java] } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { binding = FragmentMoreBinding.inflate(inflater, container, false) return binding.root } @@ -50,33 +52,25 @@ class MoreFragment : Fragment() { binding.viewmodel = viewModel licenses_textview.setOnClickListener { val url = getString(R.string.url_licence) - val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) - startActivity(intent) + viewModel.openUrl(this, url) } about_textview.setOnClickListener { val url = getString(R.string.url_about) - val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) - startActivity(intent) + viewModel.openUrl(this, url) } privacy_policy_textview.setOnClickListener { val url = getString(R.string.url_privacy_policy) - val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) - startActivity(intent) + viewModel.openUrl(this, url) } terms_and_conditions_textview.setOnClickListener { val url = getString(R.string.url_terms_and_conditions) - val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) - startActivity(intent) + viewModel.openUrl(this, url) } - feedback_textview.setOnClickListener { + feedback_textview.setOnClickListener { textView -> val email = getString(R.string.email_developer) - try { - val mailto = "mailto:$email" - val emailIntent = Intent(Intent.ACTION_SENDTO) - emailIntent.data = Uri.parse(mailto) - startActivity(emailIntent) - } catch (exception: Exception) { - Toast.makeText(it.context, R.string.email_app_not_found, Toast.LENGTH_LONG) + viewModel.sendEmail(this, email) { + val message = R.string.email_app_not_found + Toast.makeText(textView.context, message, Toast.LENGTH_LONG) .show() } } @@ -87,8 +81,7 @@ class MoreFragment : Fragment() { } update_textview.setOnClickListener { val url = getString(R.string.url_update) - val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) - startActivity(intent) + viewModel.openUrl(this, url) } } diff --git a/app/src/main/java/io/github/horaciocome1/reaque/ui/more/MoreViewModel.kt b/app/src/main/java/io/github/horaciocome1/reaque/ui/more/MoreViewModel.kt index ef82dcc..f3d116b 100644 --- a/app/src/main/java/io/github/horaciocome1/reaque/ui/more/MoreViewModel.kt +++ b/app/src/main/java/io/github/horaciocome1/reaque/ui/more/MoreViewModel.kt @@ -15,6 +15,8 @@ package io.github.horaciocome1.reaque.ui.more +import android.content.Intent +import android.net.Uri import android.view.View import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData @@ -49,10 +51,6 @@ class MoreViewModel( return _user } - init { - _user.value - } - val hasBookmarks = bookmarksRepository.hasBookmarks() val isUpdateAvailable = configurationsRepository.isUpdateAvailable() @@ -61,31 +59,53 @@ class MoreViewModel( fun openUpdateUser(view: View) { auth.currentUser?.let { - val directions = MoreFragmentDirections.actionOpenUpdateUserFromMore(it.uid) - view.findNavController().navigate(directions) + val directions = MoreFragmentDirections + .actionOpenUpdateUserFromMore(it.uid) + view.findNavController() + .navigate(directions) } } fun openCreatePost(view: View) { if (auth.currentUser != null) { - val directions = MoreFragmentDirections.actionOpenCreatePostFromMore() - view.findNavController().navigate(directions) + val directions = MoreFragmentDirections + .actionOpenCreatePostFromMore() + view.findNavController() + .navigate(directions) } } fun openUserProfile(view: View) { auth.currentUser?.let { - val directions = MoreFragmentDirections.actionOpenUserProfileFromMore(it.uid) - view.findNavController().navigate(directions) + val directions = MoreFragmentDirections + .actionOpenUserProfileFromMore(it.uid) + view.findNavController() + .navigate(directions) } } fun openBookmarks(view: View) { auth.currentUser?.let { - val directions = MoreFragmentDirections.actionOpenPostsFromMore( - it.uid, Constants.BOOKMARKS_REQUEST - ) - view.findNavController().navigate(directions) + val directions = MoreFragmentDirections + .actionOpenPostsFromMore(it.uid, Constants.BOOKMARKS_REQUEST) + view.findNavController() + .navigate(directions) + } + } + + fun openUrl(fragment: MoreFragment, url: String) { + val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url)) + fragment.startActivity(intent) + } + + fun sendEmail(fragment: MoreFragment, email: String, onFailureListener: (Exception) -> Unit) { + try { + val mailto = "mailto:$email" + val emailIntent = Intent(Intent.ACTION_SENDTO) + emailIntent.data = Uri.parse(mailto) + fragment.startActivity(emailIntent) + } catch (exception: Exception) { + onFailureListener(exception) } } diff --git a/app/src/main/java/io/github/horaciocome1/reaque/ui/posts/PostsAdapter.kt b/app/src/main/java/io/github/horaciocome1/reaque/ui/posts/PostsAdapter.kt index 4098be2..8d51618 100644 --- a/app/src/main/java/io/github/horaciocome1/reaque/ui/posts/PostsAdapter.kt +++ b/app/src/main/java/io/github/horaciocome1/reaque/ui/posts/PostsAdapter.kt @@ -28,7 +28,8 @@ class PostsAdapter(private val list: List) : RecyclerView.Adapter) : RecyclerView.Adapter) : RecyclerView.Adapter() { + class SuggestionsAdapter(private val list: List) : + RecyclerView.Adapter() { private lateinit var binding: ItemSuggestionBinding override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { - binding = ItemSuggestionBinding.inflate(LayoutInflater.from(parent.context), parent, false) + val inflater = LayoutInflater.from(parent.context) + binding = ItemSuggestionBinding.inflate(inflater, parent, false) return ViewHolder(binding.root) } diff --git a/app/src/main/java/io/github/horaciocome1/reaque/ui/posts/PostsFragment.kt b/app/src/main/java/io/github/horaciocome1/reaque/ui/posts/PostsFragment.kt index 020f501..90ef53c 100644 --- a/app/src/main/java/io/github/horaciocome1/reaque/ui/posts/PostsFragment.kt +++ b/app/src/main/java/io/github/horaciocome1/reaque/ui/posts/PostsFragment.kt @@ -14,14 +14,18 @@ import kotlinx.android.synthetic.main.fragment_posts.* class PostsFragment : Fragment() { - lateinit var binding: FragmentPostsBinding + private lateinit var binding: FragmentPostsBinding private val viewModel: PostsViewModel by lazy { val factory = InjectorUtils.postsViewModelFactory ViewModelProviders.of(this, factory)[PostsViewModel::class.java] } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { binding = FragmentPostsBinding.inflate(inflater, container, false) return binding.root } @@ -36,7 +40,9 @@ class PostsFragment : Fragment() { arguments?.let { bundle -> val args = PostsFragmentArgs.fromBundle(bundle) viewModel.get(args.parentId, args.requestId) - .observe(this, Observer { binding.viewmodel = viewModel.setPosts(it) }) + .observe(this, Observer { + binding.viewmodel = viewModel.setPosts(it) + }) } } diff --git a/app/src/main/java/io/github/horaciocome1/reaque/ui/posts/PostsViewModel.kt b/app/src/main/java/io/github/horaciocome1/reaque/ui/posts/PostsViewModel.kt index 4e9e402..7dec6b4 100644 --- a/app/src/main/java/io/github/horaciocome1/reaque/ui/posts/PostsViewModel.kt +++ b/app/src/main/java/io/github/horaciocome1/reaque/ui/posts/PostsViewModel.kt @@ -4,44 +4,26 @@ import android.view.View import androidx.lifecycle.LiveData import androidx.lifecycle.ViewModel import androidx.navigation.findNavController -import com.google.android.material.button.MaterialButton -import com.google.firebase.dynamiclinks.FirebaseDynamicLinks import io.github.horaciocome1.reaque.data.bookmarks.BookmarksRepository import io.github.horaciocome1.reaque.data.posts.Post import io.github.horaciocome1.reaque.data.posts.PostsRepository -import io.github.horaciocome1.reaque.data.ratings.RatingsRepository -import io.github.horaciocome1.reaque.data.readings.ReadingsRepository -import io.github.horaciocome1.reaque.data.shares.SharesRepository import io.github.horaciocome1.reaque.data.topics.Topic import io.github.horaciocome1.reaque.data.users.User import io.github.horaciocome1.reaque.util.Constants -import io.github.horaciocome1.reaque.util.buildShortDynamicLink class PostsViewModel( private val postsRepository: PostsRepository, - private val readingsRepository: ReadingsRepository, - private val sharesRepository: SharesRepository, - private val ratingsRepository: RatingsRepository, private val bookmarksRepository: BookmarksRepository ) : ViewModel() { - private val dynamicLinks: FirebaseDynamicLinks by lazy { - FirebaseDynamicLinks.getInstance() - } - - var readingPost = Post("") - set(value) { - if (value.id.isNotBlank() && value.id != field.id) - readingsRepository.read(value) { } - field = value - } - var posts: List = mutableListOf() val onItemClickListener: (View, Int) -> Unit = { view, position -> if (posts.isNotEmpty()) { - val directions = PostsFragmentDirections.actionOpenReadPostFromPosts(posts[position].id) - view.findNavController().navigate(directions) + val directions = PostsFragmentDirections + .actionOpenReadPostFromPosts(posts[position].id) + view.findNavController() + .navigate(directions) } } @@ -59,51 +41,4 @@ class PostsViewModel( } } - - fun get(post: Post) = postsRepository.get(post) - - fun getRating(post: Post) = ratingsRepository.get(post) - - fun setRating(view: View, post: Post, value: Int) { - ratingsRepository.set(post, value) { } - navigateUp(view) - } - - fun share(fragment: ReadPostFragment, view: View, post: Post) { - view.isEnabled = false - dynamicLinks.buildShortDynamicLink(post) { - if (fragment.isVisible) { - fragment.startActivity(it) - sharesRepository.share(post) { view.isEnabled = true } - } - } - } - - fun bookmark(view: View, post: Post) { - view.isEnabled = false - bookmarksRepository.bookmark(post) { view.visibility = View.GONE } - } - - fun unBookmark(view: View, post: Post) { - view.isEnabled = false - bookmarksRepository.unBookmark(post) { view.visibility = View.GONE } - } - - fun isBookmarked(post: Post) = bookmarksRepository.isBookmarked(post) - - fun openUserProfile(view: View, user: User) { - val directions = ReadPostFragmentDirections.actionOpenUserProfileFromReadPost(user.id) - view.findNavController().navigate(directions) - } - - fun openSetRating(view: View, post: Post) { - if (view is MaterialButton) { - val rating = view.text.toString().toInt() - val directions = ReadPostFragmentDirections.actionOpenSetRating(post.id, rating) - view.findNavController().navigate(directions) - } - } - - fun navigateUp(view: View) = view.findNavController().navigateUp() - } \ No newline at end of file diff --git a/app/src/main/java/io/github/horaciocome1/reaque/ui/posts/PostsViewModelFactory.kt b/app/src/main/java/io/github/horaciocome1/reaque/ui/posts/PostsViewModelFactory.kt index 56d7214..023b4bd 100644 --- a/app/src/main/java/io/github/horaciocome1/reaque/ui/posts/PostsViewModelFactory.kt +++ b/app/src/main/java/io/github/horaciocome1/reaque/ui/posts/PostsViewModelFactory.kt @@ -4,20 +4,13 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import io.github.horaciocome1.reaque.data.bookmarks.BookmarksRepository import io.github.horaciocome1.reaque.data.posts.PostsRepository -import io.github.horaciocome1.reaque.data.ratings.RatingsRepository -import io.github.horaciocome1.reaque.data.readings.ReadingsRepository -import io.github.horaciocome1.reaque.data.shares.SharesRepository class PostsViewModelFactory( private val postsRepository: PostsRepository, - private val readingsRepository: ReadingsRepository, - private val sharesRepository: SharesRepository, - private val ratingsRepository: RatingsRepository, private val bookmarksRepository: BookmarksRepository ) : ViewModelProvider.NewInstanceFactory() { @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class) = PostsViewModel( - postsRepository, readingsRepository, sharesRepository, ratingsRepository, bookmarksRepository - ) as T + override fun create(modelClass: Class) = + PostsViewModel(postsRepository, bookmarksRepository) as T } \ No newline at end of file diff --git a/app/src/main/java/io/github/horaciocome1/reaque/ui/posts/ReadPostFragment.kt b/app/src/main/java/io/github/horaciocome1/reaque/ui/posts/ReadPostFragment.kt deleted file mode 100644 index 925f9f5..0000000 --- a/app/src/main/java/io/github/horaciocome1/reaque/ui/posts/ReadPostFragment.kt +++ /dev/null @@ -1,68 +0,0 @@ -package io.github.horaciocome1.reaque.ui.posts - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.fragment.app.Fragment -import androidx.lifecycle.Observer -import androidx.lifecycle.ViewModelProviders -import io.github.horaciocome1.reaque.data.posts.Post -import io.github.horaciocome1.reaque.databinding.FragmentReadPostBinding -import io.github.horaciocome1.reaque.util.Constants -import io.github.horaciocome1.reaque.util.InjectorUtils -import kotlinx.android.synthetic.main.fragment_read_post.* - -class ReadPostFragment : Fragment() { - - lateinit var binding: FragmentReadPostBinding - - private val viewModel: PostsViewModel by lazy { - val factory = InjectorUtils.postsViewModelFactory - ViewModelProviders.of(this, factory)[PostsViewModel::class.java] - } - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - binding = FragmentReadPostBinding.inflate(inflater, container, false) - return binding.root - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - binding.viewmodel = viewModel - share_button?.setOnClickListener { - binding.post?.let { viewModel.share(this, view, it) } - } - } - - override fun onStart() { - super.onStart() - arguments?.let { bundle -> - val post = Post( - ReadPostFragmentArgs.fromBundle(bundle).postId - ) - viewModel.get(post).observe(this, Observer { - binding.post = it - viewModel.readingPost = it - }) - viewModel.getRating(post).observe(this, Observer { rating_button?.text = it.toString() }) - viewModel.isBookmarked(post).observe(this, Observer { - when (it) { - Constants.States.TRUE -> { - bookmark_button.visibility = View.GONE - unbookmark_button.visibility = View.VISIBLE - } - Constants.States.FALSE -> { - bookmark_button.visibility = View.VISIBLE - unbookmark_button.visibility = View.GONE - } - else -> { - bookmark_button.visibility = View.GONE - unbookmark_button.visibility = View.GONE - } - } - }) - } - } - -} \ No newline at end of file diff --git a/app/src/main/java/io/github/horaciocome1/reaque/ui/posts/create/CreatePostFragment.kt b/app/src/main/java/io/github/horaciocome1/reaque/ui/posts/create/CreatePostFragment.kt index 2db9915..ad6571b 100644 --- a/app/src/main/java/io/github/horaciocome1/reaque/ui/posts/create/CreatePostFragment.kt +++ b/app/src/main/java/io/github/horaciocome1/reaque/ui/posts/create/CreatePostFragment.kt @@ -2,14 +2,15 @@ package io.github.horaciocome1.reaque.ui.posts.create import android.Manifest import android.app.Activity -import android.content.Context.MODE_PRIVATE import android.content.Intent import android.content.pm.PackageManager +import android.net.Uri import android.os.Build import android.os.Bundle import android.view.LayoutInflater import android.view.View import android.view.ViewGroup +import android.widget.ImageView import android.widget.LinearLayout import android.widget.Toast import androidx.appcompat.app.AlertDialog @@ -18,9 +19,7 @@ import androidx.core.content.ContextCompat import androidx.fragment.app.Fragment import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProviders -import androidx.navigation.findNavController import com.google.android.material.bottomsheet.BottomSheetBehavior -import com.google.android.material.card.MaterialCardView import io.github.horaciocome1.reaque.R import io.github.horaciocome1.reaque.databinding.FragmentCreatePostBinding import io.github.horaciocome1.reaque.ui.MainActivity @@ -41,18 +40,20 @@ class CreatePostFragment : Fragment() { private lateinit var selectTopicBehavior: BottomSheetBehavior - private lateinit var selectPicBehavior: BottomSheetBehavior - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { binding = FragmentCreatePostBinding.inflate(inflater, container, false) return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - loadDraft() + activity?.run(viewModel::loadDraft) binding.viewmodel = viewModel - select_pic_from_gallery_button.setOnClickListener { + select_pic_button.setOnClickListener { val permission = ContextCompat.checkSelfPermission( activity as MainActivity, Manifest.permission.READ_EXTERNAL_STORAGE @@ -62,18 +63,16 @@ class CreatePostFragment : Fragment() { else requestStoragePermission() } - OnFocusChangeListener(context).let { - title_edittext?.onFocusChangeListener = it - message_edittext?.onFocusChangeListener = it - } - selectTopicBehavior = BottomSheetBehavior.from(select_topics_bottomsheet).apply { - state = BottomSheetBehavior.STATE_HIDDEN - skipCollapsed = true - } - selectPicBehavior = BottomSheetBehavior.from(select_pic_bottomsheet).apply { - state = BottomSheetBehavior.STATE_HIDDEN - skipCollapsed = true - } + OnFocusChangeListener(context) + .let { + title_edittext?.onFocusChangeListener = it + message_edittext?.onFocusChangeListener = it + } + selectTopicBehavior = BottomSheetBehavior.from(select_topics_bottomsheet) + .apply { + state = BottomSheetBehavior.STATE_HIDDEN + skipCollapsed = true + } topics_recyclerview?.addOnItemClickListener { _, position -> binding.topics?.let { if (it.isNotEmpty()) { @@ -81,50 +80,37 @@ class CreatePostFragment : Fragment() { viewModel.post.topic = it[position] binding.viewmodel = viewModel create_button.isEnabled = viewModel.isPostReady - saveDraft() } } } select_topic_button?.setOnClickListener { selectTopicBehavior.state = BottomSheetBehavior.STATE_HALF_EXPANDED } - select_pic_button?.setOnClickListener { - selectPicBehavior.state = BottomSheetBehavior.STATE_HALF_EXPANDED - } - toolbar?.setNavigationOnClickListener { - viewModel.navigateUp(it) - saveDraft() - } + toolbar?.setNavigationOnClickListener(viewModel.navigateUp) create_button?.setOnClickListener { binding.viewmodel = viewModel.create(it) } - preview_button?.setOnClickListener { - val directions = CreatePostFragmentDirections.actionOpenPreviewMessage( - viewModel.post.message - ) - view.findNavController().navigate(directions) - } } override fun onStart() { super.onStart() - viewModel.title.observe(this, Observer { - saveDraft() - viewModel.post.title = it - create_button.isEnabled = viewModel.isPostReady - }) - viewModel.message.observe(this, Observer { - saveDraft() - viewModel.post.message = it - create_button.isEnabled = viewModel.isPostReady - preview_button.visibility = if (it.isBlank()) - View.GONE - else - View.VISIBLE - }) - viewModel.topics.observe(this, Observer { - binding.topics = it - }) + swipeCover() + viewModel.title + .observe(this, Observer { + activity?.run(viewModel::saveDraft) + viewModel.post.title = it + create_button.isEnabled = viewModel.isPostReady + }) + viewModel.message + .observe(this, Observer { + activity?.run(viewModel::saveDraft) + viewModel.post.message = it + create_button.isEnabled = viewModel.isPostReady + }) + viewModel.topics + .observe(this, Observer { + binding.topics = it + }) } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { @@ -132,21 +118,30 @@ class CreatePostFragment : Fragment() { Activity.RESULT_OK -> { when (requestCode) { Constants.PICK_IMAGE_FROM_GALLERY_REQUEST_CODE -> { - viewModel.imageUri = data?.data!! - binding.viewmodel = viewModel + if (data != null && data.data != null) { + viewModel.imageUri = data.data!! + binding.viewmodel = viewModel + swipeCover() + } } } } } } - override fun onRequestPermissionsResult(requestCode: Int, permissions: Array, grantResults: IntArray) { + override fun onRequestPermissionsResult( + requestCode: Int, permissions: Array, grantResults: IntArray + ) { super.onRequestPermissionsResult(requestCode, permissions, grantResults) if (requestCode == Constants.STORAGE_PERMISSION_CODE) { - if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) + if ( + grantResults.isNotEmpty() + && grantResults[0] == PackageManager.PERMISSION_GRANTED + ) pickImageFromGallery() else - Toast.makeText(context, R.string.permission_denied, Toast.LENGTH_LONG).show() + Toast.makeText(context, R.string.permission_denied, Toast.LENGTH_LONG) + .show() } } @@ -193,43 +188,18 @@ class CreatePostFragment : Fragment() { } } - private fun saveDraft() { - activity?.let { - val postTitle = viewModel.post.title - val postMessage = viewModel.post.message - val topicId = viewModel.post.topic.id - val topicTitle = viewModel.post.topic.title - val sharedPreferences = it.getSharedPreferences(Constants.SharedPreferences.NAME, MODE_PRIVATE) - sharedPreferences.edit().apply { - putString(Constants.SharedPreferences.POST_TITLE, postTitle) - putString(Constants.SharedPreferences.POST_MESSAGE, postMessage) - putString(Constants.SharedPreferences.TOPIC_ID, topicId) - putString(Constants.SharedPreferences.TOPIC_TITLE, topicTitle) - apply() - } - } - } + private fun swipeCover() { - private fun loadDraft() { - activity?.let { activity -> - val sharedPreferences = activity.getSharedPreferences(Constants.SharedPreferences.NAME, MODE_PRIVATE) - sharedPreferences.getString(Constants.SharedPreferences.TOPIC_ID, "")?.let { - if (it.isNotBlank()) - viewModel.post.topic.id = it - } - sharedPreferences.getString(Constants.SharedPreferences.TOPIC_TITLE, "")?.let { - if (it.isNotBlank()) - viewModel.post.topic.title = it - } - sharedPreferences.getString(Constants.SharedPreferences.POST_TITLE, "")?.let { - if (it.isNotBlank()) - viewModel.title.value = it - } - sharedPreferences.getString(Constants.SharedPreferences.POST_MESSAGE, "")?.let { - if (it.isNotBlank()) - viewModel.message.value = it - } + fun ImageView.changeVisibility(visible: Boolean) { + this.visibility = if (visible) + View.VISIBLE + else + View.GONE } + + val visible = viewModel.imageUri != Uri.EMPTY + holder_imageview.changeVisibility(!visible) + cover_imageview.changeVisibility(visible) } } \ No newline at end of file diff --git a/app/src/main/java/io/github/horaciocome1/reaque/ui/posts/create/CreatePostViewModel.kt b/app/src/main/java/io/github/horaciocome1/reaque/ui/posts/create/CreatePostViewModel.kt index af238a2..d0d448c 100644 --- a/app/src/main/java/io/github/horaciocome1/reaque/ui/posts/create/CreatePostViewModel.kt +++ b/app/src/main/java/io/github/horaciocome1/reaque/ui/posts/create/CreatePostViewModel.kt @@ -1,14 +1,17 @@ package io.github.horaciocome1.reaque.ui.posts.create +import android.content.Context import android.net.Uri import android.view.View import androidx.databinding.Bindable +import androidx.fragment.app.FragmentActivity import androidx.lifecycle.MutableLiveData import androidx.navigation.findNavController import io.github.horaciocome1.reaque.data.posts.Post import io.github.horaciocome1.reaque.data.posts.PostsRepository import io.github.horaciocome1.reaque.data.storage.StorageRepository import io.github.horaciocome1.reaque.data.topics.TopicsRepository +import io.github.horaciocome1.reaque.util.Constants import io.github.horaciocome1.reaque.util.ObservableViewModel class CreatePostViewModel( @@ -35,21 +38,59 @@ class CreatePostViewModel( val isPostReady: Boolean get() { post.run { - return title.isNotBlank() && message.isNotBlank() && topic.id.isNotBlank() && imageUri != Uri.EMPTY + return title.isNotBlank() + && message.isNotBlank() + && topic.id.isNotBlank() + && imageUri != Uri.EMPTY } } + val navigateUp: (View) -> Unit = { + it.findNavController() + .navigateUp() + } + fun create(view: View): CreatePostViewModel { isCreatingPost = true storageRepository.uploadImage(imageUri, post.topic) { uri: Uri? -> uri?.let { post.pic = it.toString() - postsRepository.create(post) { navigateUp(view) } + postsRepository.create(post) { + navigateUp(view) + } } } return this } - fun navigateUp(view: View) = view.findNavController().navigateUp() + fun saveDraft(activity: FragmentActivity) { + val sharedPreferences = activity.getSharedPreferences( + Constants.SharedPreferences.NAME, + Context.MODE_PRIVATE + ) + sharedPreferences.edit() + .apply { + putString(Constants.SharedPreferences.POST_TITLE, post.title) + putString(Constants.SharedPreferences.POST_MESSAGE, post.message) + apply() + } + } + + fun loadDraft(activity: FragmentActivity) { + val sharedPreferences = activity.getSharedPreferences( + Constants.SharedPreferences.NAME, + Context.MODE_PRIVATE + ) + sharedPreferences.getString(Constants.SharedPreferences.POST_TITLE, "") + ?.let { + if (it.isNotBlank()) + title.value = it + } + sharedPreferences.getString(Constants.SharedPreferences.POST_MESSAGE, "") + ?.let { + if (it.isNotBlank()) + message.value = it + } + } } \ No newline at end of file diff --git a/app/src/main/java/io/github/horaciocome1/reaque/ui/posts/create/PreviewMessageFragment.kt b/app/src/main/java/io/github/horaciocome1/reaque/ui/posts/create/PreviewMessageFragment.kt deleted file mode 100644 index 2031580..0000000 --- a/app/src/main/java/io/github/horaciocome1/reaque/ui/posts/create/PreviewMessageFragment.kt +++ /dev/null @@ -1,33 +0,0 @@ -package io.github.horaciocome1.reaque.ui.posts.create - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.fragment.app.Fragment -import io.github.horaciocome1.reaque.R -import io.noties.markwon.Markwon -import kotlinx.android.synthetic.main.fragment_preview_message.* - -class PreviewMessageFragment : Fragment() { - - private val markwon: Markwon? by lazy { - if (context != null) - Markwon.create(context!!) - else - null - } - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - return inflater.inflate(R.layout.fragment_preview_message, container, false) - } - - override fun onStart() { - super.onStart() - arguments?.let { - val message = PreviewMessageFragmentArgs.fromBundle(it).message - markwon?.setMarkdown(message_textview, message) - } - } - -} \ No newline at end of file diff --git a/app/src/main/java/io/github/horaciocome1/reaque/ui/posts/read/ReadPostFragment.kt b/app/src/main/java/io/github/horaciocome1/reaque/ui/posts/read/ReadPostFragment.kt new file mode 100644 index 0000000..b6b6b00 --- /dev/null +++ b/app/src/main/java/io/github/horaciocome1/reaque/ui/posts/read/ReadPostFragment.kt @@ -0,0 +1,77 @@ +package io.github.horaciocome1.reaque.ui.posts.read + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.lifecycle.Observer +import androidx.lifecycle.ViewModelProviders +import io.github.horaciocome1.reaque.data.posts.Post +import io.github.horaciocome1.reaque.databinding.FragmentReadPostBinding +import io.github.horaciocome1.reaque.util.Constants +import io.github.horaciocome1.reaque.util.InjectorUtils +import kotlinx.android.synthetic.main.fragment_read_post.* + +class ReadPostFragment : Fragment() { + + lateinit var binding: FragmentReadPostBinding + + private val viewModel: ReadPostViewModel by lazy { + val factory = InjectorUtils.readPostViewModelFactory + ViewModelProviders.of(this, factory)[ReadPostViewModel::class.java] + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + binding = FragmentReadPostBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding.viewmodel = viewModel + share_button?.setOnClickListener { + binding.post + ?.let { + viewModel.share(this, view, it) + } + } + } + + override fun onStart() { + super.onStart() + arguments?.let { bundle -> + val post = Post( + ReadPostFragmentArgs.fromBundle(bundle).postId + ) + viewModel.get(post) + .observe(this, Observer { + binding.post = it + viewModel.readingPost = it + }) + viewModel.getRating(post) + .observe(this, Observer { + viewModel.myRating = it + ratingBar2.rating = it.toFloat() + }) + viewModel.isBookmarked(post) + .observe(this, Observer { + when (it) { + Constants.States.TRUE -> { + bookmark_button.visibility = View.GONE + unbookmark_button.visibility = View.VISIBLE + } + Constants.States.FALSE -> { + bookmark_button.visibility = View.VISIBLE + unbookmark_button.visibility = View.GONE + } + else -> { + bookmark_button.visibility = View.GONE + unbookmark_button.visibility = View.GONE + } + } + }) + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/github/horaciocome1/reaque/ui/posts/read/ReadPostViewModel.kt b/app/src/main/java/io/github/horaciocome1/reaque/ui/posts/read/ReadPostViewModel.kt new file mode 100644 index 0000000..ba4e3e1 --- /dev/null +++ b/app/src/main/java/io/github/horaciocome1/reaque/ui/posts/read/ReadPostViewModel.kt @@ -0,0 +1,95 @@ +package io.github.horaciocome1.reaque.ui.posts.read + +import android.view.View +import androidx.lifecycle.ViewModel +import androidx.navigation.findNavController +import com.google.firebase.dynamiclinks.FirebaseDynamicLinks +import io.github.horaciocome1.reaque.data.bookmarks.BookmarksRepository +import io.github.horaciocome1.reaque.data.posts.Post +import io.github.horaciocome1.reaque.data.posts.PostsRepository +import io.github.horaciocome1.reaque.data.ratings.RatingsRepository +import io.github.horaciocome1.reaque.data.readings.ReadingsRepository +import io.github.horaciocome1.reaque.data.shares.SharesRepository +import io.github.horaciocome1.reaque.data.users.User +import io.github.horaciocome1.reaque.util.buildShortDynamicLink + +class ReadPostViewModel( + private val postsRepository: PostsRepository, + private val readingsRepository: ReadingsRepository, + private val sharesRepository: SharesRepository, + private val ratingsRepository: RatingsRepository, + private val bookmarksRepository: BookmarksRepository +) : ViewModel() { + + private val dynamicLinks: FirebaseDynamicLinks by lazy { + FirebaseDynamicLinks.getInstance() + } + + var readingPost = Post("") + set(value) { + if (value.id.isNotBlank() && value.id != field.id) + readingsRepository.read(value) {} + field = value + } + + var posts: List = mutableListOf() + + var myRating = 0 + + val navigateUp: (View) -> Unit = { + it.findNavController() + .navigateUp() + } + + fun get(post: Post) = postsRepository.get(post) + + fun getRating(post: Post) = ratingsRepository.get(post) + + fun setRating(view: View, post: Post, value: Int) { + ratingsRepository.set(post, value) { } + navigateUp(view) + } + + fun share(fragment: ReadPostFragment, view: View, post: Post) { + view.isEnabled = false + dynamicLinks.buildShortDynamicLink(post) { + if (fragment.isVisible) { + fragment.startActivity(it) + sharesRepository.share(post) { + view.isEnabled = true + } + } + } + } + + fun bookmark(view: View, post: Post) { + view.isEnabled = false + bookmarksRepository.bookmark(post) { + view.visibility = View.GONE + } + } + + fun unBookmark(view: View, post: Post) { + view.isEnabled = false + bookmarksRepository.unBookmark(post) { + view.visibility = View.GONE + } + } + + fun isBookmarked(post: Post) = bookmarksRepository.isBookmarked(post) + + fun openUserProfile(view: View, user: User) { + val directions = ReadPostFragmentDirections + .actionOpenUserProfileFromReadPost(user.id) + view.findNavController() + .navigate(directions) + } + + fun openSetRating(view: View, post: Post) { + val directions = ReadPostFragmentDirections + .actionOpenSetRating(post.id, myRating) + view.findNavController() + .navigate(directions) + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/github/horaciocome1/reaque/ui/posts/read/ReadPostViewModelFactory.kt b/app/src/main/java/io/github/horaciocome1/reaque/ui/posts/read/ReadPostViewModelFactory.kt new file mode 100644 index 0000000..391be6a --- /dev/null +++ b/app/src/main/java/io/github/horaciocome1/reaque/ui/posts/read/ReadPostViewModelFactory.kt @@ -0,0 +1,27 @@ +package io.github.horaciocome1.reaque.ui.posts.read + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import io.github.horaciocome1.reaque.data.bookmarks.BookmarksRepository +import io.github.horaciocome1.reaque.data.posts.PostsRepository +import io.github.horaciocome1.reaque.data.ratings.RatingsRepository +import io.github.horaciocome1.reaque.data.readings.ReadingsRepository +import io.github.horaciocome1.reaque.data.shares.SharesRepository + +class ReadPostViewModelFactory( + private val postsRepository: PostsRepository, + private val readingsRepository: ReadingsRepository, + private val sharesRepository: SharesRepository, + private val ratingsRepository: RatingsRepository, + private val bookmarksRepository: BookmarksRepository +) : ViewModelProvider.NewInstanceFactory() { + + @Suppress("UNCHECKED_CAST") + override fun create(modelClass: Class) = ReadPostViewModel( + postsRepository, + readingsRepository, + sharesRepository, + ratingsRepository, + bookmarksRepository + ) as T +} \ No newline at end of file diff --git a/app/src/main/java/io/github/horaciocome1/reaque/ui/posts/SetRatingFragment.kt b/app/src/main/java/io/github/horaciocome1/reaque/ui/posts/read/SetRatingFragment.kt similarity index 65% rename from app/src/main/java/io/github/horaciocome1/reaque/ui/posts/SetRatingFragment.kt rename to app/src/main/java/io/github/horaciocome1/reaque/ui/posts/read/SetRatingFragment.kt index 48c357e..007c7ac 100644 --- a/app/src/main/java/io/github/horaciocome1/reaque/ui/posts/SetRatingFragment.kt +++ b/app/src/main/java/io/github/horaciocome1/reaque/ui/posts/read/SetRatingFragment.kt @@ -1,4 +1,4 @@ -package io.github.horaciocome1.reaque.ui.posts +package io.github.horaciocome1.reaque.ui.posts.read import android.os.Bundle import android.view.LayoutInflater @@ -14,11 +14,11 @@ import kotlin.math.roundToInt class SetRatingFragment : Fragment() { - lateinit var binding: FragmentSetRatingBinding + private lateinit var binding: FragmentSetRatingBinding - private val viewModel: PostsViewModel by lazy { - val factory = InjectorUtils.postsViewModelFactory - ViewModelProviders.of(this, factory)[PostsViewModel::class.java] + private val viewModel: ReadPostViewModel by lazy { + val factory = InjectorUtils.readPostViewModelFactory + ViewModelProviders.of(this, factory)[ReadPostViewModel::class.java] } private var rating = 0 @@ -27,19 +27,23 @@ class SetRatingFragment : Fragment() { private var post = Post("") - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { binding = FragmentSetRatingBinding.inflate(inflater, container, false) return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - toolbar.setNavigationOnClickListener { - viewModel.navigateUp(it) - } + toolbar.setNavigationOnClickListener(viewModel.navigateUp) rating_bar.setOnRatingBarChangeListener { _, rating, fromUser -> - done_button.isEnabled = if (rating != 0f && rating.roundToInt() != defaultRating - && post.id.isNotBlank() && fromUser + done_button.isEnabled = if (rating != 0f + && rating.roundToInt() != defaultRating + && post.id.isNotBlank() + && fromUser ) { this.rating = rating.roundToInt() true @@ -57,7 +61,7 @@ class SetRatingFragment : Fragment() { val args = SetRatingFragmentArgs.fromBundle(bundle) this.post = Post(args.postId) defaultRating = args.rating - rating_bar.rating = args.rating.toFloat() + rating_bar.rating = defaultRating.toFloat() } } diff --git a/app/src/main/java/io/github/horaciocome1/reaque/ui/sign_in/SignInFragment.kt b/app/src/main/java/io/github/horaciocome1/reaque/ui/sign_in/SignInFragment.kt index 3fde1c0..6c53693 100644 --- a/app/src/main/java/io/github/horaciocome1/reaque/ui/sign_in/SignInFragment.kt +++ b/app/src/main/java/io/github/horaciocome1/reaque/ui/sign_in/SignInFragment.kt @@ -6,39 +6,42 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment +import androidx.lifecycle.ViewModelProviders import androidx.navigation.findNavController import com.google.android.gms.auth.api.signin.GoogleSignIn -import com.google.android.gms.auth.api.signin.GoogleSignInAccount -import com.google.android.gms.auth.api.signin.GoogleSignInOptions import com.google.android.gms.common.api.ApiException -import com.google.android.material.snackbar.Snackbar -import com.google.firebase.auth.FirebaseAuth -import com.google.firebase.auth.GoogleAuthProvider -import io.github.horaciocome1.reaque.R import io.github.horaciocome1.reaque.databinding.FragmentSignInBinding -import io.github.horaciocome1.reaque.ui.MainActivity import io.github.horaciocome1.reaque.util.Constants +import io.github.horaciocome1.reaque.util.InjectorUtils import kotlinx.android.synthetic.main.fragment_sign_in.* class SignInFragment : Fragment() { - private lateinit var auth: FirebaseAuth + private val viewModel: SignInViewModel by lazy { + val factory = InjectorUtils.signInViewModelFactory + ViewModelProviders.of(this, factory)[SignInViewModel::class.java] + } + private lateinit var binding: FragmentSignInBinding - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { binding = FragmentSignInBinding.inflate(inflater, container, false) return binding.rootView } + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - auth = FirebaseAuth.getInstance() - binding.url = getString(R.string.url_background_sign_in) - } - - override fun onStart() { - super.onStart() - signInWithGoogle() + sign_in_button?.setOnClickListener { + progressBar?.visibility = View.VISIBLE + viewModel.signInWithGoogle(this) + it.visibility = View.GONE + } + progressBar.visibility = View.GONE } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { @@ -48,42 +51,25 @@ class SignInFragment : Fragment() { val task = GoogleSignIn.getSignedInAccountFromIntent(data) try { val account = task.getResult(ApiException::class.java) - account?.let { firebaseAuthWithGoogle(account) } + if (account != null) + viewModel.firebaseAuthWithGoogle(account) { + if (it is Exception) + reset() + else + sign_in_button?.findNavController() + ?.navigateUp() + } } catch (exception: ApiException) { - retry() + reset() } } } } - private fun signInWithGoogle() { - val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN) - .requestIdToken(getString(R.string.default_web_client_id)) - .requestEmail() - .build() - if (activity is MainActivity) { - val googleSignInClient = GoogleSignIn.getClient(activity as MainActivity, gso) - startActivityForResult(googleSignInClient?.signInIntent, Constants.GOOGLE_SIGN_IN_REQUEST_CODE) - } - } - - private fun firebaseAuthWithGoogle(account: GoogleSignInAccount) { - val credential = GoogleAuthProvider.getCredential(account.idToken, null) - auth.signInWithCredential(credential) - .addOnSuccessListener { root_view?.findNavController()?.navigateUp() } - .addOnFailureListener { retry() } - } - - private fun retry() { - progressbar?.visibility = View.GONE - root_view?.let { - val snackBar = Snackbar - .make(it, "Ocorreu um erro inexperado! Voltar a tentar?", Snackbar.LENGTH_INDEFINITE) - snackBar.setAction("Tentar") { - signInWithGoogle() - progressbar?.visibility = View.VISIBLE - }.show() - } + private fun reset() { + sign_in_button?.visibility = View.VISIBLE + error_textview?.visibility = View.VISIBLE + progressBar?.visibility = View.GONE } } \ No newline at end of file diff --git a/app/src/main/java/io/github/horaciocome1/reaque/ui/sign_in/SignInViewModel.kt b/app/src/main/java/io/github/horaciocome1/reaque/ui/sign_in/SignInViewModel.kt new file mode 100644 index 0000000..1ae4f3d --- /dev/null +++ b/app/src/main/java/io/github/horaciocome1/reaque/ui/sign_in/SignInViewModel.kt @@ -0,0 +1,45 @@ +package io.github.horaciocome1.reaque.ui.sign_in + +import androidx.lifecycle.ViewModel +import com.google.android.gms.auth.api.signin.GoogleSignIn +import com.google.android.gms.auth.api.signin.GoogleSignInAccount +import com.google.android.gms.auth.api.signin.GoogleSignInOptions +import com.google.firebase.auth.FirebaseAuth +import com.google.firebase.auth.GoogleAuthProvider +import io.github.horaciocome1.reaque.R +import io.github.horaciocome1.reaque.ui.MainActivity +import io.github.horaciocome1.reaque.util.Constants + +class SignInViewModel : ViewModel() { + + private val auth: FirebaseAuth by lazy { + FirebaseAuth.getInstance() + } + + fun signInWithGoogle(fragment: SignInFragment) { + val activity = fragment.activity + if (activity is MainActivity) { + val gso = GoogleSignInOptions.Builder(GoogleSignInOptions.DEFAULT_SIGN_IN) + .requestIdToken(activity.getString(R.string.default_web_client_id)) + .requestEmail() + .build() + val googleSignInClient = GoogleSignIn.getClient(activity, gso) + fragment.startActivityForResult( + googleSignInClient?.signInIntent, + Constants.GOOGLE_SIGN_IN_REQUEST_CODE + ) + } + } + + fun firebaseAuthWithGoogle( + account: GoogleSignInAccount, + listener: (Any) -> Unit + ) { + val credential = GoogleAuthProvider.getCredential(account.idToken, null) + auth.signInWithCredential(credential) + .addOnSuccessListener(listener) + .addOnFailureListener(listener) + } + + +} \ No newline at end of file diff --git a/app/src/main/java/io/github/horaciocome1/reaque/ui/sign_in/SignInViewModelFactory.kt b/app/src/main/java/io/github/horaciocome1/reaque/ui/sign_in/SignInViewModelFactory.kt new file mode 100644 index 0000000..4d2b400 --- /dev/null +++ b/app/src/main/java/io/github/horaciocome1/reaque/ui/sign_in/SignInViewModelFactory.kt @@ -0,0 +1,10 @@ +package io.github.horaciocome1.reaque.ui.sign_in + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider + +class SignInViewModelFactory : ViewModelProvider.NewInstanceFactory() { + + @Suppress("UNCHECKED_CAST") + override fun create(modelClass: Class) = SignInViewModel() as T +} \ No newline at end of file diff --git a/app/src/main/java/io/github/horaciocome1/reaque/ui/users/UserProfileFragment.kt b/app/src/main/java/io/github/horaciocome1/reaque/ui/users/UserProfileFragment.kt deleted file mode 100644 index a394cac..0000000 --- a/app/src/main/java/io/github/horaciocome1/reaque/ui/users/UserProfileFragment.kt +++ /dev/null @@ -1,70 +0,0 @@ -package io.github.horaciocome1.reaque.ui.users - -import android.os.Bundle -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import androidx.fragment.app.Fragment -import androidx.lifecycle.Observer -import androidx.lifecycle.ViewModelProviders -import io.github.horaciocome1.reaque.data.users.User -import io.github.horaciocome1.reaque.databinding.FragmentUserProfileBinding -import io.github.horaciocome1.reaque.util.Constants -import io.github.horaciocome1.reaque.util.InjectorUtils -import kotlinx.android.synthetic.main.fragment_user_profile.* - -class UserProfileFragment : Fragment() { - - lateinit var binding: FragmentUserProfileBinding - - private val viewModel: UsersViewModel by lazy { - val factory = InjectorUtils.usersViewModelFactory - ViewModelProviders.of(this, factory)[UsersViewModel::class.java] - } - - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - binding = FragmentUserProfileBinding.inflate(inflater, container, false) - return binding.root - } - - override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - super.onViewCreated(view, savedInstanceState) - binding.viewmodel = viewModel - } - - override fun onStart() { - super.onStart() - arguments?.let { bundle -> - val user = User( - UserProfileFragmentArgs.fromBundle(bundle).userId - ) - viewModel.get(user).observe(this, Observer { - binding.user = it - }) - viewModel.amSubscribedTo(user).observe(this, Observer { - when (it) { - Constants.States.TRUE -> { - subscribe_button.visibility = View.GONE - unsubscribe_button.visibility = View.VISIBLE - } - Constants.States.FALSE -> { - subscribe_button.visibility = View.VISIBLE - unsubscribe_button.visibility = View.GONE - } - else -> { - subscribe_button.visibility = View.GONE - unsubscribe_button.visibility = View.GONE - } - } - }) - } - } - - private fun visibility(state: Int): Int { - return if (state == Constants.States.TRUE) - View.VISIBLE - else - View.GONE - } - -} \ No newline at end of file diff --git a/app/src/main/java/io/github/horaciocome1/reaque/ui/users/UsersAdapter.kt b/app/src/main/java/io/github/horaciocome1/reaque/ui/users/UsersAdapter.kt index 26f8281..edb74ce 100644 --- a/app/src/main/java/io/github/horaciocome1/reaque/ui/users/UsersAdapter.kt +++ b/app/src/main/java/io/github/horaciocome1/reaque/ui/users/UsersAdapter.kt @@ -12,7 +12,8 @@ class UsersAdapter(private val list: List) : RecyclerView.Adapter val args = UsersFragmentArgs.fromBundle(bundle) viewModel.get(args.parentId, args.requestId) - .observe(this, Observer { binding.viewmodel = viewModel.setUsers(it) }) + .observe(this, Observer { + binding.viewmodel = viewModel.setUsers(it) + }) } } diff --git a/app/src/main/java/io/github/horaciocome1/reaque/ui/users/UsersViewModel.kt b/app/src/main/java/io/github/horaciocome1/reaque/ui/users/UsersViewModel.kt index 58fb7ef..eed727f 100644 --- a/app/src/main/java/io/github/horaciocome1/reaque/ui/users/UsersViewModel.kt +++ b/app/src/main/java/io/github/horaciocome1/reaque/ui/users/UsersViewModel.kt @@ -37,49 +37,4 @@ class UsersViewModel( } } - fun get(user: User) = usersRepository.get(user) - - fun subscribe(view: View, user: User) { - view.isEnabled = false - subscriptionsRepository.subscribe(user) { - view.visibility = View.GONE - } - } - - fun unSubscribe(view: View, user: User) { - view.isEnabled = false - subscriptionsRepository.unSubscribe(user) { - view.visibility = View.GONE - } - } - - fun amSubscribedTo(user: User) = subscriptionsRepository.amSubscribedTo(user) - - fun openSubscribers(view: View, user: User) { - if (user.subscribers.isNotBlank() && user.subscribers != "0") { - val directions = UserProfileFragmentDirections.actionOpenUsersFromUserProfile( - user.id, Constants.SUBSCRIBERS_REQUEST - ) - view.findNavController().navigate(directions) - } - } - - fun openSubscriptions(view: View, user: User) { - if (user.subscriptions.isNotBlank() && user.subscriptions != "0") { - val directions = UserProfileFragmentDirections.actionOpenUsersFromUserProfile( - user.id, Constants.SUBSCRIPTIONS_REQUEST - ) - view.findNavController().navigate(directions) - } - } - - fun openPosts(view: View, user: User) { - if (user.posts.isNotBlank() && user.posts != "0") { - val directions = UserProfileFragmentDirections.actionOpenPostsFromUserProfile( - user.id, Constants.USER_POSTS_REQUEST - ) - view.findNavController().navigate(directions) - } - } - } \ No newline at end of file diff --git a/app/src/main/java/io/github/horaciocome1/reaque/ui/users/profile/UserProfileFragment.kt b/app/src/main/java/io/github/horaciocome1/reaque/ui/users/profile/UserProfileFragment.kt new file mode 100644 index 0000000..4b91bc5 --- /dev/null +++ b/app/src/main/java/io/github/horaciocome1/reaque/ui/users/profile/UserProfileFragment.kt @@ -0,0 +1,69 @@ +package io.github.horaciocome1.reaque.ui.users.profile + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import androidx.lifecycle.Observer +import androidx.lifecycle.ViewModelProviders +import io.github.horaciocome1.reaque.data.users.User +import io.github.horaciocome1.reaque.databinding.FragmentUserProfileBinding +import io.github.horaciocome1.reaque.util.Constants +import io.github.horaciocome1.reaque.util.InjectorUtils +import kotlinx.android.synthetic.main.fragment_user_profile.* + +class UserProfileFragment : Fragment() { + + private lateinit var binding: FragmentUserProfileBinding + + private val viewModel: UserProfileViewModel by lazy { + val factory = InjectorUtils.userProfileViewModelFactory + ViewModelProviders.of(this, factory)[UserProfileViewModel::class.java] + } + + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { + binding = FragmentUserProfileBinding.inflate(inflater, container, false) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + binding.viewmodel = viewModel + } + + override fun onStart() { + super.onStart() + arguments?.let { bundle -> + val user = User( + UserProfileFragmentArgs.fromBundle(bundle).userId + ) + viewModel.get(user) + .observe(this, Observer { + binding.user = it + }) + viewModel.amSubscribedTo(user) + .observe(this, Observer { + when (it) { + Constants.States.TRUE -> { + subscribe_button.visibility = View.GONE + unsubscribe_button.visibility = View.VISIBLE + } + Constants.States.FALSE -> { + subscribe_button.visibility = View.VISIBLE + unsubscribe_button.visibility = View.GONE + } + else -> { + subscribe_button.visibility = View.GONE + unsubscribe_button.visibility = View.GONE + } + } + }) + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/github/horaciocome1/reaque/ui/users/profile/UserProfileViewModel.kt b/app/src/main/java/io/github/horaciocome1/reaque/ui/users/profile/UserProfileViewModel.kt new file mode 100644 index 0000000..6f95158 --- /dev/null +++ b/app/src/main/java/io/github/horaciocome1/reaque/ui/users/profile/UserProfileViewModel.kt @@ -0,0 +1,61 @@ +package io.github.horaciocome1.reaque.ui.users.profile + +import android.view.View +import androidx.lifecycle.ViewModel +import androidx.navigation.findNavController +import io.github.horaciocome1.reaque.data.subscriptions.SubscriptionsRepository +import io.github.horaciocome1.reaque.data.users.User +import io.github.horaciocome1.reaque.data.users.UsersRepository +import io.github.horaciocome1.reaque.util.Constants + +class UserProfileViewModel( + private val usersRepository: UsersRepository, + private val subscriptionsRepository: SubscriptionsRepository +) : ViewModel() { + + fun get(user: User) = usersRepository.get(user) + + fun subscribe(view: View, user: User) { + view.isEnabled = false + subscriptionsRepository.subscribe(user) { + view.visibility = View.GONE + } + } + + fun unSubscribe(view: View, user: User) { + view.isEnabled = false + subscriptionsRepository.unSubscribe(user) { + view.visibility = View.GONE + } + } + + fun amSubscribedTo(user: User) = subscriptionsRepository.amSubscribedTo(user) + + fun openSubscribers(view: View, user: User) { + if (user.subscribers.isNotBlank() && user.subscribers != "0") { + val directions = UserProfileFragmentDirections + .actionOpenUsersFromUserProfile(user.id, Constants.SUBSCRIBERS_REQUEST) + view.findNavController() + .navigate(directions) + } + } + + fun openSubscriptions(view: View, user: User) { + if (user.subscriptions.isNotBlank() && user.subscriptions != "0") { + val directions = UserProfileFragmentDirections + .actionOpenUsersFromUserProfile(user.id, Constants.SUBSCRIPTIONS_REQUEST) + view.findNavController() + .navigate(directions) + } + } + + fun openPosts(view: View, user: User) { + if (user.posts.isNotBlank() && user.posts != "0") { + val directions = UserProfileFragmentDirections + .actionOpenPostsFromUserProfile(user.id, Constants.USER_POSTS_REQUEST) + view.findNavController() + .navigate(directions) + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/io/github/horaciocome1/reaque/ui/users/profile/UserProfileViewModelFactory.kt b/app/src/main/java/io/github/horaciocome1/reaque/ui/users/profile/UserProfileViewModelFactory.kt new file mode 100644 index 0000000..a296337 --- /dev/null +++ b/app/src/main/java/io/github/horaciocome1/reaque/ui/users/profile/UserProfileViewModelFactory.kt @@ -0,0 +1,16 @@ +package io.github.horaciocome1.reaque.ui.users.profile + +import androidx.lifecycle.ViewModel +import androidx.lifecycle.ViewModelProvider +import io.github.horaciocome1.reaque.data.subscriptions.SubscriptionsRepository +import io.github.horaciocome1.reaque.data.users.UsersRepository + +class UserProfileViewModelFactory( + private val usersRepository: UsersRepository, + private val subscriptionsRepository: SubscriptionsRepository +) : ViewModelProvider.NewInstanceFactory() { + + @Suppress("UNCHECKED_CAST") + override fun create(modelClass: Class) = + UserProfileViewModel(usersRepository, subscriptionsRepository) as T +} \ No newline at end of file diff --git a/app/src/main/java/io/github/horaciocome1/reaque/ui/users/update/UpdateUserFragment.kt b/app/src/main/java/io/github/horaciocome1/reaque/ui/users/update/UpdateUserFragment.kt index 53cbeb9..0e1307c 100644 --- a/app/src/main/java/io/github/horaciocome1/reaque/ui/users/update/UpdateUserFragment.kt +++ b/app/src/main/java/io/github/horaciocome1/reaque/ui/users/update/UpdateUserFragment.kt @@ -15,14 +15,18 @@ import kotlinx.android.synthetic.main.fragment_update_user.* class UpdateUserFragment : Fragment() { - lateinit var binding: FragmentUpdateUserBinding + private lateinit var binding: FragmentUpdateUserBinding private val viewModel: UpdateUserViewModel by lazy { val factory = InjectorUtils.updateUserViewModelFactory ViewModelProviders.of(this, factory)[UpdateUserViewModel::class.java] } - override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + override fun onCreateView( + inflater: LayoutInflater, + container: ViewGroup?, + savedInstanceState: Bundle? + ): View? { binding = FragmentUpdateUserBinding.inflate(inflater, container, false) return binding.root } @@ -34,9 +38,7 @@ class UpdateUserFragment : Fragment() { bio_inputlayout.editText?.onFocusChangeListener = it address_inputlayout.editText?.onFocusChangeListener = it } - toolbar.setNavigationOnClickListener { - viewModel.navigateUp(it) - } + toolbar.setNavigationOnClickListener(viewModel.navigateUp) update_button.setOnClickListener { binding.viewmodel = viewModel.update(it) } @@ -48,20 +50,23 @@ class UpdateUserFragment : Fragment() { val user = User( UpdateUserFragmentArgs.fromBundle(bundle).userId ) - viewModel.get(user).observe(this, Observer { - viewModel.bio.value = it.bio - viewModel.address.value = it.address - binding.viewmodel = viewModel - }) + viewModel.get(user) + .observe(this, Observer { + viewModel.bio.value = it.bio + viewModel.address.value = it.address + binding.viewmodel = viewModel + }) } - viewModel.bio.observe(this, Observer { - viewModel.user.bio = it - update_button.isEnabled = viewModel.isUserReady - }) - viewModel.address.observe(this, Observer { - viewModel.user.address = it - update_button.isEnabled = viewModel.isUserReady - }) + viewModel.bio + .observe(this, Observer { + viewModel.user.bio = it + update_button.isEnabled = viewModel.isUserReady + }) + viewModel.address + .observe(this, Observer { + viewModel.user.address = it + update_button.isEnabled = viewModel.isUserReady + }) } } \ No newline at end of file diff --git a/app/src/main/java/io/github/horaciocome1/reaque/ui/users/update/UpdateUserViewModel.kt b/app/src/main/java/io/github/horaciocome1/reaque/ui/users/update/UpdateUserViewModel.kt index 5349d5e..a4a97c4 100644 --- a/app/src/main/java/io/github/horaciocome1/reaque/ui/users/update/UpdateUserViewModel.kt +++ b/app/src/main/java/io/github/horaciocome1/reaque/ui/users/update/UpdateUserViewModel.kt @@ -23,10 +23,18 @@ class UpdateUserViewModel(private val repository: UsersRepository) : ObservableV val isUserReady: Boolean get() { user.run { - return bio.isNotBlank() && address.isNotBlank() && bio != "null" && address != "null" + return bio.isNotBlank() + && address.isNotBlank() + && bio != "null" + && address != "null" } } + val navigateUp: (View) -> Unit = { + it.findNavController() + .navigateUp() + } + fun get(user: User) = repository.get(user) fun update(view: View): UpdateUserViewModel { @@ -37,6 +45,4 @@ class UpdateUserViewModel(private val repository: UsersRepository) : ObservableV return this } - fun navigateUp(view: View) = view.findNavController().navigateUp() - } \ No newline at end of file diff --git a/app/src/main/java/io/github/horaciocome1/reaque/ui/users/update/UpdateUserViewModelFactory.kt b/app/src/main/java/io/github/horaciocome1/reaque/ui/users/update/UpdateUserViewModelFactory.kt index 800d932..554b034 100644 --- a/app/src/main/java/io/github/horaciocome1/reaque/ui/users/update/UpdateUserViewModelFactory.kt +++ b/app/src/main/java/io/github/horaciocome1/reaque/ui/users/update/UpdateUserViewModelFactory.kt @@ -4,11 +4,11 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider import io.github.horaciocome1.reaque.data.users.UsersRepository -class UpdateUserViewModelFactory(private val repository: UsersRepository) : ViewModelProvider.NewInstanceFactory() { +class UpdateUserViewModelFactory(private val repository: UsersRepository) : + ViewModelProvider.NewInstanceFactory() { @Suppress("UNCHECKED_CAST") - override fun create(modelClass: Class) = UpdateUserViewModel( - repository - ) as T + override fun create(modelClass: Class) = + UpdateUserViewModel(repository) as T } \ No newline at end of file diff --git a/app/src/main/java/io/github/horaciocome1/reaque/util/BindingAdapters.kt b/app/src/main/java/io/github/horaciocome1/reaque/util/BindingAdapters.kt index c7943b8..994aa6c 100644 --- a/app/src/main/java/io/github/horaciocome1/reaque/util/BindingAdapters.kt +++ b/app/src/main/java/io/github/horaciocome1/reaque/util/BindingAdapters.kt @@ -23,15 +23,16 @@ class BindingAdapters { companion object { - @BindingAdapter("url", "uri", "type", requireAll = false) + @BindingAdapter("url", "uri", "resourceId", "type", requireAll = false) @JvmStatic - fun ImageView.loadImage(url: String?, uri: Uri?, type: Int?) { + fun ImageView.loadImage(url: String?, uri: Uri?, resourceId: Int?, type: Int?) { val image = if (uri != null && uri != Uri.EMPTY) uri else - url + resourceId ?: url val options = when (type) { - Constants.BLUR -> RequestOptions.bitmapTransform(BlurTransformation(7, 14)) + Constants.BLUR -> RequestOptions + .bitmapTransform(BlurTransformation(7, 14)) Constants.CIRCLE -> RequestOptions.circleCropTransform() else -> RequestOptions() } @@ -81,7 +82,8 @@ class BindingAdapters { private fun RecyclerView.loadPosts(list: List, columns: Int, host: Int) { layoutManager = when { - host == Constants.EXPLORE_FRAGMENT -> LinearLayoutManager(context, RecyclerView.HORIZONTAL, false) + host == Constants.EXPLORE_FRAGMENT -> + LinearLayoutManager(context, RecyclerView.HORIZONTAL, false) columns == 1 -> LinearLayoutManager(context) else -> StaggeredGridLayoutManager(columns, RecyclerView.VERTICAL) } diff --git a/app/src/main/java/io/github/horaciocome1/reaque/util/Constants.kt b/app/src/main/java/io/github/horaciocome1/reaque/util/Constants.kt index ce95391..f9a335e 100644 --- a/app/src/main/java/io/github/horaciocome1/reaque/util/Constants.kt +++ b/app/src/main/java/io/github/horaciocome1/reaque/util/Constants.kt @@ -56,8 +56,6 @@ object Constants { const val NAME = "SharedPreferencesPostDraft" const val POST_TITLE = "SharedPreferencesPostDraftPostTitle" const val POST_MESSAGE = "SharedPreferencesPostDraftPostMessage" - const val TOPIC_ID = "SharedPreferencesPostDraftTopicId" - const val TOPIC_TITLE = "SharedPreferencesPostDraftTopicTitle" } diff --git a/app/src/main/java/io/github/horaciocome1/reaque/util/DateUtils.kt b/app/src/main/java/io/github/horaciocome1/reaque/util/DateUtils.kt index baaadf5..1176712 100644 --- a/app/src/main/java/io/github/horaciocome1/reaque/util/DateUtils.kt +++ b/app/src/main/java/io/github/horaciocome1/reaque/util/DateUtils.kt @@ -22,7 +22,9 @@ val Timestamp.string: String get() { val calendar = Calendar.getInstance() calendar.time = toDate() - return "${calendar.month} ${calendar[Calendar.DAY_OF_MONTH]}, ${calendar[Calendar.YEAR]}" + return calendar.month + + " ${calendar[Calendar.DAY_OF_MONTH]}," + + " ${calendar[Calendar.YEAR]}" } val Calendar.month: String diff --git a/app/src/main/java/io/github/horaciocome1/reaque/util/DocumentSnapshotUtils.kt b/app/src/main/java/io/github/horaciocome1/reaque/util/DocumentSnapshotUtils.kt index 3c7659f..f782ae9 100644 --- a/app/src/main/java/io/github/horaciocome1/reaque/util/DocumentSnapshotUtils.kt +++ b/app/src/main/java/io/github/horaciocome1/reaque/util/DocumentSnapshotUtils.kt @@ -10,7 +10,8 @@ * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and limitations under the License. + * See the License for the specific language governing permissions and limitations + * the License. */ package io.github.horaciocome1.reaque.util @@ -35,9 +36,9 @@ val DocumentSnapshot.user: User subscribers = this@user["subscribers"].toString() subscriptions = this@user["subscriptions"].toString() topTopic = this@user["top_topic"].toString() - val score = this@user["score"] - if (score != null) - this.score = score.toString().toFloat() + if (contains("score")) + this.score = this@user["score"].toString() + .toFloat() } val DocumentSnapshot.topic: Topic @@ -61,7 +62,9 @@ val DocumentSnapshot.post: Post pic = this@post["user.pic"].toString() } if (contains("score")) - this.score = this@post["score"].toString().toFloat() + this.score = this@post["score"].toString() + .toFloat() if (contains("rating")) - rating = this@post["rating"].toString().toFloat() + rating = this@post["rating"].toString() + .toFloat() } \ No newline at end of file diff --git a/app/src/main/java/io/github/horaciocome1/reaque/util/DomainUtils.kt b/app/src/main/java/io/github/horaciocome1/reaque/util/DomainUtils.kt index b7ee84f..765deae 100644 --- a/app/src/main/java/io/github/horaciocome1/reaque/util/DomainUtils.kt +++ b/app/src/main/java/io/github/horaciocome1/reaque/util/DomainUtils.kt @@ -10,7 +10,8 @@ * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and limitations under the License. + * See the License for the specific language governing permissions and limitations + * under the License. */ package io.github.horaciocome1.reaque.util @@ -32,7 +33,8 @@ val Post.map: Map "shares" to 0, "score" to 0, "topic" to mapOf( - "id" to topic.id + "id" to topic.id, + "title" to topic.title ), "user" to mapOf( "id" to user.id, @@ -48,7 +50,8 @@ val Post.mapSimple: Map "pic" to pic, "timestamp" to timestamp, "topic" to mapOf( - "id" to topic.id + "id" to topic.id, + "title" to topic.title ), "user" to mapOf( "id" to user.id, diff --git a/app/src/main/java/io/github/horaciocome1/reaque/util/InjectorUtils.kt b/app/src/main/java/io/github/horaciocome1/reaque/util/InjectorUtils.kt index ccd3c55..06e7c54 100644 --- a/app/src/main/java/io/github/horaciocome1/reaque/util/InjectorUtils.kt +++ b/app/src/main/java/io/github/horaciocome1/reaque/util/InjectorUtils.kt @@ -32,7 +32,10 @@ import io.github.horaciocome1.reaque.ui.feed.FeedViewModelFactory import io.github.horaciocome1.reaque.ui.more.MoreViewModelFactory import io.github.horaciocome1.reaque.ui.posts.PostsViewModelFactory import io.github.horaciocome1.reaque.ui.posts.create.CreatePostViewModelFactory +import io.github.horaciocome1.reaque.ui.posts.read.ReadPostViewModelFactory +import io.github.horaciocome1.reaque.ui.sign_in.SignInViewModelFactory import io.github.horaciocome1.reaque.ui.users.UsersViewModelFactory +import io.github.horaciocome1.reaque.ui.users.profile.UserProfileViewModelFactory import io.github.horaciocome1.reaque.ui.users.update.UpdateUserViewModelFactory object InjectorUtils { @@ -69,14 +72,26 @@ object InjectorUtils { val postsViewModelFactory: PostsViewModelFactory by lazy { val postsRepository = PostsRepository .getInstance(db.postsService) - val readingsRepository = ReadingsRepository.getInstance(db.readingsService) + val bookmarksRepository = BookmarksRepository + .getInstance(db.bookmarksService) + PostsViewModelFactory( + postsRepository, + bookmarksRepository + ) + } + + val readPostViewModelFactory: ReadPostViewModelFactory by lazy { + val postsRepository = PostsRepository + .getInstance(db.postsService) + val readingsRepository = ReadingsRepository + .getInstance(db.readingsService) val sharesRepository = SharesRepository .getInstance(db.sharesService) val ratingsRepository = RatingsRepository .getInstance(db.ratingsService) val bookmarksRepository = BookmarksRepository .getInstance(db.bookmarksService) - PostsViewModelFactory( + ReadPostViewModelFactory( postsRepository, readingsRepository, sharesRepository, @@ -110,10 +125,25 @@ object InjectorUtils { ) } + val userProfileViewModelFactory: UserProfileViewModelFactory by lazy { + val usersRepository = UsersRepository + .getInstance(db.usersService) + val subscriptionsRepository = SubscriptionsRepository + .getInstance(db.subscriptionsService) + UserProfileViewModelFactory( + usersRepository, + subscriptionsRepository + ) + } + val updateUserViewModelFactory: UpdateUserViewModelFactory by lazy { val repository = UsersRepository .getInstance(db.usersService) UpdateUserViewModelFactory(repository) } + val signInViewModelFactory: SignInViewModelFactory by lazy { + SignInViewModelFactory() + } + } \ No newline at end of file diff --git a/app/src/main/java/io/github/horaciocome1/reaque/util/ObservableViewModel.kt b/app/src/main/java/io/github/horaciocome1/reaque/util/ObservableViewModel.kt index 0329361..1362ac9 100644 --- a/app/src/main/java/io/github/horaciocome1/reaque/util/ObservableViewModel.kt +++ b/app/src/main/java/io/github/horaciocome1/reaque/util/ObservableViewModel.kt @@ -23,7 +23,8 @@ open class ObservableViewModel: ViewModel(), Observable { fun notifyChange() = callbacks.notifyCallbacks(this, 0, null) // notifies listeners that a specific property has changed. - // the getter for the property that changes should be marked with [@Bindable] to generate a field in `BR` to be used as `fieldId` + // the getter for the property that changes should be marked with [@Bindable] + // to generate a field in `BR` to be used as `fieldId` fun notifyPropertyChanged(fieldId: Int) = callbacks.notifyCallbacks(this, fieldId, null) } \ No newline at end of file diff --git a/app/src/main/res/drawable/background_all_corners_rounded_secondary.xml b/app/src/main/res/drawable/background_all_corners_rounded_secondary.xml new file mode 100644 index 0000000..96bee4e --- /dev/null +++ b/app/src/main/res/drawable/background_all_corners_rounded_secondary.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/background_splash.xml b/app/src/main/res/drawable/background_splash.xml index 92604fe..c5cb3a1 100644 --- a/app/src/main/res/drawable/background_splash.xml +++ b/app/src/main/res/drawable/background_splash.xml @@ -1,12 +1,10 @@ + android:opacity="opaque"> - - - + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_undraw_camera_mg5h.xml b/app/src/main/res/drawable/ic_undraw_camera_mg5h.xml new file mode 100644 index 0000000..f0dca8f --- /dev/null +++ b/app/src/main/res/drawable/ic_undraw_camera_mg5h.xml @@ -0,0 +1,146 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_undraw_feedback_h2ft.xml b/app/src/main/res/drawable/ic_undraw_feedback_h2ft.xml new file mode 100644 index 0000000..faa1afb --- /dev/null +++ b/app/src/main/res/drawable/ic_undraw_feedback_h2ft.xml @@ -0,0 +1,175 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_undraw_ideas_s70l.xml b/app/src/main/res/drawable/ic_undraw_ideas_s70l.xml new file mode 100644 index 0000000..cb2d8cb --- /dev/null +++ b/app/src/main/res/drawable/ic_undraw_ideas_s70l.xml @@ -0,0 +1,130 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_undraw_reading_0re1.xml b/app/src/main/res/drawable/ic_undraw_reading_0re1.xml new file mode 100644 index 0000000..3e374ed --- /dev/null +++ b/app/src/main/res/drawable/ic_undraw_reading_0re1.xml @@ -0,0 +1,146 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_undraw_reading_book_4wjf.xml b/app/src/main/res/drawable/ic_undraw_reading_book_4wjf.xml new file mode 100644 index 0000000..8682d2e --- /dev/null +++ b/app/src/main/res/drawable/ic_undraw_reading_book_4wjf.xml @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/outline_arrow_forward_18.xml b/app/src/main/res/drawable/outline_arrow_forward_18.xml new file mode 100755 index 0000000..40add22 --- /dev/null +++ b/app/src/main/res/drawable/outline_arrow_forward_18.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/outline_remove_red_eye_18.xml b/app/src/main/res/drawable/outline_remove_red_eye_18.xml deleted file mode 100755 index cdf91f5..0000000 --- a/app/src/main/res/drawable/outline_remove_red_eye_18.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/splash_screen.png b/app/src/main/res/drawable/splash_screen.png deleted file mode 100644 index 6a4430fe6b8727dfce02196fd32c2df406f5dbe9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 57036 zcmb@tV{k6c4=DW9wr#sTwQbwB?Ni&fZQDI{pHo|>w(Z{E|KmIF{djjKv%AU8ERxA2 zn^V7XN`5G~|C6bdglL|2h~aDQ#B(01oYc00PL)#r|)_ zgO8e)o2a9`y}5%M06?8Yu^@?BECc`yLOBzY;XgC8GE1S7`T5uJ zQ#;0hbXx%Gw&7Z$YZ@f`i$Z8;k4ueOY#W#yd)_-Z3Y3Lb?|OdKtaRoiF+fls@+@SL zULB11*Ur}z5C9*p>|P9k=0(Yi!8Q{OuPS?mrp2LbMpN|mhjz6@@LMM!@Ga9gvfL#J z&mS=PX(3p(X$UK($Vr2VUV(Q-6O2x-9$S968{7MO*rmaa%8EZG;B$8!DZ;=Sob(C4 zIu9egUc{Eva}ytvBcB$G4?nbF2S4|LlLAHt37(R<2M{i@*fiQUsK_%_QT=O|gZ3E`ai)iwq7t~H{X{Ziw2 zg=3YwT8c$Gkx39_WgpbbSVzL9<1^$&=>4@EifPf1Z6$;tZX^!>ztc_=uH& z@N>r7){>lYRe^7zzuiY-TB{YyRuckVbmv!To#V7@Cj28QLcD0!3_54ba+spkDXY1I z=A0tE#m7V8csKktE7vJD6gr{9mVX5TmhBHA$n6Q!%EhGk>ABe`06^>hKlZB_dlEaj zI9gcSnG?HtJDC%kds+V{JzlwWxz-+s?a5(3rWgjG$Hv^)vu6a4_a8k*^R7$~R7Cdw+QFZU$ZyaX1DH zzOLL`>f~Q|XJvyL_Pk53Rwp?cd=YuxS2qX?GPXZnU!r#Ybv}OJ-Jo^P(48$_+Xl<^I>7+Jc=?u96OQUqO))m)OXI6>>a|^wPL&Zu*^d4&K}Iep!`S|~%N<)g zQMRP@+WSK03KMW2%k3**$8Efq%C>U3f6Qfx{eC={D(dljWXhT|f+gSY^*c2`!eEiO ztE@1_$tQ!24GmlNgg+yjM&JpV)lz}6VIh-3|B6|XflVF=ev?+}9Yb?Cr?O9AuzaRs zI&{E@RreHEUiV8mctmv`i;) zKw*;9$*sMs=uBq&c8ZDG+Q9FBIF9c`5l^gj?kz)OAgu~;KVY~c<9VClRn1Dp_9xs5 zDOYQ3k_UDZ{DrB&%cXV_m8q$Ga3v->JfvZwWqlA}lbVYnmSPTOm0Uel3s?!mq%vA? zr2vHBAqWRi!bDE&Pc32pu$k(mj9^TKNaa6R-RoCVevH^r%Z5~sx%$*gWwIwB3EXk& z`NcY_cm@4CDfsJOG)P1jlsoi6px5|f_#;HywJ!pf*ANzfQ?p8U_PgLMBX}i>F_FXT z81w_~<*gGbp4qz(QF}Wj{k<3*dylQ3=sOeYwC5{!b-w!{_wHr2$JNp1b+f>?xMA?K zLd&5a?4iJ<>kSbY5Pfva%lt!=o^$j6ln@Yigyg{cfCD1f{~@}3!#VLoa&GF^6{^Cd${q=-0`%kgq-f!=3%3*!iGd;Yi@pNp zG=rK_y@!W;gI}w;3lUwFO-`OvmKdS?V z`KS2Y0uzlpb3Igp26ofT=-gk^%HW6MWeTZR0$*Pb3AN~xK;`V?DFhGReAUFf0T;SV zywkxTz(UN2zIFo|v$!5!swrb6090k|3IJl5Yf4CWcNV(^mFa;D3gyZuAsq*vWbo9@ zQ9yG|crg_F!J3~TG$Dg75+b4Otis#~y{v%ft^~`aD6$HR>lUdjBw}otb%NNdv|g+o zBy)x#QJreW(6*w{V@+5YRFE@Q)xP3$QhZB(a*qGc+jvkVBsy$py0RorK)L{?P$ANM zW!-{vx}h;}7Xq`kd*gi5Ud4JmZ}7AE!l8#q1y~X;I-PawD$#jlm)yVM|c* zZ(p&*ATUn*u+VeKt1I<|CU=*d#zG8Uf?{7Bg}A8C#DXsK5`()Wa~%>`WQc99;*_pB z66&(~=C&ny`CI(j(OJ~;XWaRrfXouT`zK)nC+s?A;)~zGP|UuHq(osXZJbtAz8WEw zXlu>?NRa$e&@az2%HkrZ-EQKe)naVNpNwDP0`*WiBJ3Q6q8BCiripplp#uay_nM

x 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

^PSM6M@|>b` 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 + - - - + android:layout_height="match_parent" + android:background="@color/primaryLightColor" + tools:context=".ui.MainActivity"> - - + android:layout_height="wrap_content"> - + - + - + - + - + - + + + + + - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout-v21/fragment_read_post.xml b/app/src/main/res/layout-v21/fragment_read_post.xml index 664ecb3..3fcfd1d 100644 --- a/app/src/main/res/layout-v21/fragment_read_post.xml +++ b/app/src/main/res/layout-v21/fragment_read_post.xml @@ -2,20 +2,6 @@ - - - - - - - - - - @@ -35,71 +21,17 @@ app:layout_constraintEnd_toEndOf="parent" app:rippleColor="@color/green" /> - - - - - - - - @@ -127,37 +59,46 @@ app:type="@{Constants.DEFAULT}" app:url="@{post.pic}" /> - + + + app:layout_constraintTop_toBottomOf="@+id/textView2" /> + app:layout_constraintTop_toBottomOf="@+id/topic_title_textview" /> + app:layout_constraintTop_toBottomOf="@+id/textView21" /> + + + + + + - + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-v21/item_topic.xml b/app/src/main/res/layout-v21/item_topic.xml new file mode 100644 index 0000000..7ecf278 --- /dev/null +++ b/app/src/main/res/layout-v21/item_topic.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-w600dp/fragment_create_post.xml b/app/src/main/res/layout-w600dp/fragment_create_post.xml index c298956..e3cd22c 100644 --- a/app/src/main/res/layout-w600dp/fragment_create_post.xml +++ b/app/src/main/res/layout-w600dp/fragment_create_post.xml @@ -16,26 +16,6 @@ - - - - - - - - - - - - - - - - @@ -77,13 +57,30 @@ + + + + @@ -142,7 +143,6 @@ android:padding="16dp" app:cornerRadius="16dp" app:icon="@drawable/outline_subject_18" - app:iconTint="?colorControlNormal" app:layout_constraintBottom_toBottomOf="@+id/create_button" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@+id/create_button" /> @@ -155,28 +155,10 @@ android:padding="16dp" app:cornerRadius="16dp" app:icon="@drawable/outline_add_photo_alternate_18" - app:iconTint="?colorControlNormal" app:layout_constraintBottom_toBottomOf="@+id/select_topic_button" app:layout_constraintStart_toEndOf="@+id/select_topic_button" app:layout_constraintTop_toTopOf="@+id/select_topic_button" /> - - @@ -227,48 +209,6 @@ - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout-w900dp/fragment_create_post.xml b/app/src/main/res/layout-w900dp/fragment_create_post.xml index abf521b..01826f5 100644 --- a/app/src/main/res/layout-w900dp/fragment_create_post.xml +++ b/app/src/main/res/layout-w900dp/fragment_create_post.xml @@ -16,26 +16,6 @@ - - - - - - - - - - - - - - - - @@ -80,10 +60,24 @@ android:layout_margin="16dp" android:orientation="vertical"> + + + + @@ -142,7 +136,6 @@ android:padding="16dp" app:cornerRadius="16dp" app:icon="@drawable/outline_subject_18" - app:iconTint="?colorControlNormal" app:layout_constraintBottom_toBottomOf="@+id/create_button" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@+id/create_button" /> @@ -155,28 +148,10 @@ android:padding="16dp" app:cornerRadius="16dp" app:icon="@drawable/outline_add_photo_alternate_18" - app:iconTint="?colorControlNormal" app:layout_constraintBottom_toBottomOf="@+id/select_topic_button" app:layout_constraintStart_toEndOf="@+id/select_topic_button" app:layout_constraintTop_toTopOf="@+id/select_topic_button" /> - - @@ -227,48 +202,6 @@ - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 36cd984..002312f 100644 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -13,74 +13,78 @@ ~ See the License for the specific language governing permissions and limitations under the License. --> - + - + android:layout_height="match_parent" + android:background="@color/primaryLightColor" + tools:context=".ui.MainActivity"> + + - + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent"> - + - + - + - + - + + + - + - + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_create_post.xml b/app/src/main/res/layout/fragment_create_post.xml index 3767369..dac58bb 100644 --- a/app/src/main/res/layout/fragment_create_post.xml +++ b/app/src/main/res/layout/fragment_create_post.xml @@ -16,26 +16,6 @@ - - - - - - - - - - - - - - - - @@ -77,13 +57,30 @@ + + + + @@ -142,7 +143,6 @@ android:padding="16dp" app:cornerRadius="16dp" app:icon="@drawable/outline_subject_18" - app:iconTint="?colorControlNormal" app:layout_constraintBottom_toBottomOf="@+id/create_button" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@+id/create_button" /> @@ -155,28 +155,10 @@ android:padding="16dp" app:cornerRadius="16dp" app:icon="@drawable/outline_add_photo_alternate_18" - app:iconTint="?colorControlNormal" app:layout_constraintBottom_toBottomOf="@+id/select_topic_button" app:layout_constraintStart_toEndOf="@+id/select_topic_button" app:layout_constraintTop_toTopOf="@+id/select_topic_button" /> - - @@ -227,48 +209,6 @@ - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_preview_message.xml b/app/src/main/res/layout/fragment_preview_message.xml deleted file mode 100644 index 5870e54..0000000 --- a/app/src/main/res/layout/fragment_preview_message.xml +++ /dev/null @@ -1,8 +0,0 @@ - - \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_read_post.xml b/app/src/main/res/layout/fragment_read_post.xml index 8c9da53..fd8184c 100644 --- a/app/src/main/res/layout/fragment_read_post.xml +++ b/app/src/main/res/layout/fragment_read_post.xml @@ -2,20 +2,6 @@ - - - - - - - - - - @@ -33,75 +19,22 @@ app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" /> - - - - - - - - + + + + + app:layout_constraintTop_toBottomOf="@+id/topic_title_textview" /> @@ -175,23 +138,40 @@ app:layout_constraintTop_toBottomOf="@+id/ratingBar" /> + + + app:layout_constraintTop_toTopOf="@id/divider5" /> + + - + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_set_rating.xml b/app/src/main/res/layout/fragment_set_rating.xml index 574576d..3d2fbeb 100644 --- a/app/src/main/res/layout/fragment_set_rating.xml +++ b/app/src/main/res/layout/fragment_set_rating.xml @@ -2,18 +2,52 @@ - +