Skip to content

Commit

Permalink
MBL-1665 & 1662: Project Alerts red dot indicators on overflow menu (#…
Browse files Browse the repository at this point in the history
…2113)

* pass theme to logged in viewmodel, set indicators on hamburger icon and project alert menu item when users has project alerts available

* pass theme to logged in viewmodel, set indicators on hamburger icon and project alert menu item when users has project alerts available

* Add tests, cleanup

* linter

* fix schema after merge

---------

Co-authored-by: Leigh Douglas <[email protected]>
Co-authored-by: Isabel Martin <[email protected]>
Co-authored-by: mtgriego <[email protected]>
  • Loading branch information
4 people authored Sep 3, 2024
1 parent 794c7f9 commit 23ea68e
Show file tree
Hide file tree
Showing 10 changed files with 177 additions and 23 deletions.
33 changes: 22 additions & 11 deletions app/src/main/graphql/schema.graphqls
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,10 @@ type User implements Node {
"""
organizations("""Returns the first _n_ elements from the list.""" first: Int, """Returns the elements in the list that come after the specified cursor.""" after: String, """Returns the last _n_ elements from the list.""" last: Int, """Returns the elements in the list that come before the specified cursor.""" before: String, """Filter organizations by membership state.""" state: OrganizationMembershipState): UserOrganizationsConnection
"""
Whether backer has any action in PPO
"""
ppoHasAction: Boolean
"""
A user's project notification settings
"""
projectNotifications: [Notification!]
Expand Down Expand Up @@ -532,8 +536,6 @@ enum Feature {
late_pledges_learn_more_cta
post_campaign_backings_2024
ckeditor_project_updates
project_card_unification_2023
project_card_unification_2023_videos
backer_discovery_features_2023
payments_stripe_link_on_checkout
address_collection_2024
Expand All @@ -550,6 +552,7 @@ enum Feature {
prelaunch_story_editor
prelaunch_story_exception
creator_nav_refresh
pledge_redemption_cross_sells
}

"""
Expand Down Expand Up @@ -1372,7 +1375,7 @@ type Project implements Node & Commentable {
"""
targetLaunchDateUpdatedAt: ISO8601DateTime
"""
Tax categories configured for the project
Tax categories configured for the project excluding the non-taxable category
"""
taxCategories: [TaxCategory!]!
"""
Expand Down Expand Up @@ -6595,38 +6598,46 @@ type Order implements Node {
"""
backing: Backing!
"""
The currency of the order
"""
currency: CurrencyCode!
"""
The funds capture key
"""
fundsCaptureKey: String
id: ID!
"""
The cost of tax on reward items
"""
itemTax: Money
itemTax: Int
"""
The project associated with the order
"""
project: Project!
"""
The cost of shipping
"""
shippingAmount: Money
shippingAmount: Int
"""
The cost of tax on shipping
"""
shippingTax: Money
shippingTax: Int
"""
The order's state, e.g. draft, submitted, successful, errored, missed
"""
state: OrderStateEnum!
"""
The total cost for the order including taxes and shipping
"""
total: Money
total: Int
"""
The total tax amount for the order
"""
totalTax: Money
totalTax: Int
"""
The amount pledged during the crowdfunding campaign
"""
voucherAmount: Int!
}

"""
Expand Down Expand Up @@ -12658,9 +12669,9 @@ type CreateOrUpdateItemTaxConfigPayload {
"""
clientMutationId: String
"""
The created or updated item tax config
Success if item tax config was created or updated successfully
"""
itemTaxConfig: ItemTaxConfig!
success: Boolean!
}

"""
Expand Down Expand Up @@ -13136,7 +13147,7 @@ Shipping rule for a reward
"""
input ShippingRateInput {
id: ID
cost: Int!
cost: Int
locationId: String!
}

Expand Down
7 changes: 7 additions & 0 deletions app/src/main/java/com/kickstarter/models/User.kt
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ class User private constructor(
private val notifyOfMessages: Boolean,
private val notifyOfUpdates: Boolean,
private val optedOutOfRecommendations: Boolean,
private val ppoHasAction: Boolean?,
private val promoNewsletter: Boolean,
private val publishingNewsletter: Boolean,
private val showPublicProfile: Boolean,
Expand Down Expand Up @@ -105,6 +106,7 @@ class User private constructor(
fun notifyOfMessages() = this.notifyOfMessages
fun notifyOfUpdates() = this.notifyOfUpdates
fun optedOutOfRecommendations() = this.optedOutOfRecommendations
fun ppoHasAction() = this.ppoHasAction
fun promoNewsletter() = this.promoNewsletter
fun publishingNewsletter() = this.publishingNewsletter
fun showPublicProfile() = this.showPublicProfile
Expand Down Expand Up @@ -161,6 +163,7 @@ class User private constructor(
private var notifyOfUpdates: Boolean = false,
private var optedOutOfRecommendations: Boolean = false,
private var promoNewsletter: Boolean = false,
private var ppoHasAction: Boolean? = false,
private var publishingNewsletter: Boolean = false,
private var showPublicProfile: Boolean = false,
private var needsPassword: Boolean? = false,
Expand Down Expand Up @@ -214,6 +217,7 @@ class User private constructor(
fun notifyOfMessages(notifyOfMessages: Boolean?) = apply { this.notifyOfMessages = notifyOfMessages ?: false }
fun notifyOfUpdates(notifyOfUpdates: Boolean?) = apply { this.notifyOfUpdates = notifyOfUpdates ?: false }
fun optedOutOfRecommendations(optedOutOfRecommendations: Boolean?) = apply { this.optedOutOfRecommendations = optedOutOfRecommendations ?: false }
fun ppoHasAction(ppoHasAction: Boolean?) = apply { this.ppoHasAction = ppoHasAction ?: false }
fun promoNewsletter(promoNewsletter: Boolean?) = apply { this.promoNewsletter = promoNewsletter ?: false }
fun publishingNewsletter(publishingNewsletter: Boolean?) = apply { this.publishingNewsletter = publishingNewsletter ?: false }
fun showPublicProfile(showPublicProfile: Boolean?) = apply { this.showPublicProfile = showPublicProfile ?: false }
Expand Down Expand Up @@ -268,6 +272,7 @@ class User private constructor(
notifyOfMessages = notifyOfMessages,
notifyOfUpdates = notifyOfUpdates,
optedOutOfRecommendations = optedOutOfRecommendations,
ppoHasAction = ppoHasAction,
promoNewsletter = promoNewsletter,
publishingNewsletter = publishingNewsletter,
showPublicProfile = showPublicProfile,
Expand Down Expand Up @@ -333,6 +338,7 @@ class User private constructor(
notifyOfMessages = notifyOfMessages,
notifyOfUpdates = notifyOfUpdates,
optedOutOfRecommendations = optedOutOfRecommendations,
ppoHasAction = ppoHasAction,
promoNewsletter = promoNewsletter,
publishingNewsletter = publishingNewsletter,
showPublicProfile = showPublicProfile,
Expand Down Expand Up @@ -403,6 +409,7 @@ class User private constructor(
notifyOfFriendActivity() == obj.notifyOfFriendActivity() &&
notifyOfMessages() == obj.notifyOfMessages() &&
optedOutOfRecommendations() == obj.optedOutOfRecommendations() &&
ppoHasAction() == obj.ppoHasAction() &&
promoNewsletter() == obj.promoNewsletter() &&
publishingNewsletter() == obj.publishingNewsletter() &&
showPublicProfile() == obj.showPublicProfile() &&
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
package com.kickstarter.ui.viewholders.discoverydrawer

import android.graphics.drawable.Drawable
import android.util.Pair
import androidx.appcompat.content.res.AppCompatResources
import androidx.core.content.ContextCompat
import com.kickstarter.R
import com.kickstarter.databinding.DiscoveryDrawerLoggedInViewBinding
import com.kickstarter.libs.rx.transformers.Transformers.combineLatestPair
import com.kickstarter.libs.utils.NumberUtils
import com.kickstarter.libs.utils.extensions.addToDisposable
import com.kickstarter.libs.utils.extensions.isNullOrZero
Expand All @@ -10,6 +15,7 @@ import com.kickstarter.models.User
import com.kickstarter.ui.extensions.loadCircleImage
import com.kickstarter.ui.viewholders.KSViewHolder
import com.kickstarter.viewmodels.LoggedInViewHolderViewModel
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.CompositeDisposable

Expand All @@ -27,6 +33,7 @@ class LoggedInViewHolder(
fun loggedInViewHolderProfileClick(viewHolder: LoggedInViewHolder, user: User)
fun loggedInViewHolderSettingsClick(viewHolder: LoggedInViewHolder, user: User)
fun loggedInViewHolderPledgedProjectsClick(viewHolder: LoggedInViewHolder)
fun darkThemeEnabled(): Observable<Boolean>
}

init {
Expand Down Expand Up @@ -62,7 +69,16 @@ class LoggedInViewHolder(

this.viewModel.outputs.pledgedProjectsIsVisible()
.observeOn(AndroidSchedulers.mainThread())
.subscribe { binding.pledgedProjectsOverview.visibility = it.toVisibility() }
.subscribe { binding.drawerProjectAlerts.visibility = it.toVisibility() }
.addToDisposable(disposables)

this.viewModel.outputs.pledgedProjectsIndicatorIsVisible()
.compose<Pair<Boolean, Boolean>>(combineLatestPair(delegate.darkThemeEnabled()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe {
binding.projectAlertsIndicator.setImageDrawable(selectProjectAlertIndicatorColor(it.second))
binding.projectAlertsIndicator.visibility = it.first.toVisibility()
}
.addToDisposable(disposables)

this.viewModel.outputs.activityCountTextColor()
Expand All @@ -75,14 +91,18 @@ class LoggedInViewHolder(
binding.drawerSettings.setOnClickListener { this.delegate.loggedInViewHolderSettingsClick(this, user) }
binding.drawerProfile.setOnClickListener { this.delegate.loggedInViewHolderProfileClick(this, user) }
binding.userContainer.setOnClickListener { this.delegate.loggedInViewHolderProfileClick(this, user) }
binding.pledgedProjectsOverview.setOnClickListener { this.delegate.loggedInViewHolderPledgedProjectsClick(this) }
binding.drawerProjectAlerts.setOnClickListener { this.delegate.loggedInViewHolderPledgedProjectsClick(this) }
}.addToDisposable(disposables)

binding.drawerActivity.setOnClickListener { this.delegate.loggedInViewHolderActivityClick(this) }
binding.drawerMessages.setOnClickListener { this.delegate.loggedInViewHolderMessagesClick(this) }
binding.internalTools.internalTools.setOnClickListener { this.delegate.loggedInViewHolderInternalToolsClick(this) }
}

private fun selectProjectAlertIndicatorColor(isDarkMode: Boolean): Drawable? {
return if (isDarkMode) AppCompatResources.getDrawable(context(), R.drawable.circle_red_05) else AppCompatResources.getDrawable(context(), R.drawable.circle_red_06)
}

@Throws(Exception::class)
override fun bindData(data: Any?) {
this.viewModel.inputs.configureWith(requireNotNull(data as User))
Expand Down
14 changes: 11 additions & 3 deletions app/src/main/java/com/kickstarter/viewmodels/DiscoveryViewModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -131,12 +131,18 @@ interface DiscoveryViewModel {
val erroredBackingsCount = user?.erroredBackingsCount().intValueOrZero()
val unreadMessagesCount = user?.unreadMessagesCount().intValueOrZero()
val unseenActivityCount = user?.unseenActivityCount().intValueOrZero()

val ppoHasActions = when (user?.ppoHasAction()) {
true -> 1
false, null -> 0
}

return when {
erroredBackingsCount.isNonZero() -> {
(erroredBackingsCount.isNonZero() || ppoHasActions.isNonZero()) -> {
if (isDarkTheme) R.drawable.ic_menu_error_indicator_dark else R.drawable.ic_menu_error_indicator
}

(unreadMessagesCount + unseenActivityCount + erroredBackingsCount).isNonZero() -> {
(unreadMessagesCount + unseenActivityCount + erroredBackingsCount + ppoHasActions).isNonZero() -> {
if (isDarkTheme) R.drawable.ic_menu_indicator_dark else R.drawable.ic_menu_indicator
}

Expand Down Expand Up @@ -179,6 +185,7 @@ interface DiscoveryViewModel {
private val updateToolbarWithParams = BehaviorSubject.create<DiscoveryParams>()
private val successMessage = PublishSubject.create<String>()
private val messageError = PublishSubject.create<String?>()
private val darkThemeEnabled = io.reactivex.subjects.BehaviorSubject.create<Boolean>()
private var isDarkTheme = false
private var isDarkThemeInitialized = false

Expand Down Expand Up @@ -408,7 +415,6 @@ interface DiscoveryViewModel {
currentUser
.map { currentDrawerMenuIcon(it) }
.distinctUntilChanged()
.compose(bindToLifecycle())
.subscribe { if (isDarkThemeInitialized) drawerMenuIcon.onNext(it) }
}

Expand Down Expand Up @@ -462,10 +468,12 @@ interface DiscoveryViewModel {
override fun showErrorMessage(): Observable<String?> { return messageError }
override fun showNotifPermissionsRequest(): Observable<Void?> { return showNotifPermissionRequest }
override fun showConsentManagementDialog(): Observable<Void?> { return showConsentManagementDialog }
override fun darkThemeEnabled(): io.reactivex.Observable<Boolean> { return darkThemeEnabled }

fun setDarkTheme(isDarkTheme: Boolean) {
this.isDarkTheme = isDarkTheme
this.isDarkThemeInitialized = true
darkThemeEnabled.onNext(isDarkTheme)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ interface LoggedInViewHolderViewModel {
/** Emits the user to pass to delegate. */
fun user(): Observable<User>
fun pledgedProjectsIsVisible(): Observable<Boolean>
fun pledgedProjectsIndicatorIsVisible(): Observable<Boolean>
}

class ViewModel(val environment: Environment) : Inputs, Outputs {
Expand All @@ -59,6 +60,7 @@ interface LoggedInViewHolderViewModel {
private val unreadMessagesCount = BehaviorSubject.create<Int>()
private val userOutput = BehaviorSubject.create<User>()
private val pledgedProjectsIsVisible = BehaviorSubject.create<Boolean>()
private val pledgedProjectsIndicatorIsVisible = BehaviorSubject.create<Boolean>()

private val disposables = CompositeDisposable()
val inputs: Inputs = this
Expand Down Expand Up @@ -104,6 +106,11 @@ interface LoggedInViewHolderViewModel {
.subscribe { this.dashboardRowIsGone.onNext(it) }
.addToDisposable(disposables)

this.user
.map { it.ppoHasAction().isTrue() }
.subscribe { this.pledgedProjectsIndicatorIsVisible.onNext(it) }
.addToDisposable(disposables)

Observable.just(
environment.featureFlagClient()
?.getBoolean(FlagKey.ANDROID_PLEDGED_PROJECTS_OVERVIEW) ?: false
Expand Down Expand Up @@ -135,5 +142,6 @@ interface LoggedInViewHolderViewModel {
override fun user(): Observable<User> = this.userOutput

override fun pledgedProjectsIsVisible(): Observable<Boolean> = this.pledgedProjectsIsVisible
override fun pledgedProjectsIndicatorIsVisible(): Observable<Boolean> = this.pledgedProjectsIndicatorIsVisible
}
}
10 changes: 10 additions & 0 deletions app/src/main/res/drawable/circle_red_05.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="#F39C95" />
<padding
android:bottom="@dimen/grid_1_half"
android:left="@dimen/grid_1_half"
android:right="@dimen/grid_1_half"
android:top="@dimen/grid_1_half" />
</shape>
10 changes: 10 additions & 0 deletions app/src/main/res/drawable/circle_red_06.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="#B81F14" />
<padding
android:bottom="@dimen/grid_1_half"
android:left="@dimen/grid_1_half"
android:right="@dimen/grid_1_half"
android:top="@dimen/grid_1_half" />
</shape>
33 changes: 27 additions & 6 deletions app/src/main/res/layout/discovery_drawer_logged_in_view.xml
Original file line number Diff line number Diff line change
Expand Up @@ -81,14 +81,35 @@

</LinearLayout>

<TextView
android:id="@+id/pledged_projects_overview"
style="@style/DrawerTextView"
<LinearLayout
android:id="@+id/drawer_project_alerts"
style="@style/DrawerCountContainer"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/project_alerts_fpo"
app:drawableStartCompat="@drawable/ic_notification_bell"
android:visibility="gone"/>
android:contentDescription="@string/tabbar_activity"
android:orientation="horizontal"
android:visibility="gone">

<TextView
android:id="@+id/pledged_projects_overview"
style="@style/DrawerTextViewWithCount"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="@string/project_alerts_fpo"
android:layout_weight="1"
app:drawableStartCompat="@drawable/ic_notification_bell" />

<ImageView
android:id="@+id/project_alerts_indicator"
android:layout_marginEnd="@dimen/grid_3_half"
android:gravity="center_vertical"
android:layout_gravity="center"
android:layout_width="@dimen/grid_1"
android:layout_height="@dimen/grid_1"
android:contentDescription="@string/project_alerts_fpo"
android:visibility="gone"/>

</LinearLayout>

<LinearLayout
android:id="@+id/drawer_activity"
Expand Down
Loading

0 comments on commit 23ea68e

Please sign in to comment.