1
0
Fork 0
mirror of https://github.com/anyproto/anytype-kotlin.git synced 2025-06-08 05:47:05 +09:00

DROID-69 Tech | Feature | Add feature toggles and make logs more concise (#2699)

This commit is contained in:
Mikhail 2022-11-10 17:20:20 +03:00 committed by GitHub
parent 72fa7d39f2
commit 29347ffb4a
Signed by: github
GPG key ID: 4AEE18F83AFDEB23
33 changed files with 330 additions and 117 deletions

View file

@ -13,6 +13,12 @@ apikeyProperties.load(new FileInputStream(apikeyPropertiesFile))
def useReleaseKeystore = rootProject.file("scripts/release/app-release.jks").exists()
def localProperties = new Properties()
localProperties.with {
def lp = "local.properties"
if (new File(lp).exists()) it.load(new FileInputStream(lp))
}
android {
def config = rootProject.ext
@ -27,6 +33,11 @@ android {
versionName getBuildVersionName()
testInstrumentationRunner config.test_runner
buildConfigField "boolean", "USE_NEW_WINDOW_INSET_API", "true"
buildConfigField "boolean", "LOG_FROM_MW_LIBRARY", localProperties.getProperty("LOG_FROM_MW_LIBRARY", "false")
buildConfigField "boolean", "LOG_MW_INTERACTION", localProperties.getProperty("LOG_MW_INTERACTION", "false")
buildConfigField "boolean", "LOG_DASHBOARD_REDUCER", localProperties.getProperty("LOG_DASHBOARD_REDUCER", "false")
buildConfigField "boolean", "LOG_EDITOR_VIEWMODEL_EVENTS", localProperties.getProperty("LOG_EDITOR_VIEWMODEL_EVENTS", "false")
buildConfigField "boolean", "LOG_EDITOR_CONTROL_PANEL", localProperties.getProperty("LOG_EDITOR_CONTROL_PANEL", "false")
}
packagingOptions {
@ -69,9 +80,9 @@ android {
buildConfigField("String", "AMPLITUDE_KEY", apikeyProperties['amplitude.debug'])
//signingConfig signingConfigs.debug
firebaseAppDistribution {
artifactType="AAB"
groups="anytype-q&a, product-review, nightly"
serviceCredentialsFile="$rootDir/scripts/distribution/anytype-debug-service-account-key.json"
artifactType = "AAB"
groups = "anytype-q&a, product-review, nightly"
serviceCredentialsFile = "$rootDir/scripts/distribution/anytype-debug-service-account-key.json"
}
}
}

View file

@ -0,0 +1,24 @@
package com.anytypeio.anytype.app
import com.anytypeio.anytype.BuildConfig
import com.anytypeio.anytype.core_utils.tools.FeatureToggles
import javax.inject.Inject
class DefaultFeatureToggles @Inject constructor() : FeatureToggles {
override val isLogFromMiddlewareLibrary =
BuildConfig.LOG_FROM_MW_LIBRARY && BuildConfig.DEBUG
override val isLogMiddlewareInteraction =
BuildConfig.LOG_MW_INTERACTION && BuildConfig.DEBUG
override val isLogDashboardReducer =
BuildConfig.LOG_DASHBOARD_REDUCER && BuildConfig.DEBUG
override val isLogEditorViewModelEvents =
BuildConfig.LOG_EDITOR_VIEWMODEL_EVENTS && BuildConfig.DEBUG
override val isLogEditorControlPanelMachine =
BuildConfig.LOG_EDITOR_CONTROL_PANEL && BuildConfig.DEBUG
}

View file

@ -2,6 +2,7 @@ package com.anytypeio.anytype.di.feature
import com.anytypeio.anytype.analytics.base.Analytics
import com.anytypeio.anytype.core_utils.di.scope.PerScreen
import com.anytypeio.anytype.core_utils.tools.FeatureToggles
import com.anytypeio.anytype.domain.`object`.ObjectTypesProvider
import com.anytypeio.anytype.domain.auth.interactor.GetProfile
import com.anytypeio.anytype.domain.auth.repo.AuthRepository
@ -76,7 +77,8 @@ object HomeDashboardModule {
objectSearchSubscriptionContainer: ObjectSearchSubscriptionContainer,
cancelSearchSubscription: CancelSearchSubscription,
objectStore: ObjectStore,
createNewObject: CreateNewObject
createNewObject: CreateNewObject,
featureToggles: FeatureToggles
): HomeDashboardViewModelFactory = HomeDashboardViewModelFactory(
getProfile = getProfile,
openDashboard = openDashboard,
@ -94,7 +96,8 @@ object HomeDashboardModule {
objectSearchSubscriptionContainer = objectSearchSubscriptionContainer,
cancelSearchSubscription = cancelSearchSubscription,
objectStore = objectStore,
createNewObject = createNewObject
createNewObject = createNewObject,
featureToggles = featureToggles
)
@JvmStatic

View file

@ -7,6 +7,7 @@ import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.ObjectType
import com.anytypeio.anytype.core_models.Payload
import com.anytypeio.anytype.core_utils.di.scope.PerScreen
import com.anytypeio.anytype.core_utils.tools.FeatureToggles
import com.anytypeio.anytype.di.feature.cover.UnsplashSubComponent
import com.anytypeio.anytype.di.feature.relations.RelationAddToObjectSubComponent
import com.anytypeio.anytype.di.feature.relations.RelationCreateFromScratchForObjectBlockSubComponent
@ -223,7 +224,8 @@ object EditorSessionModule {
setDocImageIcon: SetDocumentImageIcon,
editorTemplateDelegate: EditorTemplateDelegate,
createNewObject: CreateNewObject,
objectToSet: ConvertObjectToSet
objectToSet: ConvertObjectToSet,
featureToggles: FeatureToggles
): EditorViewModelFactory = EditorViewModelFactory(
openPage = openPage,
closeObject = closePage,
@ -255,7 +257,8 @@ object EditorSessionModule {
setDocImageIcon = setDocImageIcon,
editorTemplateDelegate = editorTemplateDelegate,
createNewObject = createNewObject,
objectToSet = objectToSet
objectToSet = objectToSet,
featureToggles = featureToggles
)
@JvmStatic

View file

@ -5,6 +5,9 @@ import android.content.SharedPreferences
import androidx.security.crypto.EncryptedSharedPreferences
import androidx.security.crypto.MasterKeys
import com.anytypeio.anytype.app.DefaultAppActionManager
import com.anytypeio.anytype.core_utils.tools.DefaultUrlValidator
import com.anytypeio.anytype.core_utils.tools.FeatureToggles
import com.anytypeio.anytype.core_utils.tools.UrlValidator
import com.anytypeio.anytype.data.auth.config.DefaultFeaturesConfigProvider
import com.anytypeio.anytype.data.auth.repo.AuthCache
import com.anytypeio.anytype.data.auth.repo.AuthCacheDataStore
@ -40,18 +43,21 @@ import com.anytypeio.anytype.middleware.auth.AuthMiddleware
import com.anytypeio.anytype.middleware.block.BlockMiddleware
import com.anytypeio.anytype.middleware.interactor.Middleware
import com.anytypeio.anytype.middleware.interactor.MiddlewareFactory
import com.anytypeio.anytype.middleware.interactor.MiddlewareProtobufLogger
import com.anytypeio.anytype.middleware.interactor.ProtobufConverterProvider
import com.anytypeio.anytype.middleware.service.MiddlewareService
import com.anytypeio.anytype.middleware.service.MiddlewareServiceImplementation
import com.anytypeio.anytype.persistence.db.AnytypeDatabase
import com.anytypeio.anytype.persistence.repo.DefaultAuthCache
import com.anytypeio.anytype.persistence.repo.DefaultDebugSettingsCache
import com.anytypeio.anytype.persistence.repo.DefaultUserSettingsCache
import dagger.Binds
import dagger.Module
import dagger.Provides
import javax.inject.Named
import javax.inject.Singleton
@Module
@Module(includes = [DataModule.Bindings::class])
object DataModule {
@JvmStatic
@ -219,19 +225,15 @@ object DataModule {
@Singleton
fun provideMiddleware(
service: MiddlewareService,
factory: MiddlewareFactory
): Middleware = Middleware(service, factory)
factory: MiddlewareFactory,
logger: MiddlewareProtobufLogger
): Middleware = Middleware(service, factory, logger)
@JvmStatic
@Provides
@Singleton
fun provideMiddlewareFactory(): MiddlewareFactory = MiddlewareFactory()
@JvmStatic
@Provides
@Singleton
fun provideMiddlewareService(): MiddlewareService = MiddlewareServiceImplementation()
@JvmStatic
@Provides
@Singleton
@ -282,15 +284,17 @@ object DataModule {
): UnsplashRepository = UnsplashDataRepository(
remote = remote
)
@JvmStatic
@Provides
@Singleton
fun provideUnsplashRemote(
service: MiddlewareService
): UnsplashRemote = UnsplashMiddleware(
service = service
)
//endregion
@Module
interface Bindings {
@Binds
@Singleton
fun bindUnsplashRemote(middleware: UnsplashMiddleware): UnsplashRemote
@Binds
@Singleton
fun bindMiddlewareService(middleware: MiddlewareServiceImplementation): MiddlewareService
}
}

View file

@ -14,11 +14,12 @@ import com.anytypeio.anytype.domain.search.SubscriptionEventChannel
import com.anytypeio.anytype.domain.status.ThreadStatusChannel
import com.anytypeio.anytype.middleware.EventProxy
import com.anytypeio.anytype.middleware.interactor.*
import dagger.Binds
import dagger.Module
import dagger.Provides
import javax.inject.Singleton
@Module
@Module(includes = [EventModule.Bindings::class])
object EventModule {
@JvmStatic
@ -88,13 +89,6 @@ object EventModule {
events = proxy
)
@JvmStatic
@Provides
@Singleton
fun provideEventProxy(): EventProxy {
return EventHandler()
}
@JvmStatic
@Provides
@Singleton
@ -115,4 +109,12 @@ object EventModule {
fun provideSubscriptionEventRemoteChannel(
proxy: EventProxy
): SubscriptionEventRemoteChannel = MiddlewareSubscriptionEventChannel(events = proxy)
@Module
interface Bindings {
@Binds
@Singleton
fun bindEventProxy(handler: EventHandler): EventProxy
}
}

View file

@ -1,14 +1,19 @@
package com.anytypeio.anytype.di.main
import com.anytypeio.anytype.app.DefaultFeatureToggles
import com.anytypeio.anytype.core_utils.tools.DefaultUrlValidator
import com.anytypeio.anytype.core_utils.tools.FeatureToggles
import com.anytypeio.anytype.core_utils.tools.UrlValidator
import com.anytypeio.anytype.domain.config.Gateway
import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.middleware.interactor.MiddlewareProtobufLogger
import com.anytypeio.anytype.middleware.interactor.ProtobufConverterProvider
import dagger.Binds
import dagger.Module
import dagger.Provides
import javax.inject.Singleton
@Module
@Module(includes = [UtilModule.Bindings::class])
object UtilModule {
@JvmStatic
@ -16,8 +21,24 @@ object UtilModule {
@Singleton
fun provideUrlBuilder(gateway: Gateway): UrlBuilder = UrlBuilder(gateway)
@JvmStatic
@Provides
@Singleton
fun provideUrlValidator() : UrlValidator = DefaultUrlValidator()
@Module
interface Bindings {
@Binds
@Singleton
fun bindUrlValidator(applicator: DefaultUrlValidator): UrlValidator
@Binds
@Singleton
fun bindFeatureToggles(applicator: DefaultFeatureToggles): FeatureToggles
@Binds
@Singleton
fun bindMiddlewareProtobufLogger(logger: MiddlewareProtobufLogger.Impl): MiddlewareProtobufLogger
@Binds
@Singleton
fun bindProtobufConverterProvider(provider: ProtobufConverterProvider.Impl): ProtobufConverterProvider
}
}

View file

@ -39,6 +39,7 @@ dependencies {
implementation applicationDependencies.kotlin
implementation applicationDependencies.coroutinesAndroid
implementation applicationDependencies.dagger
implementation applicationDependencies.gson
implementation applicationDependencies.timber
implementation applicationDependencies.crashlytics

View file

@ -0,0 +1,15 @@
package com.anytypeio.anytype.core_utils.tools
interface FeatureToggles {
val isLogFromMiddlewareLibrary: Boolean
val isLogMiddlewareInteraction: Boolean
val isLogDashboardReducer: Boolean
val isLogEditorViewModelEvents: Boolean
val isLogEditorControlPanelMachine: Boolean
}

View file

@ -1,6 +1,7 @@
package com.anytypeio.anytype.core_utils.tools
import android.graphics.Color
import com.google.gson.GsonBuilder
fun String.randomColor(): Int {
var hash = 0
@ -9,4 +10,14 @@ fun String.randomColor(): Int {
}
val h = (hash % 360).toFloat()
return Color.HSVToColor(floatArrayOf(h, 0.5f, 0.9f))
}
private val gson by lazy { GsonBuilder().setPrettyPrinting().create() }
fun Any.toPrettyString(): String {
return try {
gson.toJson(this)
} catch (e: Exception) {
this.toString()
}
}

View file

@ -1,12 +1,13 @@
package com.anytypeio.anytype.core_utils.tools
import android.util.Patterns
import javax.inject.Inject
interface UrlValidator {
fun isValid(url: String) : Boolean
}
class DefaultUrlValidator: UrlValidator {
class DefaultUrlValidator @Inject constructor(): UrlValidator {
override fun isValid(url: String): Boolean {
return Patterns.WEB_URL.matcher(url).matches()
}

View file

@ -35,7 +35,8 @@ ext {
javaxInject_version = '1'
retrofit_version = '2.3.0'
okhttp_logging_interceptor_version = '3.8.1'
gson_version = '2.8.6'
gson_version = '2.10'
gson_wire_version = '4.4.3'
better_link_method_version = '2.2.0'
table_view_version = '0.8.9.4'
pickt_version = "2.0.2"
@ -118,6 +119,7 @@ ext {
javaxAnnotation: "javax.annotation:jsr250-api:$javaxAnnotations_version",
javaxInject: "javax.inject:javax.inject:$javaxInject_version",
gson: "com.google.code.gson:gson:$gson_version",
gsonWire: "com.squareup.wire:wire-gson-support:$gson_wire_version",
retrofit: "com.squareup.retrofit2:converter-gson:$retrofit_version",
okhttpLoggingInterceptor: "com.squareup.okhttp3:logging-interceptor:$okhttp_logging_interceptor_version",
timber: "com.jakewharton.timber:timber:$timber_version",
@ -177,6 +179,7 @@ ext {
protobuf = [
protobufJava: "com.google.protobuf:protobuf-java:$protobuf_java_version",
protobufJavaUtil: "com.google.protobuf:protobuf-java-util:$protobuf_java_version",
protoc: "com.google.protobuf:protoc:$protoc_version",
wireRuntime: "com.squareup.wire:wire-runtime:$wire_version"
]

View file

@ -27,6 +27,7 @@ dependencies {
implementation project(':core-models')
implementation project(':protocol')
implementation project(':data')
implementation project(':core-utils')
def applicationDependencies = rootProject.ext.mainApplication
def anytypeDependencies = rootProject.ext.anytype
@ -35,6 +36,9 @@ dependencies {
implementation applicationDependencies.kotlin
implementation applicationDependencies.timber
implementation applicationDependencies.coroutinesAndroid
implementation applicationDependencies.gsonWire
implementation applicationDependencies.gson
implementation applicationDependencies.javaxInject
implementation anytypeDependencies.middleware
testImplementation project(":test:utils")

View file

@ -6,27 +6,28 @@ import com.anytypeio.anytype.core_models.Hash
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.UnsplashImage
import com.anytypeio.anytype.data.auth.repo.unsplash.UnsplashRemote
import com.anytypeio.anytype.middleware.log.logRequest
import com.anytypeio.anytype.middleware.log.logResponse
import com.anytypeio.anytype.middleware.interactor.MiddlewareProtobufLogger
import com.anytypeio.anytype.middleware.mappers.core
import com.anytypeio.anytype.middleware.service.MiddlewareService
import javax.inject.Inject
class UnsplashMiddleware(
private val service: MiddlewareService
class UnsplashMiddleware @Inject constructor(
private val service: MiddlewareService,
private val logger: MiddlewareProtobufLogger
) : UnsplashRemote {
override fun search(query: String, limit: Int): List<UnsplashImage> {
val request = Search.Request(
query = query,
limit = limit
).also { it.logRequest() }
val response = service.unsplashSearch(request = request).also { it.logResponse() }
).also { logger.logRequest(it) }
val response = service.unsplashSearch(request = request).also { logger.logResponse(it) }
return response.pictures.map { p -> p.core() }
}
override fun download(id: Id): Hash {
val request = Download.Request(pictureId = id).also { it.logRequest() }
val response = service.unsplashDownload(request = request).also { it.logResponse() }
val request = Download.Request(pictureId = id).also { logger.logRequest(it) }
val response = service.unsplashDownload(request = request).also { logger.logResponse(it) }
return response.hash
}
}

View file

@ -9,11 +9,13 @@ import kotlinx.coroutines.flow.MutableSharedFlow
import service.Service.setEventHandlerMobile
import timber.log.Timber
import java.io.IOException
import javax.inject.Inject
class EventHandler(
private val scope: CoroutineScope = GlobalScope
class EventHandler @Inject constructor(
private val logger: MiddlewareProtobufLogger
) : EventProxy {
private val scope: CoroutineScope = GlobalScope
private val channel = MutableSharedFlow<Event>(0, 1)
init {
@ -38,10 +40,7 @@ class EventHandler(
}
private fun logEvent(event: Event) {
if (BuildConfig.DEBUG) {
val message = "[" + "\n" + event::class.java.name + ":" + "\n" + event.toString() + "\n" + "]"
Timber.d(message)
}
logger.logEvent(event)
}
override fun flow(): Flow<Event> = channel

View file

@ -39,11 +39,11 @@ import com.anytypeio.anytype.middleware.mappers.toMiddlewareModel
import com.anytypeio.anytype.middleware.mappers.toPayload
import com.anytypeio.anytype.middleware.model.CreateWalletResponse
import com.anytypeio.anytype.middleware.service.MiddlewareService
import timber.log.Timber
class Middleware(
private val service: MiddlewareService,
private val factory: MiddlewareFactory
private val factory: MiddlewareFactory,
private val logger: MiddlewareProtobufLogger,
) {
@Throws(Exception::class)
@ -1957,12 +1957,10 @@ class Middleware(
}
private fun logRequest(any: Any) {
val message = "===> " + any::class.java.canonicalName + ":" + "\n" + any.toString()
Timber.d(message)
logger.logRequest(any)
}
private fun logResponse(any: Any) {
val message = "<=== " + any::class.java.canonicalName + ":" + "\n" + any.toString()
Timber.d(message)
logger.logResponse(any)
}
}

View file

@ -0,0 +1,46 @@
package com.anytypeio.anytype.middleware.interactor
import com.anytypeio.anytype.core_utils.tools.FeatureToggles
import com.google.gson.Gson
import timber.log.Timber
import javax.inject.Inject
interface MiddlewareProtobufLogger {
fun logRequest(any: Any)
fun logResponse(any: Any)
fun logEvent(any: Any)
class Impl @Inject constructor(
private val protobufConverter: ProtobufConverterProvider,
private val featureToggles: FeatureToggles
) : MiddlewareProtobufLogger {
override fun logRequest(any: Any) {
if (featureToggles.isLogMiddlewareInteraction) {
Timber.d("request -> ${any.toLogMessage()}")
}
}
override fun logResponse(any: Any) {
if (featureToggles.isLogMiddlewareInteraction) {
Timber.d("response -> ${any.toLogMessage()}")
}
}
override fun logEvent(any: Any) {
if (featureToggles.isLogMiddlewareInteraction) {
Timber.d("event -> ${any.toLogMessage()}")
}
}
private fun Any.toLogMessage(): String {
return "${this::class.java.canonicalName}:\n${
protobufConverter.provideConverter().toJson(this)
}"
}
}
}

View file

@ -0,0 +1,22 @@
package com.anytypeio.anytype.middleware.interactor
import com.google.gson.Gson
import com.google.gson.GsonBuilder
import com.squareup.wire.WireTypeAdapterFactory
import javax.inject.Inject
interface ProtobufConverterProvider {
fun provideConverter(): Gson
class Impl @Inject constructor() : ProtobufConverterProvider {
private val provider = GsonBuilder()
.registerTypeAdapterFactory(WireTypeAdapterFactory())
.setPrettyPrinting()
.create()
override fun provideConverter(): Gson {
return provider
}
}
}

View file

@ -1,13 +0,0 @@
package com.anytypeio.anytype.middleware.log
import timber.log.Timber
fun Any.logResponse() {
val message = "===> " + this::class.java.canonicalName + ":" + "\n" + this.toString()
Timber.d(message)
}
fun Any.logRequest() {
val message = "<=== " + this::class.java.canonicalName + ":" + "\n" + this.toString()
Timber.d(message)
}

View file

@ -3,12 +3,22 @@ package com.anytypeio.anytype.middleware.service
import anytype.Rpc
import com.anytypeio.anytype.core_models.exceptions.AccountIsDeletedException
import com.anytypeio.anytype.core_models.exceptions.CreateAccountException
import com.anytypeio.anytype.core_utils.tools.FeatureToggles
import com.anytypeio.anytype.data.auth.exception.BackwardCompatilityNotSupportedException
import com.anytypeio.anytype.data.auth.exception.NotFoundObjectException
import com.anytypeio.anytype.data.auth.exception.UndoRedoExhaustedException
import service.Service
import javax.inject.Inject
class MiddlewareServiceImplementation : MiddlewareService {
class MiddlewareServiceImplementation @Inject constructor(
featureToggles: FeatureToggles
) : MiddlewareService {
init {
if (!featureToggles.isLogFromMiddlewareLibrary) {
Service.setEnv("ANYTYPE_LOG_LEVEL", "*=fatal;anytype*=error")
}
}
override fun accountCreate(request: Rpc.Account.Create.Request): Rpc.Account.Create.Response {
val encoded = Service.accountCreate(Rpc.Account.Create.Request.ADAPTER.encode(request))
@ -655,7 +665,8 @@ class MiddlewareServiceImplementation : MiddlewareService {
}
override fun objectCreateBookmark(request: Rpc.Object.CreateBookmark.Request): Rpc.Object.CreateBookmark.Response {
val encoded = Service.objectCreateBookmark(Rpc.Object.CreateBookmark.Request.ADAPTER.encode(request))
val encoded =
Service.objectCreateBookmark(Rpc.Object.CreateBookmark.Request.ADAPTER.encode(request))
val response = Rpc.Object.CreateBookmark.Response.ADAPTER.decode(encoded)
val error = response.error
if (error != null && error.code != Rpc.Object.CreateBookmark.Response.Error.Code.NULL) {
@ -666,7 +677,8 @@ class MiddlewareServiceImplementation : MiddlewareService {
}
override fun objectBookmarkFetch(request: Rpc.Object.BookmarkFetch.Request): Rpc.Object.BookmarkFetch.Response {
val encoded = Service.objectBookmarkFetch(Rpc.Object.BookmarkFetch.Request.ADAPTER.encode(request))
val encoded =
Service.objectBookmarkFetch(Rpc.Object.BookmarkFetch.Request.ADAPTER.encode(request))
val response = Rpc.Object.BookmarkFetch.Response.ADAPTER.decode(encoded)
val error = response.error
if (error != null && error.code != Rpc.Object.BookmarkFetch.Response.Error.Code.NULL) {
@ -1119,7 +1131,11 @@ class MiddlewareServiceImplementation : MiddlewareService {
override fun blockDataViewSetSource(request: Rpc.BlockDataview.SetSource.Request): Rpc.BlockDataview.SetSource.Response {
val encoded =
Service.blockDataviewSetSource(Rpc.BlockDataview.SetSource.Request.ADAPTER.encode(request))
Service.blockDataviewSetSource(
Rpc.BlockDataview.SetSource.Request.ADAPTER.encode(
request
)
)
val response = Rpc.BlockDataview.SetSource.Response.ADAPTER.decode(encoded)
val error = response.error
if (error != null && error.code != Rpc.BlockDataview.SetSource.Response.Error.Code.NULL) {

View file

@ -17,6 +17,7 @@ import org.mockito.Mock
import org.mockito.MockitoAnnotations
import org.mockito.kotlin.any
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.stub
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
@ -42,7 +43,7 @@ class MiddlewareTest {
@Before
fun setup() {
MockitoAnnotations.openMocks(this)
middleware = Middleware(service, factory)
middleware = Middleware(service, factory, mock())
}
@Test

View file

@ -7,6 +7,8 @@ import com.anytypeio.anytype.core_models.ext.amend
import com.anytypeio.anytype.core_models.ext.getChildrenIdsList
import com.anytypeio.anytype.core_models.ext.set
import com.anytypeio.anytype.core_models.ext.unset
import com.anytypeio.anytype.core_utils.tools.FeatureToggles
import com.anytypeio.anytype.core_utils.tools.toPrettyString
import com.anytypeio.anytype.domain.misc.UrlBuilder
import com.anytypeio.anytype.presentation.common.StateReducer
import com.anytypeio.anytype.presentation.dashboard.HomeDashboardStateMachine.Event
@ -34,7 +36,7 @@ sealed class HomeDashboardStateMachine {
class Interactor(
private val scope: CoroutineScope,
private val reducer: Reducer = Reducer(),
private val reducer: Reducer,
private val channel: Channel<List<Event>> = Channel(),
private val events: Flow<List<Event>> = channel.consumeAsFlow()
) {
@ -116,13 +118,19 @@ sealed class HomeDashboardStateMachine {
object OnFinishedCreatingPage : Event()
}
class Reducer : StateReducer<State, List<Event>> {
class Reducer(private val featureToggles: FeatureToggles) : StateReducer<State, List<Event>> {
override val function: suspend (State, List<Event>) -> State
get() = { state, events ->
Timber.d("REDUCE, STATE:$state, EVENT:$events")
if (featureToggles.isLogDashboardReducer) {
Timber.d("REDUCE, STATE:$state, EVENT:${events.toPrettyString()}")
}
val update = reduce(state, events)
Timber.d("REDUCE, UPDATED STATE:$update")
if (featureToggles.isLogDashboardReducer) {
Timber.d("REDUCE, UPDATED STATE:${update.toPrettyString()}")
}
update
}
@ -148,9 +156,9 @@ sealed class HomeDashboardStateMachine {
is Event.OnShowDashboard -> {
val new = event.blocks.toDashboardViews(
details = event.details,
builder = event.builder,
objectTypes = event.objectTypes
details = event.details,
builder = event.builder,
objectTypes = event.objectTypes
)
val childrenIdsList = event.blocks.getChildrenIdsList(parent = event.context)

View file

@ -22,6 +22,7 @@ import com.anytypeio.anytype.core_models.Position
import com.anytypeio.anytype.core_models.Relations
import com.anytypeio.anytype.core_utils.common.EventWrapper
import com.anytypeio.anytype.core_utils.ext.withLatestFrom
import com.anytypeio.anytype.core_utils.tools.FeatureToggles
import com.anytypeio.anytype.core_utils.ui.ViewState
import com.anytypeio.anytype.core_utils.ui.ViewStateViewModel
import com.anytypeio.anytype.domain.auth.interactor.GetProfile
@ -41,6 +42,7 @@ import com.anytypeio.anytype.domain.search.CancelSearchSubscription
import com.anytypeio.anytype.domain.search.ObjectSearchSubscriptionContainer
import com.anytypeio.anytype.presentation.BuildConfig
import com.anytypeio.anytype.presentation.dashboard.HomeDashboardStateMachine.Interactor
import com.anytypeio.anytype.presentation.dashboard.HomeDashboardStateMachine.Reducer
import com.anytypeio.anytype.presentation.dashboard.HomeDashboardStateMachine.State
import com.anytypeio.anytype.presentation.extension.sendAnalyticsRemoveObjects
import com.anytypeio.anytype.presentation.extension.sendAnalyticsRestoreFromBin
@ -84,7 +86,8 @@ class HomeDashboardViewModel(
private val objectSearchSubscriptionContainer: ObjectSearchSubscriptionContainer,
private val cancelSearchSubscription: CancelSearchSubscription,
private val objectStore: ObjectStore,
private val createNewObject: CreateNewObject
private val createNewObject: CreateNewObject,
featureToggles: FeatureToggles
) : ViewStateViewModel<State>(),
HomeDashboardEventConverter by eventConverter,
SupportNavigation<EventWrapper<AppNavigation.Command>> {
@ -93,7 +96,7 @@ class HomeDashboardViewModel(
val toasts = MutableSharedFlow<String>()
private val machine = Interactor(scope = viewModelScope)
private val machine = Interactor(scope = viewModelScope, reducer = Reducer(featureToggles))
private val movementChannel = Channel<Movement>()
private val movementChanges = movementChannel.consumeAsFlow()

View file

@ -3,6 +3,7 @@ package com.anytypeio.anytype.presentation.dashboard
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import com.anytypeio.anytype.analytics.base.Analytics
import com.anytypeio.anytype.core_utils.tools.FeatureToggles
import com.anytypeio.anytype.domain.auth.interactor.GetProfile
import com.anytypeio.anytype.domain.block.interactor.Move
import com.anytypeio.anytype.domain.config.GetConfig
@ -36,7 +37,8 @@ class HomeDashboardViewModelFactory(
private val objectSearchSubscriptionContainer: ObjectSearchSubscriptionContainer,
private val cancelSearchSubscription: CancelSearchSubscription,
private val objectStore: ObjectStore,
private val createNewObject: CreateNewObject
private val createNewObject: CreateNewObject,
private val featureToggles: FeatureToggles
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
@ -58,7 +60,8 @@ class HomeDashboardViewModelFactory(
objectSearchSubscriptionContainer = objectSearchSubscriptionContainer,
cancelSearchSubscription = cancelSearchSubscription,
objectStore = objectStore,
createNewObject = createNewObject
createNewObject = createNewObject,
featureToggles = featureToggles
) as T
}
}

View file

@ -4,6 +4,8 @@ import com.anytypeio.anytype.core_models.Block
import com.anytypeio.anytype.core_models.Block.Content.Text.Style
import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.TextBlock
import com.anytypeio.anytype.core_utils.tools.FeatureToggles
import com.anytypeio.anytype.core_utils.tools.toPrettyString
import com.anytypeio.anytype.presentation.common.StateReducer
import com.anytypeio.anytype.presentation.editor.ControlPanelMachine.Event
import com.anytypeio.anytype.presentation.editor.ControlPanelMachine.Interactor
@ -42,10 +44,10 @@ sealed class ControlPanelMachine {
* @property scope coroutine scope (state machine runs inside this scope)
*/
class Interactor(
private val scope: CoroutineScope
private val scope: CoroutineScope,
private val reducer: Reducer
) : ControlPanelMachine() {
private val reducer: Reducer = Reducer()
val channel: Channel<Event> = Channel()
private val events: Flow<Event> = channel.consumeAsFlow()
@ -232,7 +234,8 @@ sealed class ControlPanelMachine {
/**
* Concrete reducer implementation that holds all the logic related to control panels.
*/
class Reducer : StateReducer<ControlPanelState, Event> {
class Reducer(private val featureToggles: FeatureToggles) :
StateReducer<ControlPanelState, Event> {
override val function: suspend (ControlPanelState, Event) -> ControlPanelState
get() = { state, event ->
@ -938,19 +941,21 @@ sealed class ControlPanelMachine {
}
private fun logState(text: String, state: ControlPanelState) {
Timber.i(
"REDUCER, $text STATE:${
state
}"
)
if (featureToggles.isLogEditorControlPanelMachine) {
Timber.i(
"REDUCER, $text STATE:${state.toPrettyString()}"
)
}
}
private fun logEvent(event: Event) {
Timber.i(
"REDUCER, EVENT:${
event::class.qualifiedName?.substringAfter("Event.")
}"
)
if (featureToggles.isLogEditorControlPanelMachine) {
Timber.i(
"REDUCER, EVENT:${
event::class.qualifiedName?.substringAfter("Event.")
}"
)
}
}
}
}

View file

@ -6,6 +6,7 @@ import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_models.Relations
import com.anytypeio.anytype.core_models.ext.content
import com.anytypeio.anytype.core_utils.ext.replace
import com.anytypeio.anytype.core_utils.tools.toPrettyString
import com.anytypeio.anytype.presentation.common.StateReducer
import timber.log.Timber
@ -142,7 +143,9 @@ class DocumentExternalEventReducer : StateReducer<List<Block>, Event> {
target = { block -> block.id == event.id }
)
else -> state.also { Timber.d("Ignoring event: $event") }
else -> state.also {
Timber.d("Ignoring event: ${event::class.java.canonicalName}:\n${event.toPrettyString()}")
}
}
}

View file

@ -88,6 +88,8 @@ import com.anytypeio.anytype.presentation.editor.editor.Orchestrator
import com.anytypeio.anytype.presentation.editor.editor.Proxy
import com.anytypeio.anytype.presentation.editor.editor.SideEffect
import com.anytypeio.anytype.core_models.ThemeColor
import com.anytypeio.anytype.core_utils.tools.FeatureToggles
import com.anytypeio.anytype.core_utils.tools.toPrettyString
import com.anytypeio.anytype.domain.`object`.ConvertObjectToSet
import com.anytypeio.anytype.presentation.editor.editor.ViewState
import com.anytypeio.anytype.presentation.editor.editor.actions.ActionItemType
@ -249,7 +251,8 @@ class EditorViewModel(
private val setDocImageIcon: SetDocumentImageIcon,
private val templateDelegate: EditorTemplateDelegate,
private val createNewObject: CreateNewObject,
private val objectToSet: ConvertObjectToSet
private val objectToSet: ConvertObjectToSet,
private val featureToggles: FeatureToggles
) : ViewStateViewModel<ViewState>(),
PickerListener,
SupportNavigation<EventWrapper<AppNavigation.Command>>,
@ -295,7 +298,10 @@ class EditorViewModel(
val footers = MutableStateFlow<EditorFooter>(EditorFooter.None)
private val controlPanelInteractor = Interactor(viewModelScope)
private val controlPanelInteractor = Interactor(
viewModelScope,
reducer = ControlPanelMachine.Reducer(featureToggles)
)
val controlPanelViewState = MutableLiveData<ControlPanelState>()
/**
@ -496,9 +502,9 @@ class EditorViewModel(
}
private suspend fun processEvents(events: List<Event>): List<Flag> {
if (BuildConfig.DEBUG) {
Timber.d("Blocks before handling events: $blocks")
Timber.d("Events: $events")
if (featureToggles.isLogEditorViewModelEvents) {
Timber.d("Blocks before handling events: ${blocks.toPrettyString()}")
Timber.d("Events: ${events.toPrettyString()}")
}
events.forEach { event ->
when (event) {
@ -522,8 +528,8 @@ class EditorViewModel(
}
orchestrator.stores.document.update(reduce(blocks, event))
}
if (BuildConfig.DEBUG) {
Timber.d("Blocks after handling events: $blocks")
if (featureToggles.isLogEditorViewModelEvents) {
Timber.d("Blocks after handling events: ${blocks.toPrettyString()}")
}
return events.flags(context)
}

View file

@ -6,6 +6,7 @@ import com.anytypeio.anytype.analytics.base.Analytics
import com.anytypeio.anytype.core_models.Block
import com.anytypeio.anytype.core_models.Event
import com.anytypeio.anytype.core_models.Payload
import com.anytypeio.anytype.core_utils.tools.FeatureToggles
import com.anytypeio.anytype.domain.`object`.ConvertObjectToSet
import com.anytypeio.anytype.domain.`object`.ObjectTypesProvider
import com.anytypeio.anytype.domain.`object`.UpdateDetail
@ -69,7 +70,8 @@ open class EditorViewModelFactory(
private val setDocImageIcon: SetDocumentImageIcon,
private val editorTemplateDelegate: EditorTemplateDelegate,
private val createNewObject: CreateNewObject,
private val objectToSet: ConvertObjectToSet
private val objectToSet: ConvertObjectToSet,
private val featureToggles: FeatureToggles
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
@ -105,7 +107,8 @@ open class EditorViewModelFactory(
setDocImageIcon = setDocImageIcon,
templateDelegate = editorTemplateDelegate,
createNewObject = createNewObject,
objectToSet = objectToSet
objectToSet = objectToSet,
featureToggles = featureToggles
) as T
}
}

View file

@ -40,6 +40,7 @@ import org.mockito.Mock
import org.mockito.kotlin.any
import org.mockito.kotlin.doAnswer
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.stub
open class DashboardTestSetup {
@ -147,7 +148,8 @@ open class DashboardTestSetup {
main = coroutineTestRule.testDispatcher
)
),
createNewObject = createNewObject
createNewObject = createNewObject,
featureToggles = mock()
)
}

View file

@ -51,6 +51,7 @@ import org.mockito.kotlin.atLeast
import org.mockito.kotlin.doAnswer
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.stub
import org.mockito.kotlin.times
import org.mockito.kotlin.verify
@ -152,7 +153,8 @@ class HomeDashboardViewModelTest {
cancelSearchSubscription = cancelSearchSubscription,
objectStore = objectStore,
objectSearchSubscriptionContainer = objectSearchSubscriptionContainer,
createNewObject = createNewObject
createNewObject = createNewObject,
featureToggles = mock()
)
}

View file

@ -16,6 +16,7 @@ import org.junit.Rule
import org.junit.Test
import org.mockito.Mock
import org.mockito.MockitoAnnotations
import org.mockito.kotlin.mock
import kotlin.test.assertEquals
@ -28,7 +29,7 @@ class ControlPanelStateReducerTest {
lateinit var gateway: Gateway
lateinit var urlBuilder: UrlBuilder
private val reducer = ControlPanelMachine.Reducer()
private val reducer = ControlPanelMachine.Reducer(mock())
val paragraph = Block(
id = MockDataFactory.randomUuid(),

View file

@ -137,6 +137,7 @@ import org.mockito.kotlin.doAnswer
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.doThrow
import org.mockito.kotlin.eq
import org.mockito.kotlin.mock
import org.mockito.kotlin.never
import org.mockito.kotlin.stub
import org.mockito.kotlin.times
@ -4018,7 +4019,8 @@ open class EditorViewModelTest {
setDocImageIcon = setDocImageIcon,
templateDelegate = editorTemplateDelegate,
createNewObject = createNewObject,
objectToSet = objectToSet
objectToSet = objectToSet,
featureToggles = mock()
)
}

View file

@ -93,6 +93,7 @@ import org.mockito.Mock
import org.mockito.kotlin.any
import org.mockito.kotlin.doAnswer
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.stub
open class EditorPresentationTestSetup {
@ -376,7 +377,8 @@ open class EditorPresentationTestSetup {
setDocImageIcon = setDocImageIcon,
templateDelegate = editorTemplateDelegate,
createNewObject = createNewObject,
objectToSet = objectToSet
objectToSet = objectToSet,
featureToggles = mock()
)
}