diff --git a/Habitica/res/layout/activity_notifications.xml b/Habitica/res/layout/activity_notifications.xml index 1a7909054..5866b4e2e 100644 --- a/Habitica/res/layout/activity_notifications.xml +++ b/Habitica/res/layout/activity_notifications.xml @@ -45,6 +45,7 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:divider="?android:listDivider" + android:visibility="invisible" android:orientation="vertical" android:showDividers="middle" /> diff --git a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/NotificationsActivity.kt b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/NotificationsActivity.kt index d0f3f828f..b5d316e96 100644 --- a/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/NotificationsActivity.kt +++ b/Habitica/src/main/java/com/habitrpg/android/habitica/ui/activities/NotificationsActivity.kt @@ -18,6 +18,7 @@ import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.data.InventoryRepository import com.habitrpg.android.habitica.data.SocialRepository import com.habitrpg.android.habitica.databinding.ActivityNotificationsBinding +import com.habitrpg.android.habitica.extensions.fadeInAnimation import com.habitrpg.android.habitica.models.inventory.QuestContent import com.habitrpg.android.habitica.ui.viewmodels.NotificationsViewModel import com.habitrpg.common.habitica.extensions.fromHtml @@ -37,6 +38,7 @@ import com.habitrpg.common.habitica.models.notifications.QuestInvitationData import com.habitrpg.common.habitica.models.notifications.UnallocatedPointsData import com.habitrpg.common.habitica.views.PixelArtView import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.launch import kotlinx.coroutines.withContext @@ -54,6 +56,7 @@ class NotificationsActivity : BaseActivity(), androidx.swiperefreshlayout.widget val viewModel: NotificationsViewModel by viewModels() var inflater: LayoutInflater? = null + val viewTagMap = mutableMapOf() override fun getLayoutResId(): Int = R.layout.activity_notifications @@ -104,8 +107,6 @@ class NotificationsActivity : BaseActivity(), androidx.swiperefreshlayout.widget private fun setNotifications(notifications: List) { this.notifications = notifications - binding.notificationItems.removeAllViewsInLayout() - if (notifications.isEmpty()) { displayNoNotificationsView() } else { @@ -114,9 +115,10 @@ class NotificationsActivity : BaseActivity(), androidx.swiperefreshlayout.widget } private fun displayNoNotificationsView() { + binding.notificationItems.removeAllViewsInLayout() binding.notificationItems.showDividers = LinearLayout.SHOW_DIVIDER_NONE - binding.notificationItems.addView(inflater?.inflate(R.layout.no_notifications, binding.notificationItems, false)) + refreshViews(listOf()) } private fun displayNotificationsListView(notifications: List) { @@ -125,6 +127,7 @@ class NotificationsActivity : BaseActivity(), androidx.swiperefreshlayout.widget binding.notificationItems.addView( createNotificationsHeaderView(notifications.count()) ) + val viewList = arrayListOf() lifecycleScope.launch(ExceptionHandler.coroutine()) { notifications.map { @@ -144,17 +147,35 @@ class NotificationsActivity : BaseActivity(), androidx.swiperefreshlayout.widget } if (item != null) { - item.tag = it.id - - if (binding.notificationItems.findViewWithTag(it.id) == null) { - binding.notificationItems.addView(item) - } + viewList.add(item) } } + refreshViews(viewList) } } + private fun refreshViews(newItems: List) { + val currentViews = (0 until binding.notificationItems.childCount).map { + binding.notificationItems.getChildAt(it) + } + val viewsToRemove = currentViews - newItems + viewsToRemove.forEach { binding.notificationItems.removeView(it) } + val viewsToAdd = newItems - currentViews + viewsToAdd.forEach { + binding.notificationItems.addView(it) + } + + lifecycleScope.launch { + delay(250) + // Unnecessary but looks clean c: + if (binding.notificationItems.visibility != View.VISIBLE) { + binding.notificationItems.fadeInAnimation(200) + } + } + } + + private fun createNotificationsHeaderView(notificationCount: Int): View? { val header = inflater?.inflate(R.layout.notifications_header, binding.notificationItems, false) diff --git a/common/src/main/java/com/habitrpg/common/habitica/extensions/ViewExt.kt b/common/src/main/java/com/habitrpg/common/habitica/extensions/ViewExt.kt index 94a989a76..2f55512a4 100644 --- a/common/src/main/java/com/habitrpg/common/habitica/extensions/ViewExt.kt +++ b/common/src/main/java/com/habitrpg/common/habitica/extensions/ViewExt.kt @@ -1,5 +1,6 @@ package com.habitrpg.android.habitica.extensions +import android.animation.ObjectAnimator import android.content.Context import android.view.View import android.view.ViewTreeObserver @@ -28,3 +29,13 @@ inline fun View.afterMeasured(crossinline f: View.() -> Unit) { } }) } + +fun View.fadeInAnimation(duration: Long = 500) { + this.alpha = 0f + this.visibility = View.VISIBLE + + val fadeInAnimation = ObjectAnimator.ofFloat(this, "alpha", 0f, 1f) + fadeInAnimation.duration = duration + fadeInAnimation.start() +} +