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

Expose HostConfiguration type to Compose through Treehouse #393

Merged
merged 1 commit into from
Oct 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,22 @@ package app.cash.redwood.treehouse.composeui

import android.annotation.SuppressLint
import android.content.Context
import android.content.res.Configuration
import android.content.res.Configuration.UI_MODE_NIGHT_MASK
import android.content.res.Configuration.UI_MODE_NIGHT_YES
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.widget.FrameLayout
import androidx.compose.foundation.layout.Column
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.platform.ComposeView
import app.cash.redwood.treehouse.HostConfiguration
import app.cash.redwood.treehouse.TreehouseApp
import app.cash.redwood.treehouse.TreehouseView
import app.cash.redwood.widget.Widget
import app.cash.redwood.widget.compose.ComposeWidgetChildren
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow

@SuppressLint("ViewConstructor")
public class TreehouseComposeView<T : Any>(
Expand Down Expand Up @@ -62,6 +68,12 @@ public class TreehouseComposeView<T : Any>(
}
}

private val mutableHostConfiguration =
MutableStateFlow(computeHostConfiguration(context.resources.configuration))

override val hostConfiguration: StateFlow<HostConfiguration>
get() = mutableHostConfiguration
Comment on lines +71 to +75
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


public fun setContent(content: TreehouseView.Content<T>) {
treehouseApp.dispatchers.checkUi()
this.content = content
Expand All @@ -78,6 +90,19 @@ public class TreehouseComposeView<T : Any>(
treehouseApp.onContentChanged(this)
}

override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
mutableHostConfiguration.value = computeHostConfiguration(newConfig)
}

override fun generateDefaultLayoutParams(): LayoutParams =
LayoutParams(MATCH_PARENT, MATCH_PARENT)
}

