Skip to content

Commit 20af100

Browse files
committed
Merge branch 'hotfix/5.228.2'
2 parents 83d0695 + f112b78 commit 20af100

File tree

11 files changed

+89
-97
lines changed

11 files changed

+89
-97
lines changed

app/src/androidTest/java/com/duckduckgo/app/browser/BrowserTabViewModelTest.kt

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -577,6 +577,7 @@ class BrowserTabViewModelTest {
577577
subscriptions = mock(),
578578
duckPlayer = mockDuckPlayer,
579579
brokenSitePrompt = mockBrokenSitePrompt,
580+
userBrowserProperties = mockUserBrowserProperties,
580581
)
581582

582583
val siteFactory = SiteFactoryImpl(
@@ -2450,24 +2451,6 @@ class BrowserTabViewModelTest {
24502451
verify(mockPixel).fire(cta.shownPixel!!, cta.pixelShownParameters())
24512452
}
24522453

2453-
@Test
2454-
fun whenRegisterDaxBubbleCtaDismissedThenRegisterInDatabase() = runTest {
2455-
val cta = DaxBubbleCta.DaxIntroSearchOptionsCta(mockOnboardingStore, mockAppInstallStore)
2456-
testee.ctaViewState.value = CtaViewState(cta = cta)
2457-
2458-
testee.registerDaxBubbleCtaDismissed()
2459-
verify(mockDismissedCtaDao).insert(DismissedCta(cta.ctaId))
2460-
}
2461-
2462-
@Test
2463-
fun whenRegisterDaxBubbleCtaDismissedThenCtaChangedToNull() = runTest {
2464-
val cta = DaxBubbleCta.DaxIntroSearchOptionsCta(mockOnboardingStore, mockAppInstallStore)
2465-
testee.ctaViewState.value = CtaViewState(cta = cta)
2466-
2467-
testee.registerDaxBubbleCtaDismissed()
2468-
assertNull(testee.ctaViewState.value!!.cta)
2469-
}
2470-
24712454
@Test
24722455
fun whenRefreshCtaIfCtaAlreadyShownForCurrentPageThenReturnNull() = runTest {
24732456
setBrowserShowing(isBrowsing = true)

app/src/androidTest/java/com/duckduckgo/app/cta/ui/CtaViewModelTest.kt

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ import com.duckduckgo.app.trackerdetection.model.TrackerType
4949
import com.duckduckgo.app.trackerdetection.model.TrackingEvent
5050
import com.duckduckgo.app.widget.ui.WidgetCapabilities
5151
import com.duckduckgo.brokensite.api.BrokenSitePrompt
52+
import com.duckduckgo.browser.api.UserBrowserProperties
5253
import com.duckduckgo.common.test.CoroutineTestRule
5354
import com.duckduckgo.common.test.InstantSchedulersRule
5455
import com.duckduckgo.duckplayer.api.DuckPlayer
@@ -114,6 +115,8 @@ class CtaViewModelTest {
114115

115116
private val mockBrokenSitePrompt: BrokenSitePrompt = mock()
116117

118+
private val mockUserBrowserProperties: UserBrowserProperties = mock()
119+
117120
private val requiredDaxOnboardingCtas: List<CtaId> = listOf(
118121
CtaId.DAX_INTRO,
119122
CtaId.DAX_DIALOG_SERP,
@@ -166,6 +169,7 @@ class CtaViewModelTest {
166169
subscriptions = mockSubscriptions,
167170
duckPlayer = mockDuckPlayer,
168171
brokenSitePrompt = mockBrokenSitePrompt,
172+
userBrowserProperties = mockUserBrowserProperties,
169173
)
170174
}
171175

@@ -241,35 +245,35 @@ class CtaViewModelTest {
241245

242246
@Test
243247
fun whenRegisterDaxBubbleIntroCtaThenDatabaseNotified() = runTest {
244-
testee.registerDaxBubbleCtaDismissed(DaxBubbleCta.DaxIntroSearchOptionsCta(mockOnboardingStore, mockAppInstallStore))
248+
testee.onUserDismissedCta(DaxBubbleCta.DaxIntroSearchOptionsCta(mockOnboardingStore, mockAppInstallStore))
245249
verify(mockDismissedCtaDao).insert(DismissedCta(CtaId.DAX_INTRO))
246250
}
247251

248252
@Test
249253
fun whenRegisterDaxBubbleIntroVisitSiteCtaThenDatabaseNotified() = runTest {
250-
testee.registerDaxBubbleCtaDismissed(DaxBubbleCta.DaxIntroVisitSiteOptionsCta(mockOnboardingStore, mockAppInstallStore))
254+
testee.onUserDismissedCta(DaxBubbleCta.DaxIntroVisitSiteOptionsCta(mockOnboardingStore, mockAppInstallStore))
251255
verify(mockDismissedCtaDao).insert(DismissedCta(CtaId.DAX_INTRO_VISIT_SITE))
252256
}
253257

254258
@Test
255259
fun whenRegisterDaxBubbleEndCtaThenDatabaseNotified() = runTest {
256-
testee.registerDaxBubbleCtaDismissed(DaxBubbleCta.DaxEndCta(mockOnboardingStore, mockAppInstallStore))
260+
testee.onUserDismissedCta(DaxBubbleCta.DaxEndCta(mockOnboardingStore, mockAppInstallStore))
257261
verify(mockDismissedCtaDao).insert(DismissedCta(CtaId.DAX_END))
258262
}
259263

260264
@Test
261265
fun whenRegisterCtaAndUserHasPendingOnboardingCtasThenStageNotCompleted() = runTest {
262266
givenDaxOnboardingActive()
263267
givenShownDaxOnboardingCtas(emptyList())
264-
testee.registerDaxBubbleCtaDismissed(DaxBubbleCta.DaxEndCta(mockOnboardingStore, mockAppInstallStore))
268+
testee.onUserDismissedCta(DaxBubbleCta.DaxEndCta(mockOnboardingStore, mockAppInstallStore))
265269
verify(mockUserStageStore, times(0)).stageCompleted(any())
266270
}
267271

268272
@Test
269273
fun whenRegisterCtaAndAllDaxOnboardingCtasShownThenStageCompleted() = runTest {
270274
givenDaxOnboardingActive()
271275
givenShownDaxOnboardingCtas(requiredDaxOnboardingCtas)
272-
testee.registerDaxBubbleCtaDismissed(DaxBubbleCta.DaxEndCta(mockOnboardingStore, mockAppInstallStore))
276+
testee.onUserDismissedCta(DaxBubbleCta.DaxEndCta(mockOnboardingStore, mockAppInstallStore))
273277
verify(mockUserStageStore).stageCompleted(AppStage.DAX_ONBOARDING)
274278
}
275279

@@ -662,7 +666,7 @@ class CtaViewModelTest {
662666

663667
@Test
664668
fun whenRegisterDismissedDaxIntroVisitSiteCtaThenDatabaseNotified() = runTest {
665-
testee.registerDaxBubbleCtaDismissed(
669+
testee.onUserDismissedCta(
666670
DaxBubbleCta.DaxIntroVisitSiteOptionsCta(
667671
mockOnboardingStore,
668672
mockAppInstallStore,

app/src/main/java/com/duckduckgo/app/browser/BrowserTabFragment.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1515,7 +1515,6 @@ class BrowserTabFragment :
15151515
clientBrandHintProvider.setOn(webView?.safeSettings, url)
15161516
hideKeyboard()
15171517
omnibar.hideFindInPage()
1518-
viewModel.registerDaxBubbleCtaDismissed()
15191518
webView?.loadUrl(url, headers)
15201519
}
15211520

app/src/main/java/com/duckduckgo/app/browser/BrowserTabViewModel.kt

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2704,14 +2704,6 @@ class BrowserTabViewModel @Inject constructor(
27042704
command.value = if (shouldHideKeyboard) HideKeyboard else ShowKeyboard
27052705
}
27062706

2707-
fun registerDaxBubbleCtaDismissed() {
2708-
viewModelScope.launch {
2709-
val cta = ctaViewState.value?.cta ?: return@launch
2710-
ctaViewModel.registerDaxBubbleCtaDismissed(cta)
2711-
ctaViewState.value = currentCtaViewState().copy(cta = null)
2712-
}
2713-
}
2714-
27152707
fun onUserClickCtaOkButton(cta: Cta) {
27162708
viewModelScope.launch {
27172709
ctaViewModel.onUserClickCtaOkButton(cta)

app/src/main/java/com/duckduckgo/app/cta/ui/Cta.kt

Lines changed: 33 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ interface DaxCta {
6161
val onboardingStore: OnboardingStore
6262
val appInstallStore: AppInstallStore
6363
var ctaPixelParam: String
64+
val markAsReadOnShow: Boolean
65+
get() = false
6466

6567
companion object {
6668
const val MAX_DAYS_ALLOWED = 3
@@ -79,9 +81,6 @@ interface Cta {
7981
}
8082

8183
interface OnboardingDaxCta {
82-
val markAsReadOnShow: Boolean
83-
get() = false
84-
8584
fun showOnboardingCta(
8685
binding: FragmentBrowserTabBinding,
8786
onPrimaryCtaClicked: () -> Unit,
@@ -510,6 +509,7 @@ sealed class DaxBubbleCta(
510509
onTypingAnimationFinished: () -> Unit,
511510
) {
512511
ctaView = view
512+
clearDialog()
513513
val daxTitle = view.context.getString(title)
514514
val daxText = view.context.getString(description)
515515
val optionsViews: List<DaxButton> = listOf(
@@ -523,33 +523,27 @@ sealed class DaxBubbleCta(
523523
view.findViewById<DaxButton>(R.id.primaryCta).show()
524524
view.findViewById<DaxButton>(R.id.primaryCta).alpha = 0f
525525
view.findViewById<DaxButton>(R.id.primaryCta).text = view.context.getString(it)
526-
} ?: view.findViewById<DaxButton>(R.id.primaryCta).gone()
526+
}
527527

528528
secondaryCta?.let {
529529
view.findViewById<DaxButton>(R.id.secondaryCta).show()
530530
view.findViewById<DaxButton>(R.id.secondaryCta).alpha = 0f
531531
view.findViewById<DaxButton>(R.id.secondaryCta).text = view.context.getString(it)
532-
} ?: view.findViewById<DaxButton>(R.id.secondaryCta).gone()
532+
}
533533

534534
placeholder?.let {
535535
view.findViewById<ImageView>(R.id.placeholder).show()
536536
view.findViewById<ImageView>(R.id.placeholder).alpha = 0f
537537
view.findViewById<ImageView>(R.id.placeholder).setImageResource(it)
538-
} ?: view.findViewById<ImageView>(R.id.placeholder).gone()
539-
540-
if (options.isNullOrEmpty()) {
541-
view.findViewById<DaxButton>(R.id.daxDialogOption1).gone()
542-
view.findViewById<DaxButton>(R.id.daxDialogOption2).gone()
543-
view.findViewById<DaxButton>(R.id.daxDialogOption3).gone()
544-
view.findViewById<DaxButton>(R.id.daxDialogOption4).gone()
545-
} else {
546-
options?.let {
547-
optionsViews.forEachIndexed { index, buttonView ->
548-
if (it.size > index) {
549-
it[index].setOptionView(buttonView)
550-
} else {
551-
buttonView.gone()
552-
}
538+
}
539+
540+
options?.let {
541+
optionsViews.forEachIndexed { index, buttonView ->
542+
buttonView.show()
543+
if (it.size > index) {
544+
it[index].setOptionView(buttonView)
545+
} else {
546+
buttonView.gone()
553547
}
554548
}
555549
}
@@ -588,6 +582,23 @@ sealed class DaxBubbleCta(
588582
view.findViewById<View>(R.id.cardContainer).setOnClickListener { afterAnimation() }
589583
}
590584

585+
private fun clearDialog() {
586+
ctaView?.findViewById<DaxButton>(R.id.primaryCta)?.alpha = 0f
587+
ctaView?.findViewById<DaxButton>(R.id.primaryCta)?.gone()
588+
ctaView?.findViewById<DaxButton>(R.id.secondaryCta)?.alpha = 0f
589+
ctaView?.findViewById<DaxButton>(R.id.secondaryCta)?.gone()
590+
ctaView?.findViewById<ImageView>(R.id.placeholder)?.alpha = 0f
591+
ctaView?.findViewById<ImageView>(R.id.placeholder)?.gone()
592+
ctaView?.findViewById<DaxButton>(R.id.daxDialogOption1)?.alpha = 0f
593+
ctaView?.findViewById<DaxButton>(R.id.daxDialogOption1)?.gone()
594+
ctaView?.findViewById<DaxButton>(R.id.daxDialogOption2)?.alpha = 0f
595+
ctaView?.findViewById<DaxButton>(R.id.daxDialogOption2)?.gone()
596+
ctaView?.findViewById<DaxButton>(R.id.daxDialogOption3)?.alpha = 0f
597+
ctaView?.findViewById<DaxButton>(R.id.daxDialogOption3)?.gone()
598+
ctaView?.findViewById<DaxButton>(R.id.daxDialogOption4)?.alpha = 0f
599+
ctaView?.findViewById<DaxButton>(R.id.daxDialogOption4)?.gone()
600+
}
601+
591602
fun setOnPrimaryCtaClicked(onButtonClicked: () -> Unit) {
592603
ctaView?.findViewById<DaxButton>(R.id.primaryCta)?.setOnClickListener {
593604
onButtonClicked.invoke()
@@ -612,6 +623,8 @@ sealed class DaxBubbleCta(
612623
}
613624
}
614625

626+
override val markAsReadOnShow: Boolean = true
627+
615628
override fun pixelCancelParameters(): Map<String, String> = mapOf(Pixel.PixelParameter.CTA_SHOWN to ctaPixelParam)
616629

617630
override fun pixelOkParameters(): Map<String, String> = mapOf(Pixel.PixelParameter.CTA_SHOWN to ctaPixelParam)

app/src/main/java/com/duckduckgo/app/cta/ui/CtaViewModel.kt

Lines changed: 25 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -35,12 +35,14 @@ import com.duckduckgo.app.onboarding.store.OnboardingStore
3535
import com.duckduckgo.app.onboarding.store.UserStageStore
3636
import com.duckduckgo.app.onboarding.store.daxOnboardingActive
3737
import com.duckduckgo.app.onboarding.ui.page.extendedonboarding.ExtendedOnboardingFeatureToggles
38+
import com.duckduckgo.app.pixels.AppPixelName
3839
import com.duckduckgo.app.privacy.db.UserAllowListRepository
3940
import com.duckduckgo.app.settings.db.SettingsDataStore
4041
import com.duckduckgo.app.statistics.pixels.Pixel
4142
import com.duckduckgo.app.tabs.model.TabRepository
4243
import com.duckduckgo.app.widget.ui.WidgetCapabilities
4344
import com.duckduckgo.brokensite.api.BrokenSitePrompt
45+
import com.duckduckgo.browser.api.UserBrowserProperties
4446
import com.duckduckgo.common.utils.DispatcherProvider
4547
import com.duckduckgo.di.scopes.AppScope
4648
import com.duckduckgo.duckplayer.api.DuckPlayer
@@ -80,6 +82,7 @@ class CtaViewModel @Inject constructor(
8082
private val subscriptions: Subscriptions,
8183
private val duckPlayer: DuckPlayer,
8284
private val brokenSitePrompt: BrokenSitePrompt,
85+
private val userBrowserProperties: UserBrowserProperties,
8386
) {
8487
@ExperimentalCoroutinesApi
8588
@VisibleForTesting
@@ -136,20 +139,23 @@ class CtaViewModel @Inject constructor(
136139
pixel.fire(it, cta.pixelShownParameters())
137140
}
138141
}
139-
if (cta is OnboardingDaxDialogCta && cta.markAsReadOnShow) {
142+
if (cta is DaxCta && cta.markAsReadOnShow) {
140143
dismissedCtaDao.insert(DismissedCta(cta.ctaId))
141144
}
142145
if (cta is BrokenSitePromptDialogCta) {
143146
brokenSitePrompt.ctaShown()
144147
}
145-
}
146-
}
147148

148-
suspend fun registerDaxBubbleCtaDismissed(cta: Cta) {
149-
withContext(dispatchers.io()) {
150-
if (cta is DaxBubbleCta) {
151-
dismissedCtaDao.insert(DismissedCta(cta.ctaId))
152-
completeStageIfDaxOnboardingCompleted()
149+
// Temporary pixel
150+
val isVisitSiteSuggestionsCta = cta is DaxBubbleCta.DaxIntroVisitSiteOptionsCta || cta is OnboardingDaxDialogCta.DaxSiteSuggestionsCta
151+
if (isVisitSiteSuggestionsCta) {
152+
if (userBrowserProperties.daysSinceInstalled() <= MIN_DAYS_TO_COUNT_ONBOARDING_CTA_SHOWN) {
153+
val count = onboardingStore.visitSiteCtaDisplayCount ?: 0
154+
pixel.fire(AppPixelName.ONBOARDING_VISIT_SITE_CTA_SHOWN, mapOf("count" to count.toString()))
155+
onboardingStore.visitSiteCtaDisplayCount = count + 1
156+
} else {
157+
onboardingStore.clearVisitSiteCtaDisplayCount()
158+
}
153159
}
154160
}
155161
}
@@ -267,19 +273,14 @@ class CtaViewModel @Inject constructor(
267273
@WorkerThread
268274
private suspend fun canShowDaxIntroVisitSiteCta(): Boolean =
269275
daxOnboardingActive() && daxDialogIntroShown() && !hideTips() &&
270-
!(daxDialogNetworkShown() || daxDialogOtherShown() || daxDialogTrackersFoundShown())
276+
!(daxDialogIntroVisitSiteShown() || daxDialogNetworkShown() || daxDialogOtherShown() || daxDialogTrackersFoundShown())
271277

272278
@WorkerThread
273-
private suspend fun canShowDaxCtaEndOfJourney(): Boolean = daxOnboardingActive() &&
274-
!daxDialogEndShown() &&
275-
daxDialogIntroShown() &&
276-
!hideTips() &&
277-
(daxDialogNetworkShown() || daxDialogOtherShown() || daxDialogSerpShown() || daxDialogTrackersFoundShown())
278-
279-
private suspend fun canShowPrivacyProCta(): Boolean {
280-
return daxOnboardingActive() && !hideTips() && !daxDialogPrivacyProShown() &&
281-
subscriptions.isEligible() && extendedOnboardingFeatureToggles.privacyProCta().isEnabled()
282-
}
279+
private suspend fun canShowDaxCtaEndOfJourney(): Boolean = daxOnboardingActive() && !daxDialogEndShown() && daxDialogIntroShown() && !hideTips()
280+
281+
@WorkerThread
282+
private suspend fun canShowPrivacyProCta(): Boolean = daxOnboardingActive() && !hideTips() && !daxDialogPrivacyProShown() &&
283+
subscriptions.isEligible() && extendedOnboardingFeatureToggles.privacyProCta().isEnabled()
283284

284285
@WorkerThread
285286
private suspend fun getBrowserCta(site: Site?): Cta? {
@@ -360,21 +361,22 @@ class CtaViewModel @Inject constructor(
360361

361362
private fun daxDialogIntroShown(): Boolean = dismissedCtaDao.exists(CtaId.DAX_INTRO)
362363

364+
private fun daxDialogIntroVisitSiteShown(): Boolean = dismissedCtaDao.exists(CtaId.DAX_INTRO_VISIT_SITE)
365+
363366
// We only want to show New Tab when the Home CTAs from Onboarding has finished
364367
// https://app.asana.com/0/1157893581871903/1207769731595075/f
365368
suspend fun areBubbleDaxDialogsCompleted(): Boolean {
366369
return withContext(dispatchers.io()) {
367370
val noBrowserCtaExperiment = extendedOnboardingFeatureToggles.noBrowserCtas().isEnabled()
368-
val bubbleCtasShown = daxDialogEndShown() && (daxDialogNetworkShown() || daxDialogOtherShown() || daxDialogTrackersFoundShown())
369-
noBrowserCtaExperiment || bubbleCtasShown || hideTips() || !userStageStore.daxOnboardingActive()
371+
noBrowserCtaExperiment || daxDialogEndShown() || hideTips()
370372
}
371373
}
372374

373375
suspend fun areInContextDaxDialogsCompleted(): Boolean {
374376
return withContext(dispatchers.io()) {
375377
val noBrowserCtaExperiment = extendedOnboardingFeatureToggles.noBrowserCtas().isEnabled()
376378
val inContextDaxCtasShown = daxDialogSerpShown() && daxDialogTrackersFoundShown() && daxDialogFireEducationShown() && daxDialogEndShown()
377-
noBrowserCtaExperiment || inContextDaxCtasShown || hideTips() || !userStageStore.daxOnboardingActive()
379+
noBrowserCtaExperiment || inContextDaxCtasShown || hideTips()
378380
}
379381
}
380382

@@ -449,5 +451,6 @@ class CtaViewModel @Inject constructor(
449451

450452
companion object {
451453
private const val MAX_TABS_OPEN_FIRE_EDUCATION = 2
454+
private const val MIN_DAYS_TO_COUNT_ONBOARDING_CTA_SHOWN = 3
452455
}
453456
}

app/src/main/java/com/duckduckgo/app/onboarding/store/OnboardingStore.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,11 @@ import com.duckduckgo.app.cta.ui.DaxBubbleCta.DaxDialogIntroOption
2020

2121
interface OnboardingStore {
2222
var onboardingDialogJourney: String?
23+
var visitSiteCtaDisplayCount: Int
2324

2425
@Deprecated(message = "Parameter used for a temporary pixel")
2526
fun getSearchOptions(): List<DaxDialogIntroOption>
2627
fun getSitesOptions(): List<DaxDialogIntroOption>
2728
fun getExperimentSearchOptions(): List<DaxDialogIntroOption>
29+
fun clearVisitSiteCtaDisplayCount()
2830
}

app/src/main/java/com/duckduckgo/app/onboarding/store/OnboardingStoreImpl.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ class OnboardingStoreImpl @Inject constructor(
3535
get() = preferences.getString(ONBOARDING_JOURNEY, null)
3636
set(dialogJourney) = preferences.edit { putString(ONBOARDING_JOURNEY, dialogJourney) }
3737

38+
override var visitSiteCtaDisplayCount: Int
39+
get() = preferences.getInt(VISIT_SITE_CTA_DISPLAY_COUNT, 0)
40+
set(count) = preferences.edit { putInt(VISIT_SITE_CTA_DISPLAY_COUNT, count) }
41+
3842
override fun getSearchOptions(): List<DaxDialogIntroOption> {
3943
val country = Locale.getDefault().country
4044
val language = Locale.getDefault().language
@@ -207,8 +211,13 @@ class OnboardingStoreImpl @Inject constructor(
207211
)
208212
}
209213

214+
override fun clearVisitSiteCtaDisplayCount() {
215+
preferences.edit { remove(VISIT_SITE_CTA_DISPLAY_COUNT) }
216+
}
217+
210218
companion object {
211219
const val FILENAME = "com.duckduckgo.app.onboarding.settings"
212220
const val ONBOARDING_JOURNEY = "onboardingJourney"
221+
const val VISIT_SITE_CTA_DISPLAY_COUNT = "visitSiteCtaDisplayCount"
213222
}
214223
}

app/src/main/java/com/duckduckgo/app/pixels/AppPixelName.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ enum class AppPixelName(override val pixelName: String) : Pixel.PixelName {
5151
ONBOARDING_DAX_ALL_CTA_HIDDEN("m_odc_h"),
5252
ONBOARDING_DAX_CTA_OK_BUTTON("m_odc_ok"),
5353
ONBOARDING_DAX_CTA_CANCEL_BUTTON("m_onboarding_dax_cta_cancel"),
54+
ONBOARDING_VISIT_SITE_CTA_SHOWN("onboarding_visit_site_cta_shown"),
5455

5556
BROWSER_MENU_ALLOWLIST_ADD("mb_wla"),
5657
BROWSER_MENU_ALLOWLIST_REMOVE("mb_wlr"),

0 commit comments

Comments
 (0)