Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

MBL-1665 & 1662: Project Alerts red dot indicators on overflow menu #2113

Merged
merged 11 commits into from
Sep 3, 2024
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