private fun computeHostConfiguration(
config: Configuration,
): HostConfiguration {
return HostConfiguration(
darkMode = (config.uiMode and UI_MODE_NIGHT_MASK) == UI_MODE_NIGHT_YES,
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,16 @@ package app.cash.redwood.treehouse

import android.annotation.SuppressLint
import android.content.Context
import android.content.res.Configuration
import android.content.res.Configuration.UI_MODE_NIGHT_MASK
import android.content.res.Configuration.UI_MODE_NIGHT_YES
import android.view.View
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
import android.widget.FrameLayout
import app.cash.redwood.widget.ViewGroupChildren
import app.cash.redwood.widget.Widget
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow

@SuppressLint("ViewConstructor")
public class TreehouseWidgetView<T : Any>(
Expand All @@ -41,6 +46,12 @@ public class TreehouseWidgetView<T : Any>(

override val children: Widget.Children<*> = ViewGroupChildren(this)

private val mutableHostConfiguration =
MutableStateFlow(computeHostConfiguration(context.resources.configuration))

override val hostConfiguration: StateFlow<HostConfiguration>
get() = mutableHostConfiguration

public fun setContent(content: TreehouseView.Content<T>) {
treehouseApp.dispatchers.checkUi()
this.content = content
Expand All @@ -57,6 +68,19 @@ public class TreehouseWidgetView<T : Any>(
treehouseApp.onContentChanged(this)
}

override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
mutableHostConfiguration.value = computeHostConfiguration(newConfig)
}

override fun generateDefaultLayoutParams(): LayoutParams =
LayoutParams(MATCH_PARENT, MATCH_PARENT)
}

private fun computeHostConfiguration(
config: Configuration,
): HostConfiguration {
return HostConfiguration(
darkMode = (config.uiMode and UI_MODE_NIGHT_MASK) == UI_MODE_NIGHT_YES,
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright (C) 2022 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package app.cash.redwood.treehouse

import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.serialization.Contextual
import kotlinx.serialization.Serializable

@Serializable
public data class FlowWithInitialValue<T>(
val initial: T,
@Contextual val flow: Flow<T>,
)

public fun <T> StateFlow<T>.toFlowWithInitialValue(): FlowWithInitialValue<T> =
FlowWithInitialValue(value, this)
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright (C) 2022 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package app.cash.redwood.treehouse

import kotlinx.serialization.Serializable

@Serializable
public data class HostConfiguration(
val darkMode: Boolean = false,
) {
public companion object
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

weeeird

}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,9 @@ import app.cash.zipline.ZiplineService
* Most callers shouldn't use this directly; instead use `TreehouseUi`.
*/
public interface ZiplineTreehouseUi : ZiplineService {
public fun start(diffSink: DiffSinkService)
public fun start(
diffSink: DiffSinkService,
hostConfigurations: FlowWithInitialValue<HostConfiguration>,
)
public fun sendEvent(event: Event)
}
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ public class TreehouseApp<T : Any>(
}
}

content.start(diffSinkService)
content.start(diffSinkService, view.hostConfiguration.toFlowWithInitialValue())
}

fun cancel() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@
package app.cash.redwood.treehouse

import app.cash.redwood.widget.Widget
import kotlinx.coroutines.flow.StateFlow

public interface TreehouseView<T : Any> {
/** This is the actual content, or null if not attached to the screen. */
public val boundContent: Content<T>?
public val children: Widget.Children<*>
public val hostConfiguration: StateFlow<HostConfiguration>

public interface Content<T : Any> {
public fun get(app: T): ZiplineTreehouseUi
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,11 @@ import app.cash.redwood.widget.MutableListChildren
import app.cash.redwood.widget.Widget
import kotlinx.cinterop.ObjCAction
import kotlinx.cinterop.cValue
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import platform.CoreGraphics.CGRectZero
import platform.UIKit.UITraitCollection
import platform.UIKit.UIUserInterfaceStyle.UIUserInterfaceStyleDark
import platform.UIKit.UIView
import platform.UIKit.addSubview
import platform.UIKit.removeFromSuperview
Expand Down Expand Up @@ -48,6 +52,11 @@ public class TreehouseUIKitView<T : Any>(
newViews.forEach(view::addSubview)
}

private val mutableHostConfiguration = MutableStateFlow(HostConfiguration())

override val hostConfiguration: StateFlow<HostConfiguration>
get() = mutableHostConfiguration

public fun setContent(content: TreehouseView.Content<T>) {
treehouseApp.dispatchers.checkUi()
this.content = content
Expand All @@ -57,6 +66,12 @@ public class TreehouseUIKitView<T : Any>(
internal fun superviewChanged() {
treehouseApp.onContentChanged(this)
}

internal fun updateHostConfiguration(traitCollection: UITraitCollection) {
mutableHostConfiguration.value = HostConfiguration(
darkMode = traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark,
)
}
}

@Suppress("unused") // cinterop erroneously exposes these as extension functions.
Expand All @@ -71,5 +86,13 @@ private class RootUiView(

@ObjCAction fun didMoveToSuperview() {
treehouseView.superviewChanged()
if (superview != null) {
treehouseView.updateHostConfiguration(traitCollection)
}
}

override fun traitCollectionDidChange(previousTraitCollection: UITraitCollection?) {
super.traitCollectionDidChange(previousTraitCollection)
treehouseView.updateHostConfiguration(traitCollection)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright (C) 2022 Square, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package app.cash.redwood.treehouse

import androidx.compose.runtime.Composable
import androidx.compose.runtime.ProvidableCompositionLocal
import androidx.compose.runtime.ReadOnlyComposable
import androidx.compose.runtime.compositionLocalOf
import app.cash.redwood.compose.WidgetVersion

/**
* Provide the configuration of the host display.
* This value will be bound automatically when Treehouse is used.
* Custom values should only be provided into a composition for testing purposes!
*
* @see WidgetVersion
*/
public val LocalHostConfiguration: ProvidableCompositionLocal<HostConfiguration> =
compositionLocalOf {
throw AssertionError("HostConfiguration was not provided!")
}

/**
* Expose various configuration properties of the host.
*
* @see HostConfiguration
*/
@Suppress("unused") // Emulating a CompositionLocal.
public val HostConfiguration.Companion.current: HostConfiguration
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ahhh there it is

@Composable
@ReadOnlyComposable
get() = LocalHostConfiguration.current
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
package app.cash.redwood.treehouse

import androidx.compose.runtime.BroadcastFrameClock
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import app.cash.redwood.protocol.Event
import app.cash.redwood.protocol.compose.DiffProducingWidget
import app.cash.redwood.protocol.compose.ProtocolRedwoodComposition
Expand All @@ -37,37 +40,39 @@ public fun TreehouseUi.asZiplineTreehouseUi(
factory = factory,
widgetVersion = widgetVersion,
)

return composition.asZiplineTreehouseUi {
composition.setContent {
Show()
}
}
return RedwoodZiplineTreehouseUi(composition, this)
}

private fun ProtocolRedwoodComposition.asZiplineTreehouseUi(
startSignal: () -> Unit = {},
): ZiplineTreehouseUi {
val delegate = this
private class RedwoodZiplineTreehouseUi(
private val composition: ProtocolRedwoodComposition,
private val treehouseUi: TreehouseUi,
) : ZiplineTreehouseUi {
private var diffSinkToClose: DiffSinkService? = null

return object : ZiplineTreehouseUi {
var diffSinkToClose: DiffSinkService? = null
override fun sendEvent(event: Event) {
composition.sendEvent(event)
}

override fun sendEvent(event: Event) {
delegate.sendEvent(event)
}
override fun start(
diffSink: DiffSinkService,
hostConfigurations: FlowWithInitialValue<HostConfiguration>,
) {
check(diffSinkToClose == null)
diffSinkToClose = diffSink
composition.start(diffSink)

override fun start(diffSink: DiffSinkService) {
check(diffSinkToClose == null)
diffSinkToClose = diffSink
delegate.start(diffSink)
startSignal()
val (initialHostConfiguration, hostConfigurationFlow) = hostConfigurations
composition.setContent {
val hostConfiguration by hostConfigurationFlow.collectAsState(initialHostConfiguration)
CompositionLocalProvider(LocalHostConfiguration provides hostConfiguration) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

infix functions so weird

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed

treehouseUi.Show()
}
}
}

override fun close() {
delegate.cancel()
diffSinkToClose?.close()
}
override fun close() {
composition.cancel()
diffSinkToClose?.close()
}
}

Expand Down