Merge pull request #7040 from thundernest/add_feature_flags
Add feature flags
This commit is contained in:
commit
878cc45c04
15 changed files with 231 additions and 3 deletions
|
@ -17,6 +17,7 @@ dependencies {
|
|||
implementation(projects.backend.pop3)
|
||||
debugImplementation(projects.backend.demo)
|
||||
|
||||
implementation(projects.core.featureflags)
|
||||
implementation(projects.feature.launcher)
|
||||
|
||||
// TODO remove account setup dependency
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
package com.fsck.k9
|
||||
|
||||
import app.k9mail.core.common.oauth.OAuthConfigurationFactory
|
||||
import app.k9mail.core.featureflag.FeatureFlagFactory
|
||||
import app.k9mail.core.featureflag.FeatureFlagProvider
|
||||
import app.k9mail.core.featureflag.InMemoryFeatureFlagProvider
|
||||
import app.k9mail.ui.widget.list.messageListWidgetModule
|
||||
import com.fsck.k9.account.newAccountModule
|
||||
import com.fsck.k9.auth.AppOAuthConfigurationFactory
|
||||
|
@ -9,6 +12,7 @@ import com.fsck.k9.controller.ControllerExtension
|
|||
import com.fsck.k9.crypto.EncryptionExtractor
|
||||
import com.fsck.k9.crypto.openpgp.OpenPgpEncryptionExtractor
|
||||
import com.fsck.k9.feature.featureModule
|
||||
import com.fsck.k9.featureflag.InMemoryFeatureFlagFactory
|
||||
import com.fsck.k9.notification.notificationModule
|
||||
import com.fsck.k9.preferences.K9StoragePersister
|
||||
import com.fsck.k9.preferences.StoragePersister
|
||||
|
@ -33,6 +37,12 @@ private val mainAppModule = module {
|
|||
single<EncryptionExtractor> { OpenPgpEncryptionExtractor.newInstance() }
|
||||
single<StoragePersister> { K9StoragePersister(get()) }
|
||||
single<OAuthConfigurationFactory> { AppOAuthConfigurationFactory() }
|
||||
single<FeatureFlagFactory> { InMemoryFeatureFlagFactory() }
|
||||
single<FeatureFlagProvider> {
|
||||
InMemoryFeatureFlagProvider(
|
||||
featureFlagFactory = get(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
val appModules = listOf(
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
package com.fsck.k9.featureflag
|
||||
|
||||
import app.k9mail.core.featureflag.FeatureFlag
|
||||
import app.k9mail.core.featureflag.FeatureFlagFactory
|
||||
import app.k9mail.core.featureflag.FeatureFlagKey
|
||||
|
||||
class InMemoryFeatureFlagFactory : FeatureFlagFactory {
|
||||
override fun createFeatureCatalog(): List<FeatureFlag> {
|
||||
return listOf(
|
||||
FeatureFlag(FeatureFlagKey("new_onboarding"), false),
|
||||
)
|
||||
}
|
||||
}
|
|
@ -11,6 +11,7 @@ dependencies {
|
|||
implementation(projects.mail.common)
|
||||
implementation(projects.uiUtils.toolbarBottomSheet)
|
||||
|
||||
implementation(projects.core.featureflags)
|
||||
implementation(projects.feature.launcher)
|
||||
// TODO: Remove AccountOauth dependency
|
||||
implementation(projects.feature.account.oauth)
|
||||
|
|
|
@ -28,6 +28,8 @@ import androidx.fragment.app.commit
|
|||
import androidx.fragment.app.commitNow
|
||||
import app.k9mail.core.android.common.contact.CachingRepository
|
||||
import app.k9mail.core.android.common.contact.ContactRepository
|
||||
import app.k9mail.core.featureflag.FeatureFlagKey
|
||||
import app.k9mail.core.featureflag.FeatureFlagProvider
|
||||
import app.k9mail.feature.launcher.FeatureLauncherActivity
|
||||
import com.fsck.k9.Account
|
||||
import com.fsck.k9.K9
|
||||
|
@ -92,6 +94,7 @@ open class MessageList :
|
|||
private val generalSettingsManager: GeneralSettingsManager by inject()
|
||||
private val messagingController: MessagingController by inject()
|
||||
private val contactRepository: ContactRepository by inject()
|
||||
private val featureFlagProvider: FeatureFlagProvider by inject()
|
||||
|
||||
private val permissionUiHelper: PermissionUiHelper = K9PermissionUiHelper(this)
|
||||
|
||||
|
@ -154,10 +157,12 @@ open class MessageList :
|
|||
deleteIncompleteAccounts(accounts)
|
||||
val hasAccountSetup = accounts.any { it.isFinishedSetup }
|
||||
if (!hasAccountSetup) {
|
||||
val useNewOnboarding = false
|
||||
if (useNewOnboarding) {
|
||||
featureFlagProvider.provide(FeatureFlagKey("new_onboarding")).onEnabled {
|
||||
FeatureLauncherActivity.launchOnboarding(this)
|
||||
} else {
|
||||
}.onDisabled {
|
||||
OnboardingActivity.launch(this)
|
||||
}.onUnavailable {
|
||||
Timber.d("Feature flag 'new_onboarding' is unavailable, falling back to old onboarding")
|
||||
OnboardingActivity.launch(this)
|
||||
}
|
||||
finish()
|
||||
|
|
8
core/featureflags/build.gradle.kts
Normal file
8
core/featureflags/build.gradle.kts
Normal file
|
@ -0,0 +1,8 @@
|
|||
plugins {
|
||||
id(ThunderbirdPlugins.Library.jvm)
|
||||
alias(libs.plugins.android.lint)
|
||||
}
|
||||
|
||||
dependencies {
|
||||
testImplementation(projects.core.testing)
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package app.k9mail.core.featureflag
|
||||
|
||||
data class FeatureFlag(
|
||||
val key: FeatureFlagKey,
|
||||
val enabled: Boolean = false,
|
||||
)
|
|
@ -0,0 +1,5 @@
|
|||
package app.k9mail.core.featureflag
|
||||
|
||||
fun interface FeatureFlagFactory {
|
||||
fun createFeatureCatalog(): List<FeatureFlag>
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
package app.k9mail.core.featureflag
|
||||
|
||||
@JvmInline
|
||||
value class FeatureFlagKey(val key: String)
|
|
@ -0,0 +1,5 @@
|
|||
package app.k9mail.core.featureflag
|
||||
|
||||
fun interface FeatureFlagProvider {
|
||||
fun provide(key: FeatureFlagKey): FeatureFlagResult
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
package app.k9mail.core.featureflag
|
||||
|
||||
sealed interface FeatureFlagResult {
|
||||
object Enabled : FeatureFlagResult
|
||||
object Disabled : FeatureFlagResult
|
||||
object Unavailable : FeatureFlagResult
|
||||
|
||||
fun onEnabled(action: () -> Unit): FeatureFlagResult {
|
||||
if (this is Enabled) {
|
||||
action()
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
fun onDisabled(action: () -> Unit): FeatureFlagResult {
|
||||
if (this is Disabled) {
|
||||
action()
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
fun onUnavailable(action: () -> Unit): FeatureFlagResult {
|
||||
if (this is Unavailable) {
|
||||
action()
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package app.k9mail.core.featureflag
|
||||
|
||||
class InMemoryFeatureFlagProvider(
|
||||
featureFlagFactory: FeatureFlagFactory,
|
||||
) : FeatureFlagProvider {
|
||||
|
||||
private val features: Map<FeatureFlagKey, FeatureFlag> =
|
||||
featureFlagFactory.createFeatureCatalog().associateBy { it.key }
|
||||
|
||||
override fun provide(key: FeatureFlagKey): FeatureFlagResult {
|
||||
return when (features[key]?.enabled) {
|
||||
null -> FeatureFlagResult.Unavailable
|
||||
true -> FeatureFlagResult.Enabled
|
||||
false -> FeatureFlagResult.Disabled
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
package app.k9mail.core.featureflags
|
||||
|
||||
import app.k9mail.core.featureflag.FeatureFlagResult
|
||||
import assertk.assertThat
|
||||
import assertk.assertions.isEqualTo
|
||||
import org.junit.Test
|
||||
|
||||
class FeatureFlagResultTest {
|
||||
|
||||
@Test
|
||||
fun `should only call onEnabled when enabled`() {
|
||||
val testSubject = FeatureFlagResult.Enabled
|
||||
|
||||
var result = ""
|
||||
|
||||
testSubject.onEnabled {
|
||||
result = "enabled"
|
||||
}.onDisabled {
|
||||
result = "disabled"
|
||||
}.onUnavailable {
|
||||
result = "unavailable"
|
||||
}
|
||||
|
||||
assertThat(result).isEqualTo("enabled")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should only call onDisabled when disabled`() {
|
||||
val testSubject = FeatureFlagResult.Disabled
|
||||
|
||||
var result = ""
|
||||
|
||||
testSubject.onEnabled {
|
||||
result = "enabled"
|
||||
}.onDisabled {
|
||||
result = "disabled"
|
||||
}.onUnavailable {
|
||||
result = "unavailable"
|
||||
}
|
||||
|
||||
assertThat(result).isEqualTo("disabled")
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should only call onUnavailable when unavailable`() {
|
||||
val testSubject = FeatureFlagResult.Unavailable
|
||||
|
||||
var result = ""
|
||||
|
||||
testSubject.onEnabled {
|
||||
result = "enabled"
|
||||
}.onDisabled {
|
||||
result = "disabled"
|
||||
}.onUnavailable {
|
||||
result = "unavailable"
|
||||
}
|
||||
|
||||
assertThat(result).isEqualTo("unavailable")
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
package app.k9mail.core.featureflags
|
||||
|
||||
import app.k9mail.core.featureflag.FeatureFlag
|
||||
import app.k9mail.core.featureflag.FeatureFlagKey
|
||||
import app.k9mail.core.featureflag.FeatureFlagResult
|
||||
import app.k9mail.core.featureflag.InMemoryFeatureFlagProvider
|
||||
import assertk.assertThat
|
||||
import assertk.assertions.isInstanceOf
|
||||
import org.junit.Test
|
||||
|
||||
class InMemoryFeatureFlagProviderTest {
|
||||
|
||||
@Test
|
||||
fun `should return FeatureFlagResult#Enabled when feature is enabled`() {
|
||||
val feature1Key = FeatureFlagKey("feature1")
|
||||
val featureFlagProvider = InMemoryFeatureFlagProvider(
|
||||
featureFlagFactory = {
|
||||
listOf(
|
||||
FeatureFlag(key = feature1Key, enabled = true),
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
val result = featureFlagProvider.provide(feature1Key)
|
||||
|
||||
assertThat(result).isInstanceOf<FeatureFlagResult.Enabled>()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should return FeatureFlagResult#Disabled when feature is disabled`() {
|
||||
val feature1Key = FeatureFlagKey("feature1")
|
||||
val featureFlagProvider = InMemoryFeatureFlagProvider(
|
||||
featureFlagFactory = {
|
||||
listOf(
|
||||
FeatureFlag(key = feature1Key, enabled = false),
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
val result = featureFlagProvider.provide(feature1Key)
|
||||
|
||||
assertThat(result).isInstanceOf<FeatureFlagResult.Disabled>()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `should return FeatureFlagResult#Unavailable when feature is not found`() {
|
||||
val feature1Key = FeatureFlagKey("feature1")
|
||||
val feature2Key = FeatureFlagKey("feature2")
|
||||
val featureFlagProvider = InMemoryFeatureFlagProvider(
|
||||
featureFlagFactory = {
|
||||
listOf(
|
||||
FeatureFlag(key = feature1Key, enabled = false),
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
val result = featureFlagProvider.provide(feature2Key)
|
||||
|
||||
assertThat(result).isInstanceOf<FeatureFlagResult.Unavailable>()
|
||||
}
|
||||
}
|
|
@ -52,6 +52,7 @@ include(
|
|||
|
||||
include(
|
||||
":core:common",
|
||||
":core:featureflags",
|
||||
":core:testing",
|
||||
":core:android:common",
|
||||
":core:android:testing",
|
||||
|
|
Loading…
Reference in a new issue